Skip to content

phy: add RxToken::preprocess hook#1118

Open
guoweikang wants to merge 1 commit intosmoltcp-rs:mainfrom
guoweikang:rx-token-preprocess
Open

phy: add RxToken::preprocess hook#1118
guoweikang wants to merge 1 commit intosmoltcp-rs:mainfrom
guoweikang:rx-token-preprocess

Conversation

@guoweikang
Copy link

@guoweikang guoweikang commented Feb 3, 2026

Motivation

When implementing a POSIX-like TCP server with accept() semantics in an operating system using smoltcp, we need to pre-allocate TCP sockets when receiving SYN packets, before the packet reaches the protocol stack's normal processing flow.

Currently, there is no clean way to intercept and analyze incoming packets before they are consumed by the Interface. This leads to either:

  1. Parsing packets twice (once for preprocessing, once by smoltcp)
  2. Implementing complex workarounds that duplicate packet data
  3. Maintaining a fork with custom modifications

Proposed Solution

Add an optional preprocess method to the RxToken trait that allows users to inspect packet data and access the socket set before packet consumption.

API Design

pub trait RxToken {
    fn consume<R, F>(self, f: F) -> R
    where
        F: FnOnce(&[u8]) -> R;

    fn meta(&self) -> PacketMeta {
        PacketMeta::default()
    }

    /// Optional hook called before packet consumption.
    /// 
    /// This method is invoked by `Interface::socket_ingress` after receiving
    /// a packet but before consuming it, allowing users to:
    /// - Inspect packet contents without consuming the token
    /// - Access the socket set to perform preparatory actions
    /// - Implement custom packet handling logic (e.g., pre-allocating sockets)
    /// 
    /// The default implementation does nothing, ensuring zero overhead for
    /// users who don't need this functionality.
    fn preprocess(&self, _sockets: &mut crate::iface::SocketSet<'_>) {}
}

Use Case: TCP Accept Queue

impl<'a> phy::RxToken for MyRxToken<'a> {
    fn consume<R, F>(self, f: F) -> R {
        f(self.packet_data)
    }

    fn preprocess(&self, sockets: &mut SocketSet) {
        // Parse the packet to detect TCP SYN
        if is_tcp_syn_packet(self.packet_data) {
            // Pre-create a socket in the accept queue
            prepare_tcp_socket(sockets, ...);
        }
    }
}







Add optional preprocess method to RxToken trait for custom packet
preprocessing before consumption. Default implementation is no-op.

This enables use cases like pre-allocating TCP accept queue sockets
in OS implementations.

Signed-off-by: Weikang Guo <guoweikang@kylinos.cn>
@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.27%. Comparing base (f011605) to head (543b652).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1118   +/-   ##
=======================================
  Coverage   81.27%   81.27%           
=======================================
  Files          81       81           
  Lines       24839    24841    +2     
=======================================
+ Hits        20188    20190    +2     
  Misses       4651     4651           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@whitequark
Copy link
Contributor

I'm opposed to this implementation. The phy layer should not depend on anything from the upper layers. It's not fully clear to me what the intended final use of the feature is, but this seems like something that should be a part of the Interface.

@fire332
Copy link

fire332 commented Feb 3, 2026

If all you need is accept()-like semantics, why not create a new socket to listen on the same port when a listening socket reaches the ESTABLISHED state? Nvm, that wouldn't allow new connections until the socket is established. Embassy recommends simply having several sockets listening simultaneously: https://github.com/embassy-rs/embassy/blob/main/embassy-net/src/tcp.rs

@Dirbaio
Copy link
Member

Dirbaio commented Feb 3, 2026

Adding it to Phy is a layering violation yes. However, even if this was added to Interface I think it'd still be suboptimal because it's basically exposing implementation details so users can override stuff.

If the problem is we want a TCP accept queue, we should simply add a TCP accept queue.

How would it work, not sure. Maybe a new kind of TCP socket that only holds memory for N SYN packets, so the user can poll it for new connections, and to accept them they have to set up a "full" TcpSocket.

@whitequark
Copy link
Contributor

whitequark commented Feb 3, 2026

Maybe a new kind of TCP socket that only holds memory for N SYN packets, so the user can poll it for new connections, and to accept them they have to set up a "full" TcpSocket.

This sounds reasonable to me, and it maps well to the underlying TCP reality.

@fire332
Copy link

fire332 commented Feb 3, 2026

With SYN cookies, the new kind of socket wouldn't even need a user-provided queue. But that would require being able to deserialize a TCP socket in the ESTABLISHED state from a SYN cookie.

@whitequark
Copy link
Contributor

I don't see a way to do this entirely without allocation—you'd need some sort of storage to make sure SYN cookies won't be forged, no?

@fire332
Copy link

fire332 commented Feb 3, 2026

If that's just a cryptographic hash, the RNG it's seeded from can be stored on the stack if no hardware RNG is present.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants