a smol tcp/ip stack

Overview

smoltcp

smoltcp is a standalone, event-driven TCP/IP stack that is designed for bare-metal, real-time systems. Its design goals are simplicity and robustness. Its design anti-goals include complicated compile-time computations, such as macro or type tricks, even at cost of performance degradation.

smoltcp does not need heap allocation at all, is extensively documented, and compiles on stable Rust 1.40 and later.

smoltcp achieves ~Gbps of throughput when tested against the Linux TCP stack in loopback mode.

Features

smoltcp is missing many widely deployed features, usually because no one implemented them yet. To set expectations right, both implemented and omitted features are listed.

Media layer

The only supported medium is Ethernet.

  • Regular Ethernet II frames are supported.
  • Unicast, broadcast and multicast packets are supported.
  • ARP packets (including gratuitous requests and replies) are supported.
  • ARP requests are sent at a rate not exceeding one per second.
  • Cached ARP entries expire after one minute.
  • 802.3 frames and 802.1Q are not supported.
  • Jumbo frames are not supported.

IP layer

IPv4

  • IPv4 header checksum is generated and validated.
  • IPv4 time-to-live value is configurable per socket, set to 64 by default.
  • IPv4 default gateway is supported.
  • Routing outgoing IPv4 packets is supported, through a default gateway or a CIDR route table.
  • IPv4 fragmentation is not supported.
  • IPv4 options are not supported and are silently ignored.

IPv6

  • IPv6 hop-limit value is configurable per socket, set to 64 by default.
  • Routing outgoing IPv6 packets is supported, through a default gateway or a CIDR route table.
  • IPv6 hop-by-hop header is supported.
  • ICMPv6 parameter problem message is generated in response to an unrecognized IPv6 next header.
  • ICMPv6 parameter problem message is not generated in response to an unknown IPv6 hop-by-hop option.

IP multicast

IGMP

The IGMPv1 and IGMPv2 protocols are supported, and IPv4 multicast is available.

  • Membership reports are sent in response to membership queries at equal intervals equal to the maximum response time divided by the number of groups to be reported.

ICMP layer

ICMPv4

The ICMPv4 protocol is supported, and ICMP sockets are available.

  • ICMPv4 header checksum is supported.
  • ICMPv4 echo replies are generated in response to echo requests.
  • ICMP sockets can listen to ICMPv4 Port Unreachable messages, or any ICMPv4 messages with a given IPv4 identifier field.
  • ICMPv4 protocol unreachable messages are not passed to higher layers when received.
  • ICMPv4 parameter problem messages are not generated.

ICMPv6

The ICMPv6 protocol is supported, but is not available via ICMP sockets.

  • ICMPv6 header checksum is supported.
  • ICMPv6 echo replies are generated in response to echo requests.
  • ICMPv6 protocol unreachable messages are not passed to higher layers when received.

NDISC

  • Neighbor Advertisement messages are generated in response to Neighbor Solicitations.
  • Router Advertisement messages are not generated or read.
  • Router Solicitation messages are not generated or read.
  • Redirected Header messages are not generated or read.

UDP layer

The UDP protocol is supported over IPv4 and IPv6, and UDP sockets are available.

  • Header checksum is always generated and validated.
  • In response to a packet arriving at a port without a listening socket, an ICMP destination unreachable message is generated.

TCP layer

The TCP protocol is supported over IPv4 and IPv6, and server and client TCP sockets are available.

  • Header checksum is generated and validated.
  • Maximum segment size is negotiated.
  • Window scaling is negotiated.
  • Multiple packets are transmitted without waiting for an acknowledgement.
  • Reassembly of out-of-order segments is supported, with no more than 4 or 32 gaps in sequence space.
  • Keep-alive packets may be sent at a configurable interval.
  • Retransmission timeout starts at at an estimate of RTT, and doubles every time.
  • Time-wait timeout has a fixed interval of 10 s.
  • User timeout has a configurable interval.
  • Delayed acknowledgements are supported, with configurable delay.
  • Selective acknowledgements are not implemented.
  • Silly window syndrome avoidance is not implemented.
  • Nagle's algorithm is not implemented.
  • Congestion control is not implemented.
  • Timestamping is not supported.
  • Urgent pointer is ignored.
  • Probing Zero Windows is not implemented.
  • Packetization Layer Path MTU Discovery PLPMTU is not implemented.

Installation

To use the smoltcp library in your project, add the following to Cargo.toml:

[dependencies]
smoltcp = "0.5"

The default configuration assumes a hosted environment, for ease of evaluation. You probably want to disable default features and configure them one by one:

[dependencies]
smoltcp = { version = "0.5", default-features = false, features = ["log"] }

Feature std

The std feature enables use of objects and slices owned by the networking stack through a dependency on std::boxed::Box and std::vec::Vec.

This feature is enabled by default.

Feature alloc

The alloc feature enables use of objects owned by the networking stack through a dependency on collections from the alloc crate. This only works on nightly rustc.

This feature is disabled by default.

Feature log

The log feature enables logging of events within the networking stack through the log crate. Normal events (e.g. buffer level or TCP state changes) are emitted with the TRACE log level. Exceptional events (e.g. malformed packets) are emitted with the DEBUG log level.

This feature is enabled by default.

Feature verbose

The verbose feature enables logging of events where the logging itself may incur very high overhead. For example, emitting a log line every time an application reads or writes as little as 1 octet from a socket is likely to overwhelm the application logic unless a BufReader or BufWriter is used, which are of course not available on heap-less systems.

This feature is disabled by default.

Features phy-raw_socket and phy-tap_interface

Enable smoltcp::phy::RawSocket and smoltcp::phy::TapInterface, respectively.

These features are enabled by default.

Features socket-raw, socket-udp, and socket-tcp

Enable smoltcp::socket::RawSocket, smoltcp::socket::UdpSocket, and smoltcp::socket::TcpSocket, respectively.

These features are enabled by default.

Features proto-ipv4 and proto-ipv6

Enable IPv4 and IPv6 respectively.

Hosted usage examples

smoltcp, being a freestanding networking stack, needs to be able to transmit and receive raw frames. For testing purposes, we will use a regular OS, and run smoltcp in a userspace process. Only Linux is supported (right now).

On *nix OSes, transmiting and receiving raw frames normally requires superuser privileges, but on Linux it is possible to create a persistent tap interface that can be manipulated by a specific user:

sudo ip tuntap add name tap0 mode tap user $USER
sudo ip link set tap0 up
sudo ip addr add 192.168.69.100/24 dev tap0
sudo ip -6 addr add fe80::100/64 dev tap0
sudo ip -6 addr add fdaa::100/64 dev tap0
sudo ip -6 route add fe80::/64 dev tap0
sudo ip -6 route add fdaa::/64 dev tap0

It's possible to let smoltcp access Internet by enabling routing for the tap interface:

sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE
sudo sysctl net.ipv4.ip_forward=1
sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE
sudo sysctl -w net.ipv6.conf.all.forwarding=1

Fault injection

In order to demonstrate the response of smoltcp to adverse network conditions, all examples implement fault injection, available through command-line options:

  • The --drop-chance option randomly drops packets, with given probability in percents.
  • The --corrupt-chance option randomly mutates one octet in a packet, with given probability in percents.
  • The --size-limit option drops packets larger than specified size.
  • The --tx-rate-limit and --rx-rate-limit options set the amount of tokens for a token bucket rate limiter, in packets per bucket.
  • The --shaping-interval option sets the refill interval of a token bucket rate limiter, in milliseconds.

A good starting value for --drop-chance and --corrupt-chance is 15%. A good starting value for --?x-rate-limit is 4 and --shaping-interval is 50 ms.

Note that packets dropped by the fault injector still get traced; the rx: randomly dropping a packet message indicates that the packet above it got dropped, and the tx: randomly dropping a packet message indicates that the packet below it was.

Packet dumps

All examples provide a --pcap option that writes a libpcap file containing a view of every packet as it is seen by smoltcp.

examples/tcpdump.rs

examples/tcpdump.rs is a tiny clone of the tcpdump utility.

Unlike the rest of the examples, it uses raw sockets, and so it can be used on regular interfaces, e.g. eth0 or wlan0, as well as the tap0 interface we've created above.

Read its source code, then run it as:

cargo build --example tcpdump
sudo ./target/debug/examples/tcpdump eth0

examples/httpclient.rs

examples/httpclient.rs emulates a network host that can initiate HTTP requests.

The host is assigned the hardware address 02-00-00-00-00-02, IPv4 address 192.168.69.1, and IPv6 address fdaa::1.

Read its source code, then run it as:

cargo run --example httpclient -- tap0 ADDRESS URL

For example:

cargo run --example httpclient -- tap0 93.184.216.34 http://example.org/

or:

cargo run --example httpclient -- tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/

It connects to the given address (not a hostname) and URL, and prints any returned response data. The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting.

examples/ping.rs

examples/ping.rs implements a minimal version of the ping utility using raw sockets.

The host is assigned the hardware address 02-00-00-00-00-02 and IPv4 address 192.168.69.1.

Read its source code, then run it as:

cargo run --example ping -- tap0 ADDRESS

It sends a series of 4 ICMP ECHO_REQUEST packets to the given address at one second intervals and prints out a status line on each valid ECHO_RESPONSE received.

The first ECHO_REQUEST packet is expected to be lost since arp_cache is empty after startup; the ECHO_REQUEST packet is dropped and an ARP request is sent instead.

Currently, netmasks are not implemented, and so the only address this example can reach is the other endpoint of the tap interface, 192.168.69.100. It cannot reach itself because packets entering a tap interface do not loop back.

examples/server.rs

examples/server.rs emulates a network host that can respond to basic requests.

The host is assigned the hardware address 02-00-00-00-00-01 and IPv4 address 192.168.69.1.

Read its source code, then run it as:

cargo run --example server -- tap0

It responds to:

  • pings (ping 192.168.69.1);
  • UDP packets on port 6969 (socat stdio udp4-connect:192.168.69.1:6969 <<<"abcdefg"), where it will respond "hello" to any incoming packet;
  • TCP connections on port 6969 (socat stdio tcp4-connect:192.168.69.1:6969), where it will respond "hello" to any incoming connection and immediately close it;
  • TCP connections on port 6970 (socat stdio tcp4-connect:192.168.69.1:6970 <<<"abcdefg"), where it will respond with reversed chunks of the input indefinitely.
  • TCP connections on port 6971 (socat stdio tcp4-connect:192.168.69.1:6971 </dev/urandom), which will sink data. Also, keep-alive packets (every 1 s) and a user timeout (at 2 s) are enabled on this port; try to trigger them using fault injection.
  • TCP connections on port 6972 (socat stdio tcp4-connect:192.168.69.1:6972 >/dev/null), which will source data.

Except for the socket on port 6971. the buffers are only 64 bytes long, for convenience of testing resource exhaustion conditions.

examples/client.rs

examples/client.rs emulates a network host that can initiate basic requests.

The host is assigned the hardware address 02-00-00-00-00-02 and IPv4 address 192.168.69.2.

Read its source code, then run it as:

cargo run --example client -- tap0 ADDRESS PORT

It connects to the given address (not a hostname) and port (e.g. socat stdio tcp4-listen:1234), and will respond with reversed chunks of the input indefinitely.

examples/benchmark.rs

examples/benchmark.rs implements a simple throughput benchmark.

Read its source code, then run it as:

cargo run --release --example benchmark -- tap0 [reader|writer]

It establishes a connection to itself from a different thread and reads or writes a large amount of data in one direction.

A typical result (achieved on a Intel Core i7-7500U CPU and a Linux 4.9.65 x86_64 kernel running on a Dell XPS 13 9360 laptop) is as follows:

$ cargo run -q --release --example benchmark tap0 reader
throughput: 2.556 Gbps
$ cargo run -q --release --example benchmark tap0 writer
throughput: 5.301 Gbps

Bare-metal usage examples

Examples that use no services from the host OS are necessarily less illustrative than examples that do. Because of this, only one such example is provided.

examples/loopback.rs

examples/loopback.rs sets up smoltcp to talk with itself via a loopback interface. Although it does not require std, this example still requires the alloc feature to run, as well as log, proto-ipv4 and socket-tcp.

Read its source code, then run it without std:

cargo run --example loopback --no-default-features --features="log proto-ipv4  socket-tcp alloc"

... or with std (in this case the features don't have to be explicitly listed):

cargo run --example loopback -- --pcap loopback.pcap

It opens a server and a client TCP socket, and transfers a chunk of data. You can examine the packet exchange by opening loopback.pcap in Wireshark.

If the std feature is enabled, it will print logs and packet dumps, and fault injection is possible; otherwise, nothing at all will be displayed and no options are accepted.

License

smoltcp is distributed under the terms of 0-clause BSD license.

See LICENSE-0BSD for details.

Comments
  • Add IEEE 802.15.4/6LoWPAN support

    Add IEEE 802.15.4/6LoWPAN support

    This adds the IEEE 802.15.4 frame representation. Still a work in progress and interested to know what you think about this.

    I really would like to add 6LowPAN as well, however I'm not sure where to put this in smoltcp, since 6LowPAN is kind of weird.

    opened by thvdveld 49
  • DHCPv4 client

    DHCPv4 client

    Hi

    This is an attempt at creating a DHCP client for IPv4. Please give feedback.

    Cc: @phil-opp, this PR is uses the work merged in #75.

    I've taken a completely different approach compared to #63. UDP sockets don't allow us to send and receive on unspecified/broadcast IPv4 addresses. Therefore IPv4/UDP is parsed/serialized on top of a RawSocket.

    ~~There are still two problems with RawSocketBuffer:~~

    • ~~How do I let the user provide storage? I have tried but failed to accept that as parameters in Client::new().~~ I've found the proper lifetime relation by now.
    • ~~The RawSocket is getting stuck with Error::Exhausted when contig_window is too small for the DHCP egress packet. Somehow it appears to behave like sent packets don't get dequeued from the tx_buffer. Is that known? What is my code doing wrong regarding RawSocket?~~ See #187
    opened by astro 46
  • Add raw sockets

    Add raw sockets

    Problem: smoltcp doesn't support raw sockets, low-level utilities like ping and custom IP protocols are impossible to implement.

    Solution: implement raw sockets, provide an example of their usage.

    Changes introduced by this pull request:

    • socket::udp::SocketBuffer has been factored out as generic common::RingBuffer.
    • socket::raw module has been implemented.
    • iface::EthernetInterface has been changed to support raw sockets, giving them priority over the built-in handlers.
    • The ping example has been implemented.

    Status: the ping example works as expected.

    TODOs:

    • Currently, smoltcp uses iteration over the list of sockets for packet dispatch, which is highly inefficient on the platforms where standard containers like maps and hash tables are available. The existing SocketSet container should be complimented with lookup tables for raw and tcp/udp sockets.

    Other:

    • I'm using the current version of rustfmt for my code, sometimes it looks weird.
    • I've used some recent stable Rust features like field shorthands and pub(crate), so I've bumped Rust version in Readme.md to 1.18.
    opened by batonius 32
  • Add GC & threshold to the ARP cache.

    Add GC & threshold to the ARP cache.

    Implementation of solution discussed in: https://github.com/m-labs/smoltcp/issues/83

    Main things to look at:

    1. I figured this only really makes sense for the ManagedMap::Owned case, so I conditionally compile for the alloc case. Let me know if this is wrong

    2. There weren't any tests in the neighbor.rs file that use the Owned case, so I had to start using BTreeMap. I'm not totally sure what the downsides of this are, so let me know if there's anything I should be aware of.

    3. The reason I needed to upgrade the version of managed is so that we could have len operator on ManagedMap.

    Manual Testing

    I did manually test this with the httpclient example (i changed around where run_gc gets run in order to make it do something) and checked that the sizes were correct.

    thanks for taking a look at this!

    opened by squidarth 25
  • Add support for IP mediums, v1

    Add support for IP mediums, v1

    Opening this PR so I can get feedback earlier in case there are problems with the approach I'm taking.

    This is a first draft of #334. The goal is to split EthernetInterface into Ethernet and IP parts, so the IP parts can be reused in other non-ethernet interface types (linux tun, VPNs, PPP).

    The IP module defines the following structs:

    • Config: contains IP configuration (addresses, routes) that doesn't change when processing IP packets.
    • State: contains state that can change when processing IP packets.
    • Processor: contains a &Config and a &mut State. Most of the functions moved from ethernet are here. This is just a convenience wrapper, as most functions need the config and the state, so they don't have to be passed around as parameters all the time.

    The motivation for separating Config and State is that all the ethernet code needs read-only access to Config. This way Config can be immutably borrowed many times and shared everywhere it's needed, at the same time State is mutably borrowed to process packets. Without this I was getting borrow issues with both the ethernet and ip code needing to access the ip config.

    For packet egress, the Ethernet layer passes a struct implementing a Dispatcher traint to the IP layer, which is used to pass IP packets back to the Ethernet layer.

    Missing items:

    • Tests. I'll update them as soon as we've confirmed the code structure is OK
    • IPv6 NDISC. I don't know what to do, I have 2 ideas:
      • Have ethernet::Interface inject a Socket that handles NDISC. The socket would have to mut borrow ethernet::InnerInterface so it would have to be created on the fly every time, not sure if it's even possible.
      • Pass a process_ndisc callback to process_ipv6... ugly but should work?
    • Some random FIXMEs in the code.

    My questions:

    1. any general feedback on the taken approach, or possible improvements?
    2. Most of the functions in the Ethernet code take as param the ip::Config, passing it around is somewhat annoying. This could benefit from an abstraction similar to ip::Processor: ethernet::InterfaceInner would become ethernet::State, and there would be an ethernet::Processor with ip_config, ethernet_state. Pros: no passing around ip all the time, design is consistent with IP. Cons: it's an extra struct. What do you think?
    3. Should the IP structs be pub or pub(crate)? I guess the latter, unless we want people to implement their interfaces out-of-tree?
    4. Is the route table an ethernet concept or an IP concept? To me it looks like it is an Ethernet concept. The only use in the IP code is for AnyIP. To me it feels a bit strange that AnyIP reads the route table instead of just using the CIDRs in ip_addrs. (Also AFAICT these CIDRs aren't used for anything?). If we change it to use just ip_addrs, then routes could be moved to ethernet.
    opened by Dirbaio 23
  • question: listen on 'any' port ?

    question: listen on 'any' port ?

    So I was using google netstack gvisor tcp/ip in 'promiscuous mode' (thats their terminology) where it will listen and accept connections on 'any' port - which is extremely useful, and is what i need. I see that in smoltcp we need to specificy individual ports to listen/accept, on a quick scan through the source I did not find any way to listen on any port, can you please tell me if there is any way ?

    Rgds, Gopa.

    kind/question 
    opened by gopakumarce 22
  • rewrite ::phy::raw_socket

    rewrite ::phy::raw_socket

    @whitequark

    Changes:

    1. Add cfg-if crate.
    2. Update libc version to latest.
    3. Make examples httpclient/ping/server/client/benchmark only work on linux platform.
    4. Update example tcpdump.
    5. Add phy::LinkLayer.
    6. Rewrite phy::RawSocket and phy::sys::RawSocket.

    Problems:

    1. BPF buffer reader ( see code )
    opened by LuoZijun 20
  • Stuck at retransmitting

    Stuck at retransmitting

    This code here https://github.com/m-labs/smoltcp/blob/master/src/socket/tcp.rs#L913 should either reset local_rx_dup_acks or let it count from 255 to 0 with a overflowing add or stay at 255 with a saturation. What is the desired behavior?

                            self.local_rx_dup_acks += 1;
                            if self.local_rx_dup_acks == 3 {
                            }
    
    

    thread 'main' panicked at 'attempt to add with overflow', smoltcp/src/socket/tcp.rs:913:25

    kind/bug 
    opened by pothos 19
  • RawSocket buffer size is too small

    RawSocket buffer size is too small

    https://github.com/smoltcp-rs/smoltcp/blob/027f255f904b9b7c4226cfd8b2d31f272ffa5105/src/phy/raw_socket.rs#L54-L60 You can't simply limit the buffer size with MTU. Because MTU is the maximum packet size you can send in one eth frame, not how big a frame you can receive. In case that remote MTU is bigger than local MTU, it really hurts performance(very often in AWS). https://github.com/smoltcp-rs/smoltcp/issues/493#issuecomment-862211323

    opened by qiujiangkun 18
  • Add simpler and more robust APIs for creating packets

    Add simpler and more robust APIs for creating packets

    I recently ported a project over to using the wire types from smoltcp. The places in my code where I create Arp/Udp/Ipv4/Ethernet packets are now significantly more verbose and I also introduced several bugs that could have been prevented by a more typesafe API.

    Basically the problem is this: Suppose I want to wrap a udp packet into an ipv4 packet. The way I'd currently do this is with something like.

    let udp: UdpPacket<_> = ...;
    
    let udp_raw_bytes = udp.into_inner();
    let ipv4_repr = Ipv4Repr {
        src_addr: ...,
        dst_addr: ...,
        protocol: IpProtocol::Tcp,
        payload_len: udp_raw_bytes.len(),
    };
    let mut ipv4 = Ipv4Packet::new(allocate_some_bytes(ipv4_repr.buffer_len()));
    ipv4_repr.emit(&mut ipv4);
    ipv4.payload_mut().clone_from_slice(&udp_raw_bytes);
    

    This is too long. Also it contains a couple of bugs that aren't caught by the typechecker or prevented by the API. Firstly, by converting the udp packet into raw bytes we lose information (at the type level) about what sort of data we're putting in the packet. It would be better if there was a packet-emitting function that took a UdpPacket directly so that I can't accidentally wrap one as a TCP packet. Also, I have to tell the API the lengths of data I'm dealing with in several places, and it's possible to get these wrong (by eg. calling ipv4_repr.buffer_len() thinking it will give me the total length of memory I need to allocate for the packet). The library already knows how much memory it needs, I shouldn't have to tell it. Also it's easy to forget to copy across the UDP packet data. This also shouldn't be possible to do.

    I propose to add packet-creation APIs that look something like this:

    Ipv4Packet::from_udp<T, B>(
        src_addr: Ipv4Address,
        dst_addr: Ipv4Address,
        ttl: u8,
        packet: UdpPacket<T>,
        alloc: impl FnOnce(usize) -> B,
    ) -> Ipv4Packet
    where
        T: AsRef<[u8]>,
        B: AsMut<[u8]>,
    

    That way, I can pass it the allocation function I want to use and trust it to create the packet correctly. Would something like this be desirable?

    kind/question 
    opened by canndrew 18
  • Implement TCP Window Scaling

    Implement TCP Window Scaling

    This should close #106 ~~once I put in a couple unit tests~~ (EDIT: they're done).

    I would like an early review and a set of independent testing because I suspect this PR may cause future issues.

    I did testing with the server example using buffers that were 10, 100, and 1000 times bigger, and did repeated 200 MB file transfers. The expanded windows did seem to behave correctly, and the file was correctly assembled at the other end.

    However, my testing also suggested that this increased "speed limit" has made smoltcp quite timing sensitive, and is exposing unrelated edge cases and/or timing bugs.

    The most annoying was when, on occasion, it would send a duplicate ACK every 20 microseconds between two received segments for reasons unknown. Another was more rare, but ominous. If segments arrived at a high enough speed with the CPU was starved, they would seem to get lost; the Assembler would read those that made it as "out of order". If the CPU remained starved for long enough, it was possible to exceed its tracking limit and hit a panic.

    Both problems were intermittent, and I could not troubleshoot them because adding debug code made them go away. :unamused:

    Also, changes were needed to allow rx_buffer > TCP window, even before any of the actual window sizing changes. It makes me wonder if there are more rx_buffer == TCP window baked-in assumptions that I missed somewhere. I hope not. :crossed_fingers:

    And finally, it seems no one tried to run cargo bench lately, or they would have discovered my last patch broke it. :blush: That's fixed in this patch as well.

    opened by jhwgh1968 17
  • Assembler refactors

    Assembler refactors

    Some refactors trying to simplify the data structures and API for the assemblers. See individual commit messages for details.

    @thvdveld since you wrote the original code: Could you take a look? Is there anything I might have missed?

    One thing that's wrong for now is the offset correction. Previously, the PacketAssembler was initialized when receiving the first fragment, with the right offset correction, and non-first fragments were discarded if they arrived first. The new API has only .get() which creates the assembler if it doesn't exist, so now non-first fragments will still be processed, but with the wrong offset correction. This is a problem, because only the first packet has enough info to learn the offset correction, right? Perhaps what we're supposed to do is decompress the headers before writing them to the assembler?

    opened by Dirbaio 3
  • DHCPv4 sockets seem to cause an assertion failure

    DHCPv4 sockets seem to cause an assertion failure

    Hi! This is the first time I am trying to use smoltcp, so it is entirely possible I am doing something wrong. I have been trying to add a DHCP socket to an Ethernet interface; as soon as I do that, the second call to poll() results in an assertion failure.

    Here is a reduced source file that reproduces the issue (with smoltcp v0.8.2):

    use std::collections::BTreeMap;
    
    use log::info;
    use smoltcp::{
        iface::{InterfaceBuilder, NeighborCache, Routes},
        phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken},
        socket::Dhcpv4Socket,
        time::Instant,
        wire::HardwareAddress,
        Result,
    };
    
    struct FakeDevice {}
    
    const PACKET_SIZE: usize = 1514;
    
    struct FakeRxToken {
        packet: [u8; PACKET_SIZE],
        len: usize,
    }
    
    struct FakeTxToken {}
    
    impl TxToken for FakeTxToken {
        fn consume<R, F>(self, _: Instant, len: usize, f: F) -> Result<R>
        where
            F: FnOnce(&mut [u8]) -> Result<R>,
        {
            let mut buf = [0u8; PACKET_SIZE];
            assert!(len <= PACKET_SIZE);
            let payload = &mut buf[..len];
    
            let ret = f(payload)?;
    
            info!("transmit: {:?}", payload);
    
            Ok(ret)
        }
    }
    
    impl RxToken for FakeRxToken {
        fn consume<R, F>(mut self, _: Instant, f: F) -> Result<R>
        where
            F: FnOnce(&mut [u8]) -> Result<R>,
        {
            let packet = &mut self.packet[..self.len];
            info!("receive: {:?}", packet);
            f(packet)
        }
    }
    
    impl<'d> Device<'d> for FakeDevice {
        type TxToken = FakeTxToken;
        type RxToken = FakeRxToken;
    
        fn receive(&'d mut self) -> Option<(Self::RxToken, Self::TxToken)> {
            None
        }
    
        fn transmit(&'d mut self) -> Option<Self::TxToken> {
            Some(FakeTxToken {})
        }
    
        fn capabilities(&self) -> DeviceCapabilities {
            let mut caps = DeviceCapabilities::default();
            caps.medium = Medium::Ethernet;
            caps.max_transmission_unit = PACKET_SIZE;
            caps.max_burst_size = Some(1);
            caps
        }
    }
    
    fn main() {
        env_logger::init();
        let device = FakeDevice {};
    
        let routes = Routes::new(BTreeMap::new());
        let neighbor_cache = NeighborCache::new(BTreeMap::new());
        let hw_addr = HardwareAddress::Ethernet(smoltcp::wire::EthernetAddress::from_bytes(&[
            0, 1, 2, 3, 4, 5,
        ]));
    
        let mut interface = InterfaceBuilder::new(device, Vec::new())
            .hardware_addr(hw_addr)
            .routes(routes)
            .neighbor_cache(neighbor_cache)
            .finalize();
    
        let dhcp_socket = Dhcpv4Socket::new();
        let dhcp_socket_handle = interface.add_socket(dhcp_socket);
    
        for i in 0.. {
            info!("IPs: {:?}", interface.ip_addrs());
            let status = interface.poll(Instant::from_micros(i));
            info!("Poll: {:?}", status);
    
            let dhcp_status = interface
                .get_socket::<Dhcpv4Socket>(dhcp_socket_handle)
                .poll();
    
            info!("{:?}", dhcp_status);
        }
    }
    

    and here are the corresponding logs:

    luca@xps ~/smoltcp-repro  $ RUST_LOG=trace cargo run
       Compiling smoltcp-repro v0.1.0 (/home/luca/smoltcp-repro)
        Finished dev [unoptimized + debuginfo] target(s) in 0.49s
         Running `target/debug/smoltcp-repro`
    [2022-12-11T01:03:38Z TRACE smoltcp::iface::socket_set] [0]: adding
    [2022-12-11T01:03:38Z INFO  smoltcp_repro] IPs: []
    [2022-12-11T01:03:38Z DEBUG smoltcp::socket::dhcpv4] DHCP send DISCOVER to 255.255.255.255: Repr { message_type: Discover, transaction_id: 4260165794, client_hardware_address: Address([0, 1, 2, 3, 4, 5]), client_ip: Address([0, 0, 0, 0]), your_ip: Address([0, 0, 0, 0]), server_ip: Address([0, 0, 0, 0]), router: None, subnet_mask: None, relay_agent_ip: Address([0, 0, 0, 0]), broadcast: false, requested_ip: None, client_identifier: Some(Address([0, 1, 2, 3, 4, 5])), server_identifier: None, parameter_request_list: Some([1, 3, 6]), dns_servers: None, max_size: Some(1432), lease_duration: None }
    [2022-12-11T01:03:38Z TRACE smoltcp::iface::socket_meta] #0: neighbor 255.255.255.255 missing, silencing until t+3.000s
    [2022-12-11T01:03:38Z INFO  smoltcp_repro] Poll: Ok(false)
    [2022-12-11T01:03:38Z INFO  smoltcp_repro] Some(Deconfigured)
    [2022-12-11T01:03:38Z INFO  smoltcp_repro] IPs: []
    thread 'main' panicked at 'assertion failed: protocol_addr.is_unicast()', /home/luca/.cargo/registry/src/github.com-1ecc6299db9ec823/smoltcp-0.8.2/src/iface/neighbor.rs:199:9
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    Any help would be appreciated!

    Note that the problem seems to go away if using current GitHub main:

    use std::collections::BTreeMap;
    
    use log::info;
    use smoltcp::{
        iface::{InterfaceBuilder, NeighborCache, Routes, SocketSet},
        phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken},
        socket::dhcpv4::Socket as Dhcpv4Socket,
        time::Instant,
        wire::HardwareAddress,
        Result,
    };
    
    struct FakeDevice {}
    
    const PACKET_SIZE: usize = 1514;
    
    struct FakeRxToken {
        packet: [u8; PACKET_SIZE],
        len: usize,
    }
    
    struct FakeTxToken {}
    
    impl TxToken for FakeTxToken {
        fn consume<R, F>(self, _: Instant, len: usize, f: F) -> Result<R>
        where
            F: FnOnce(&mut [u8]) -> Result<R>,
        {
            let mut buf = [0u8; PACKET_SIZE];
            assert!(len <= PACKET_SIZE);
            let payload = &mut buf[..len];
    
            let ret = f(payload)?;
    
            info!("transmit: {:?}", payload);
    
            Ok(ret)
        }
    }
    
    impl RxToken for FakeRxToken {
        fn consume<R, F>(mut self, _: Instant, f: F) -> Result<R>
        where
            F: FnOnce(&mut [u8]) -> Result<R>,
        {
            let packet = &mut self.packet[..self.len];
            info!("receive: {:?}", packet);
            f(packet)
        }
    }
    
    impl Device for FakeDevice {
        type TxToken<'d> = FakeTxToken;
        type RxToken<'d> = FakeRxToken;
    
        fn receive(&mut self) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
            None
        }
    
        fn transmit(&mut self) -> Option<Self::TxToken<'_>> {
            Some(FakeTxToken {})
        }
    
        fn capabilities(&self) -> DeviceCapabilities {
            let mut caps = DeviceCapabilities::default();
            caps.medium = Medium::Ethernet;
            caps.max_transmission_unit = PACKET_SIZE;
            caps.max_burst_size = Some(1);
            caps
        }
    }
    
    fn main() {
        env_logger::init();
        let mut device = FakeDevice {};
    
        let routes = Routes::new(BTreeMap::new());
        let neighbor_cache = NeighborCache::new(BTreeMap::new());
        let hw_addr = HardwareAddress::Ethernet(smoltcp::wire::EthernetAddress::from_bytes(&[
            0, 1, 2, 3, 4, 5,
        ]));
    
        let mut interface = InterfaceBuilder::new()
            .hardware_addr(hw_addr)
            .routes(routes)
            .neighbor_cache(neighbor_cache)
            .finalize(&mut device);
    
        let dhcp_socket = Dhcpv4Socket::new();
        let mut socket_set = SocketSet::new(vec![]);
        let dhcp_socket_handle = socket_set.add(dhcp_socket);
    
        for i in 0.. {
            info!("IPs: {:?}", interface.ip_addrs());
            let status = interface.poll(Instant::from_micros(i), &mut device, &mut socket_set);
            info!("Poll: {:?}", status);
    
            let dhcp_status = socket_set
                .get_mut::<Dhcpv4Socket>(dhcp_socket_handle)
                .poll();
    
            info!("{:?}", dhcp_status);
        }
    }
    
    opened by veluca93 0
  • defmt does not work with alloc feature

    defmt does not work with alloc feature

    This error is pretty curious, I wonder if that is due to core changes since I remember Box should have had handled Format to passthrough transparently to the underlying type:

    (base) PS C:\Users\steve\esp32c2-rust-hello-world> cargo build
       Compiling smoltcp v0.8.2
       Compiling esp32c3-hal v0.3.0
       Compiling esp-wifi v0.1.0 (https://github.com/esp-rs/esp-wifi.git?rev=4d8243f#4d8243f7)
    error[E0277]: the trait bound `Box<[Contig; 32]>: Format` is not satisfied
       --> C:\Users\steve\.cargo\registry\src\github.com-1ecc6299db9ec823\smoltcp-0.8.2\src\storage\assembler.rs:104:5
        |
    99  | #[cfg_attr(feature = "defmt", derive(defmt::Format))]
        |                                      ------------- required by a bound introduced by this call
    ...
    104 |     contigs: Box<[Contig; CONTIG_COUNT]>,
        |     ^^^^^^^ the trait `Format` is not implemented for `Box<[Contig; 32]>`
        |
        = help: the following other types implement trait `Format`:
                  &T
                  &mut T
                  ()
                  (T0, T1)
                  (T0, T1, T2)
                  (T0, T1, T2, T3)
                  (T0, T1, T2, T3, T4)
                  (T0, T1, T2, T3, T4, T5)
                and 185 others
    note: required by a bound in `defmt::export::fmt`
       --> C:\Users\steve\.cargo\registry\src\github.com-1ecc6299db9ec823\defmt-0.3.2\src\export\mod.rs:137:15
        |
    137 | pub fn fmt<T: Format + ?Sized>(f: &T) {
        |               ^^^^^^ required by this bound in `defmt::export::fmt`
    
    For more information about this error, try `rustc --explain E0277`.                                                                                                                                                                                                                                                    
    error: could not compile `smoltcp` due to previous error
    warning: build failed, waiting for other jobs to finish...
    

    Cargo.toml entry:

    smoltcp = { version = "0.8.2", default-features = false, features = [
         "medium-ethernet", "medium-ip", "medium-ieee802154",
         "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6",
         "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4",
         "async", "defmt", "alloc"
    ] }
    

    If I disabled alloc or defmt it is absolutely working: https://github.com/smoltcp-rs/smoltcp/blob/0e614bc6cca8b97a1a11c27154f76221c5403739/src/storage/assembler.rs#L98-L99

    opened by stevefan1999-personal 2
  • new crates.io release of smoltcp with GAT-device ?

    new crates.io release of smoltcp with GAT-device ?

    Hi @whitequark / @Dirbaio - can you pls release a new version of smoltcp on crates.io so that the smoltcp GAT-device capability is available on crates.io

    opened by gopakumarce 1
  • smoltcp support windows platform?

    smoltcp support windows platform?

    we are trying to use smoltcp in windows platform and transfer packet throgh wintun (we add windows platform support in rust-tun).

    we found that some packet is not recognized, icmp packet will lost

    opened by mikexxma 0
Owner
smoltcp
TCP/IP Stack for Embedded Rust
smoltcp
A high performance TCP SYN port scanner.

Armada A High-Performance TCP SYN scanner What is Armada? Armada is a high performance TCP SYN scanner. This is equivalent to the type of scanning tha

resync 259 Dec 19, 2022
A tcp over http2 + tls proxy

mtunnel A tcp over http2 + tls proxy. Usage 1. get certificates, by following steps. 2. make your config client config: { "local_addr": "127.0.0.1

cssivision 9 Sep 5, 2022
🤖 brwrs is a new protocol running over TCP/IP that is intended to be a suitable candidate for terminal-only servers

brwrs is a new protocol running over TCP/IP that is intended to be a suitable candidate for terminal-only servers (plain text data). That is, although it can be accessed from a browser, brwrs will not correctly interpret the browser's GET request.

daCoUSB 3 Jul 30, 2021
TCP is so widely used, however QUIC may have a better performance.

TCP is so widely used, however QUIC may have a better performance. For softwares which use protocols built on TCP, this program helps them take FULL advantage of QUIC.

zephyr 15 Jun 10, 2022
Library + CLI-Tool to measure the TTFB (time to first byte) of HTTP requests. Additionally, this crate measures the times of DNS lookup, TCP connect and TLS handshake.

TTFB: CLI + Lib to Measure the TTFB of HTTP/1.1 Requests Similar to the network tab in Google Chrome or Mozilla Firefox, this crate helps you find the

Philipp Schuster 24 Dec 1, 2022
Simple utility to ping a TCP port.

TcpPing Simple utility to ping a TCP port. Example > tcpping 1.1.1.1 53 -b en0 -i 1 -t 4 Connected to 1.1.1.1:53 in 21 ms Connected to 1.1.1.1:53 in 3

null 11 Nov 24, 2022
Transforms UDP stream into (fake) TCP streams that can go through Layer 3 & Layer 4 (NAPT) firewalls/NATs.

Phantun A lightweight and fast UDP to TCP obfuscator. Table of Contents Phantun Latest release Overview Usage 1. Enable Kernel IP forwarding 2. Add re

Datong Sun 782 Dec 30, 2022
RedLizard - A Rust TCP Reverse Shell with SSL

RedLizard - A Rust TCP Reverse Shell with SSL RedLizard Rust TCP Reverse Shell Server/Client This is a reverse shell in Rust called RedLizard, basical

Thanasis Tserpelis 105 Dec 24, 2022
Passive TCP/IP fingerprinting tool

This tool analyzes first stage of TCP handshake (SYN) and recognize operating system of client Build To build sp0ky, you need to install Rust git clon

Ivan Tyunin 19 Sep 12, 2022
A simple tcp server that written in rustlang

rust_tcp A simple tcp server that written in rustlang How to build In the root dir cargo run Then you can do a test by using telnet as a client telne

null 1 Oct 25, 2021
Tunnel TCP traffic through SOCKS5 or HTTP using a TUN interface.

tun2proxy Tunnel TCP traffic through SOCKS5 or HTTP on Linux. Authentication not yet supported. Error handling incomplete and too restrictive. Build C

B. Blechschmidt 34 Nov 29, 2022
A remote shell, TCP tunnel and HTTP proxy for Replit.

Autobahn A remote shell, TCP tunnel and HTTP proxy for Replit. Hybrid SSH/HTTP server for Replit. Based on leon332157/replish. Autobahn runs a WebSock

Patrick Winters 12 Sep 24, 2022
A tcp proxy server/client which exchange the data in temp files

ftcp A tcp proxy server/client which exchange the data in temp files 通过在临时文件中交换数据来进行TCP代理的一个服务端/客户端 学校内网中有针对教学楼的防火墙导致教室电脑难以上网( 但学校内建有公共ftp服务器,因此就有了这个借

Daile Liu 2 Feb 17, 2022
A rustic tcp + serialization abstraction.

Wire An abstraction over TCP and Serialization "put a struct in one side and it comes out the other end" Wire is a library that makes writing applicat

Ty Overby 33 May 12, 2021
Send files over TCP. Quick and simple. Made in Rust.

SFT Multithreaded utility to send files over TCP. The sender writes a header containing the filename, and then the contents of the file, buffered, to

Orel 0 Dec 24, 2021
A modern, simple TCP tunnel in Rust that exposes local ports to a remote server, bypassing standard NAT connection firewalls

bore A modern, simple TCP tunnel in Rust that exposes local ports to a remote server, bypassing standard NAT connection firewalls. That's all it does:

Eric Zhang 6.2k Dec 31, 2022
A tcp port forwarding system like ngrok.

Pruxy A tcp port forwarding system like ngrok. Todo http request handler agent <-> server connection agent How to use Generate cert files mkdir ssl_ce

null 1 Jan 24, 2022
An app which reads data from a serial port and serves it on a TCP port.

serial-to-tcp An app which reads data from a serial port and serves it on a TCP port. How to use Clone this repo and build the app as outlined below (

Mr. E 3 Oct 21, 2022
A multi-connection TCP reverse proxy server and client.

tprox A multi-connection TCP reverse proxy. The tprox server is able to proxy multiple incoming connections to the tprox client over a single TCP conn

Mohammed Ajmal Siddiqui 4 Sep 21, 2022