Skip to content

Add blog post: Rust smoltcp as an alternative TCP/IP stack for ESP-IDF#742

Open
DatanoiseTV wants to merge 3 commits into
espressif:mainfrom
DatanoiseTV:blog/rust-smoltcp-tcpip-stack
Open

Add blog post: Rust smoltcp as an alternative TCP/IP stack for ESP-IDF#742
DatanoiseTV wants to merge 3 commits into
espressif:mainfrom
DatanoiseTV:blog/rust-smoltcp-tcpip-stack

Conversation

@DatanoiseTV

@DatanoiseTV DatanoiseTV commented Jun 10, 2026

Copy link
Copy Markdown

📝 Description

A new community article introducing esp-smoltcp — a set of ESP-IDF components that run the Rust smoltcp stack as the IPv4/IPv6 data plane while keeping the IDF networking stack (esp_http_server, esp-tls, esp_http_client, esp-mqtt, mbedTLS) working without source changes.

The article covers:

  • The linker --wrap BSD-socket shim that redirects lwip_* calls to smoltcp, so application and IDF networking source is unmodified.
  • The single-task poll architecture, the slab-allocated RX frame pool, and the no_std Rust glue layer.
  • Measured throughput on a Waveshare ESP32-P4-Nano: 91.15 Mbit/s sustained on the built-in 100 Mbit/s EMAC (~96% of practical wire-line max), 0 TX failures / 0 frame-pool drops across 200 MiB.
  • The select()/VFS issue and the esp_vfs_register_fd_range() fix that removes the original CONFIG_VFS_SUPPORT_SELECT=n workaround.
  • Honest current limitations (P4-only hardware verification, ESP-Hosted Wi-Fi path untested, no SLAAC/DHCPv6, minimal bundled DNS).

This was written following the suggestion in espressif/esp-idf#18549 (cc @david-cermak), where the --wrap approach and the VFS fix were discussed.

Ready for technical/editorial review.

Publish date

Expected publish date: 2026-06-19 (per review suggestion).

Review process

  • Initiate technical review
  • Once tech review mostly done, initiate editorial review

Checks

  • Article folder and file names:
    • Folder path is content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf
    • Folder and file names have no underscores, spaces, or uppercase letters
  • New article's YAML frontmatter: title, date: 2026-06-19, summary, authors (sylwester-sosnowski, new author page + data entry + avatar), tags
  • Updated article's YAML frontmatter: N/A (new article)
  • Article media files:
    • Author avatar is .webp, ~11 KB (well under the 500 KB limit)
    • featureAsset reuses the shared img/featured/featured-rust.webp
    • No raw HTML; the architecture diagram is a fenced code block
  • Links in articles: all links checked; no Google Docs links; no ESP-IDF stable/latest documentation links
  • Git history: single clean commit, Conventional Commits message, branch based on main

🔗 Related

🧪 Testing (Hugo)

  • Frontmatter, folder/date consistency, featureAsset, author entry, and trailing-whitespace were validated against the rules in tools/ci/check_article_details.sh.
  • Hugo is not installed in my environment, so I have not run a local hugo server build; relying on this PR's CI for the Hugo build and author-presence check. Happy to fix anything CI flags.

@DatanoiseTV DatanoiseTV force-pushed the blog/rust-smoltcp-tcpip-stack branch from 1e509f3 to db2080a Compare June 10, 2026 18:39
@DatanoiseTV DatanoiseTV marked this pull request as ready for review June 10, 2026 18:48
…r ESP-IDF

New community article under content/blog/2026/06/ covering the smoltcp
ESP-IDF components: the linker --wrap BSD-socket compatibility shim, the
single-task poll architecture, measured ESP32-P4 throughput (91.15 Mbit/s
on a 100 Mbit Ethernet link), and the current limitations.

Adds the sylwester-sosnowski author page, data entry, and avatar.
Follows the suggestion in espressif/esp-idf#18549.
@DatanoiseTV DatanoiseTV force-pushed the blog/rust-smoltcp-tcpip-stack branch from db2080a to e5497fe Compare June 11, 2026 08:54

@david-cermak david-cermak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for writing this article! I went through the installation (on ESP32C3) and noticed some inconsistencies, please fix them and treat all other comments as optional suggestions.

I've also noticed that the VFS_SUPPORT_SELECT=n-version is on the main branch in your repo.

title: "Rust smoltcp as an alternative TCP/IP stack for ESP-IDF"
date: "2026-06-11"
summary: "A set of ESP-IDF components that run the Rust smoltcp stack as the IPv4/IPv6 data plane, while keeping esp_http_server, esp-tls and esp-mqtt working without source changes. This article explains the linker --wrap shim that makes it compatible, the single-task poll architecture, the throughput I measured on an ESP32-P4 (91.15 Mbit/s on a 100 Mbit link), and the limitations to be aware of."
featureAsset: "img/featured/featured-rust.webp"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's usually more engaging if you attach some picture, maybe just a screenshot from the component manager landing on your smaltcp component

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a screenshot of the esp_smoltcp registry page in the Getting started section.

into IRAM so it isn't stalled on flash access. A throughput figure without
its conditions isn't worth much, so those are the conditions.

The cost on the build is roughly +80 KiB of code and ~120 KiB of BSS (the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be also good to compare with the lwip component -- footprint, static mem usage, heap usage.

I've just quickly checked on esp32c3

Archive File                    ┃ Total Size ┃  DRAM ┃  .bss ┃ .data ┃ .text ┃ Flash Code ┃  .text ┃ Flash Data ┃
libsmoltcp_glue.a               │     108317 │ 24580 │     0 │ 24580 │     0 │      82206 │  82206 │       1531 │           0 │    1531 │
libwpa_supplicant.a             │      82613 │  1370 │  1362 │     8 │     0 │      79698 │  79698 │       1545 │           0 │    1545 │
libmbedcrypto.a                 │      77528 │   426 │   252 │    88 │    86 │      70594 │  70594 │       6508 │           0 │    6508 │
libpp.a                         │      68725 │ 20082 │  1262 │  2644 │ 16176 │      44774 │  44774 │       3869 │           0 │    3869 │
libesp_smoltcp.a                │      53940 │ 50491 │ 49905 │     0 │   586 │       3408 │   3408 │         41 │           0 │      41 │
liblwip.a                       │      48645 │   969 │   965 │     4 │     0 │      47430 │  47430 │        246 │           0 │     246 │

PS: I can maybe help with a quick comparison (temp david-cermak/esp-smoltcp@1f27f52)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a "Footprint vs lwIP" subsection using your ESP32-C3 size-components numbers (credited) — smoltcp ~85.6 KiB flash / ~74.5 KiB static RAM vs lwIP 47.4 KiB / ~1 KiB static. I spelled out the static-vs-heap caveat so the RAM comparison is fair in both directions: smoltcp pre-pays a fixed bounded budget, lwIP allocates from heap under load. A proper runtime heap-watermark comparison under identical load is still on my list — your temp commit looks like a good starting point for that, thanks.

slab pool plus the Rust scratch). On a P4 that is negligible; on a smaller
part it is a real consideration.

## The `select()` problem, and how the RFC fixed it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can skip this part and focus on the actual state of the component (maybe just refer to the GH issue, so readers are aware of the history, but in general they'd be interested in 1) how it works and 2) how they can use it 3) numbers vs lwip)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — replaced the history section with a short present-state caveat (v0.1.x needs VFS_SUPPORT_SELECT=n and why in two sentences, v0.2.0-rc.1 doesn't), with a link to the RFC issue for the background.


## Getting started

The three components are published on the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add some instructions to install cargo (not all readers and potential users would know/have it -- running just rustup worked for me)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added rustup install instructions to Getting started, plus the required sdkconfig.defaults options that were missing from the article (LWIP_COMPAT_ENABLE, LWIP_NETIF_LOOPBACK, and the v0.1.x VFS_SUPPORT_SELECT=n).

idf.py add-dependency "datanoisetv/esp_smoltcp_lwip_compat^0.1.0"
```

- Source, the complete `eth_basic` example, and architecture notes:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed two minor errors (on the main branch) worth fixing before publishing the article:

  1. include dir is no-existent: just remove this line https://github.com/DatanoiseTV/esp-smoltcp/blob/main/components/esp_smoltcp/CMakeLists.txt#L29
  2. undefined hostname:
#ifndef CONFIG_APP_HOSTNAME
#define CONFIG_APP_HOSTNAME "espressif"
#endif

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixed, and published as esp_smoltcp_lwip_compat 0.1.1 on the registry (the ^0.1.0 range in the article resolves to it automatically):

  1. The missing include dir was actually in esp_smoltcp_lwip_compat (its CMakeLists declared INCLUDE_DIRS "include" with no such dir — the component's API is purely --wrap symbols, so the line is simply removed). esp_smoltcp itself does ship its include/ with the two public headers, so that one stays.
  2. CONFIG_APP_HOSTNAME now defaults to "espressif" when undefined, exactly as you suggested — it was a leftover from the original template app's Kconfig. v0.2 replaces it with a proper component-level option (CONFIG_LWIP_COMPAT_HOSTNAME).

Fix commit: DatanoiseTV/esp-smoltcp@f7952ec, tagged v0.1.1. Thanks for actually installing it — both bugs were invisible from inside the template project where the symbols happened to exist.

@@ -0,0 +1,300 @@
---
title: "Rust smoltcp as an alternative TCP/IP stack for ESP-IDF"
date: "2026-06-11"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest 06-19 to have some time for editorial review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, date is 2026-06-19.

DatanoiseTV added a commit to DatanoiseTV/esp-smoltcp that referenced this pull request Jun 11, 2026
Two bugs broke `idf.py add-dependency` installs into projects other
than the original template, both found by @david-cermak in review of
the developer-portal article (espressif/developer-portal#742):

- CMakeLists declared INCLUDE_DIRS "include" but the component ships
  no include/ directory (its API is purely --wrap symbols), so CMake
  configure failed on any fresh checkout. Drop the line.
- esp_netif_shim.c used CONFIG_APP_HOSTNAME, a Kconfig symbol defined
  by the template app, undefined everywhere else. Default it to
  "espressif" when missing; v0.2 has a proper component Kconfig
  option instead.
- Move the publish date to 2026-06-19 as suggested
- Replace the RFC-history select() section with a short, present-state
  caveat (v0.1.x needs CONFIG_VFS_SUPPORT_SELECT=n, v0.2 does not)
- Add a footprint comparison vs lwIP (idf.py size-components, ESP32-C3),
  with the static-vs-heap RAM caveat spelled out
- Getting started: rustup install instructions, the required
  sdkconfig.defaults options, and a note about the lwip_compat 0.1.1
  packaging fix
- Add a screenshot of the component on the ESP Component Registry
@DatanoiseTV

Copy link
Copy Markdown
Author

Thanks for the thorough review — especially for actually running the install, which caught two real packaging bugs.

All comments addressed in 3e8e1fe:

  • Inconsistencies (required): the article now gives the complete install path for what readers actually get from the registry — rustup setup, the required sdkconfig.defaults (including the v0.1.x CONFIG_VFS_SUPPORT_SELECT=n), and the version story is explicit: v0.1.x has the constraint, 0.2.0-rc.1 doesn't. On your note about main: correct, main is the v0.1.x line — the VFS-select fix lives on the feat/v0.2-vfs-and-netif branch and in the 0.2.0-rc.1 prerelease on the registry; main gets it when v0.2 stabilizes.
  • Repo bugs: fixed and published as esp_smoltcp_lwip_compat 0.1.1 (details in the inline reply).
  • Optional suggestions: all taken — registry screenshot added, footprint-vs-lwIP table added (your C3 numbers, credited), select() history condensed to present state with the RFC linked, date moved to 06-19.

The preview at https://preview-developer.espressif.com/pr742/ should reflect it once CI re-runs.

tags: ["Rust", "smoltcp", "Networking", "TCP/IP", "ESP-IDF", "ESP32-P4", "lwIP"]
---

## Why a second TCP/IP stack

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is to change this to Introduction and move Why a second TCP/IP stack to where you introduce smoltcp and change the first sentence.

##Why a second TCP/IP stack

[smoltcp](https://github.com/smoltcp-rs/smoltcp) it's a
`#![no_std]` TCP/IP stack written in Rust, with no heap requirement and a...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — split into an "Introduction" section (lwIP context, the two motivations) and "Why a second TCP/IP stack" opening with the smoltcp description, as you sketched.

the stack behind your code.

Now, getting smoltcp to run on an ESP32 is not the hard part. The hard part
is running it *under ESP-IDF without giving up the IDF networking stack you

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...is running it *under ESP-IDF without giving up the native networking stack you...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, reworded to "the native networking stack".


## How compatibility works: a linker `--wrap` shim

Every IDF networking component eventually calls BSD sockets — `socket()`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDF -> ESP-IDF


Every IDF networking component eventually calls BSD sockets — `socket()`,
`bind()`, `listen()`, `accept()`, `send()`, `recv()`, `select()`,
`getaddrinfo()`. In the IDF, these come from `<lwip/sockets.h>`, and the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDF -> ESP-IDF
Please replace all IDF with ESP-IDF.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — replaced every standalone "IDF" with "ESP-IDF" throughout (prose and the architecture diagram; code identifiers like idf.py untouched).


## Performance

The hardware is a Waveshare ESP32-P4-Nano with the built-in 100 Mbit/s

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a link to the Waveshare ESP32-P4-Nano product.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, linked to the Waveshare product page.

@pedrominatel

Copy link
Copy Markdown
Member

Thank you for your contribution, @DatanoiseTV.
I have some minor comments.

- Split the opening section into "Introduction" (lwIP context) and
  "Why a second TCP/IP stack" (smoltcp), as suggested
- Replace all standalone "IDF" with "ESP-IDF"; use "native networking
  stack" where suggested
- Link the Waveshare ESP32-P4-Nano product page
@DatanoiseTV

Copy link
Copy Markdown
Author

All of @pedrominatel's editorial comments are addressed in 725c192.

On the failing blog_0-9a-f / check-links: that failure is not caused by this article's links — lychee aborts before checking anything. The workflow appends the baseline link dump verbatim to .lycheeignore, whose lines lychee parses as regexes, and a baseline URL from content/blog/2026/05/simple-provisioning/ (qrcode.html?data={%22ver%22...}) contains {...}, which is an invalid regex quantifier. Every PR touching the blog_0-9a-f bucket fails the same way right now (note blog_g-z passes here). Proposed fix in #744 — escape regex metacharacters before appending the baseline.

@pedrominatel

Copy link
Copy Markdown
Member

All of @pedrominatel's editorial comments are addressed in 725c192.

On the failing blog_0-9a-f / check-links: that failure is not caused by this article's links — lychee aborts before checking anything. The workflow appends the baseline link dump verbatim to .lycheeignore, whose lines lychee parses as regexes, and a baseline URL from content/blog/2026/05/simple-provisioning/ (qrcode.html?data={%22ver%22...}) contains {...}, which is an invalid regex quantifier. Every PR touching the blog_0-9a-f bucket fails the same way right now (note blog_g-z passes here). Proposed fix in #744 — escape regex metacharacters before appending the baseline.

Nice, @DatanoiseTV.

Don't worry about the CI failing on the link checker for your article. We will check the PR #744. Thank you for that.

@github-actions

Copy link
Copy Markdown

🎉 A preview for this PR is available at: https://preview-developer.espressif.com/pr742/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants