Giter Site home page Giter Site logo

smoltcp's Introduction

smoltcp

docs.rs crates.io crates.io crates.io codecov

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.65 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

There are 3 supported mediums.

  • 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
    • Unicast, broadcast and multicast packets are supported.
  • IEEE 802.15.4
    • Only support for data frames.

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 and reassembly is 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.

6LoWPAN

  • Implementation of RFC6282.
  • Fragmentation is supported, as defined in RFC4944.
  • UDP header compression/decompression is supported.
  • Extension header compression/decompression is supported.
  • Uncompressed IPv6 Extension Headers are not supported.

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, and ICMP sockets are available.

  • 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.
  • Nagle's algorithm is implemented.
  • Selective acknowledgements are not implemented.
  • Silly window syndrome avoidance 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.10.0"

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.10.0", default-features = false, features = ["log"] }

Feature flags

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 defmt

The defmt feature enables logging of events with the defmt crate.

This feature is disabled by default, and cannot be used at the same time as log.

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-tuntap_interface

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

These features are enabled by default.

Features socket-raw, socket-udp, socket-tcp, socket-icmp, socket-dhcpv4, socket-dns

Enable the corresponding socket type.

These features are enabled by default.

Features proto-ipv4, proto-ipv6 and proto-sixlowpan

Enable IPv4, IPv6 and 6LoWPAN respectively.

Configuration

smoltcp has some configuration settings that are set at compile time, affecting sizes and counts of buffers.

They can be set in two ways:

  • Via Cargo features: enable a feature like <name>-<value>. name must be in lowercase and use dashes instead of underscores. For example. iface-max-addr-count-3. Only a selection of values is available, check Cargo.toml for the list.
  • Via environment variables at build time: set the variable named SMOLTCP_<value>. For example SMOLTCP_IFACE_MAX_ADDR_COUNT=3 cargo build. You can also set them in the [env] section of .cargo/config.toml. Any value can be set, unlike with Cargo features.

Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting with different values, compilation fails.

IFACE_MAX_ADDR_COUNT

Max amount of IP addresses that can be assigned to one interface (counting both IPv4 and IPv6 addresses). Default: 2.

IFACE_MAX_MULTICAST_GROUP_COUNT

Max amount of multicast groups that can be joined by one interface. Default: 4.

IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT

Max amount of 6LoWPAN address contexts that can be assigned to one interface. Default: 4.

IFACE_NEIGHBOR_CACHE_COUNT

Amount of "IP address -> hardware address" entries the neighbor cache (also known as the "ARP cache" or the "ARP table") holds. Default: 4.

IFACE_MAX_ROUTE_COUNT

Max amount of routes that can be added to one interface. Includes the default route. Includes both IPv4 and IPv6. Default: 2.

FRAGMENTATION_BUFFER_SIZE

Size of the buffer used for fragmenting outgoing packets larger than the MTU. Packets larger than this setting will be dropped instead of fragmented. Default: 1500.

ASSEMBLER_MAX_SEGMENT_COUNT

Maximum number of non-contiguous segments the assembler can hold. Used for both packet reassembly and TCP stream reassembly. Default: 4.

REASSEMBLY_BUFFER_SIZE

Size of the buffer used for reassembling (de-fragmenting) incoming packets. If the reassembled packet is larger than this setting, it will be dropped instead of reassembled. Default: 1500.

REASSEMBLY_BUFFER_COUNT

Number of reassembly buffers, i.e how many different incoming packets can be reassembled at the same time. Default: 1.

DNS_MAX_RESULT_COUNT

Maximum amount of address results for a given DNS query that will be kept. For example, if this is set to 2 and the queried name has 4 A records, only the first 2 will be returned. Default: 1.

DNS_MAX_SERVER_COUNT

Maximum amount of DNS servers that can be configured in one DNS socket. Default: 1.

DNS_MAX_NAME_SIZE

Maximum length of DNS names that can be queried. Default: 255.

IPV6_HBH_MAX_OPTIONS

The maximum amount of parsed options the IPv6 Hop-by-Hop header can hold. Default: 1.

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, transmitting 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

# Some distros have a default policy of DROP. This allows the traffic.
sudo iptables -A FORWARD -i tap0 -s 192.168.69.0/24 -j ACCEPT
sudo iptables -A FORWARD -o tap0 -d 192.168.69.0/24 -j ACCEPT

Bridged connection

Instead of the routed connection above, you may also set up a bridged (switched) connection. This will make smoltcp speak directly to your LAN, with real ARP, etc. It is needed to run the DHCP example.

NOTE: In this case, the examples' IP configuration must match your LAN's!

NOTE: this ONLY works with actual wired Ethernet connections. It will NOT work on a WiFi connection.

# Replace with your wired Ethernet interface name
ETH=enp0s20f0u1u1

sudo modprobe bridge
sudo modprobe br_netfilter

sudo sysctl -w net.bridge.bridge-nf-call-arptables=0
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0
sudo sysctl -w net.bridge.bridge-nf-call-iptables=0

sudo ip tuntap add name tap0 mode tap user $USER
sudo brctl addbr br0
sudo brctl addif br0 tap0
sudo brctl addif br0 $ETH
sudo ip link set tap0 up
sudo ip link set $ETH up
sudo ip link set br0 up

# This connects your host system to the internet, so you can use it
# at the same time you run the examples.
sudo dhcpcd br0

To tear down:

sudo killall dhcpcd
sudo ip link set br0 down
sudo brctl delbr br0

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 -- --tap tap0 ADDRESS URL

For example:

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

or:

cargo run --example httpclient -- --tap 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 -- --tap 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 -- --tap 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 with reversed chunks of the input indefinitely;
  • 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 -- --tap 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 -- --tap 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 -- --tap tap0 reader
throughput: 2.556 Gbps
$ cargo run -q --release --example benchmark -- --tap 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.

smoltcp's People

Contributors

astro avatar barskern avatar batonius avatar benbrittain avatar bors[bot] avatar canndrew avatar crawford avatar datdenkikniet avatar davidedellagiustina avatar dirbaio avatar dlrobertson avatar heroickatora avatar hjr3 avatar jakkusakura avatar jarredallen avatar jhwgh1968 avatar korken89 avatar lachlansneff-parallel avatar mabezdev avatar phil-opp avatar podhrmic avatar pothos avatar progval avatar ryan-summers avatar ssrlive avatar thalesfragoso avatar thegreathir avatar thvdveld avatar whitequark avatar wmcleish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smoltcp's Issues

TCP reset generation is not quite correct

From RFC 793:

If the incoming segment has an ACK field, the reset takes its
sequence number from the ACK field of the segment, otherwise the
reset has sequence number zero and the ACK field is set to the sum
of the sequence number and segment length of the incoming segment.

We take small liberties with ACK handling but we shouldn't.

Implementing IGMP protocol and multicast support

Hello, for our application I need smoltcp to handle multicast packets (the application communicates over multicast). I am happy to do that and implement this functionality to smoltcp, but I would like to get some pointers and guidance first.

  1. IGMP protocol will have to be implemented - I would add another branch on the match expression here https://github.com/m-labs/smoltcp/blob/master/src/iface/ethernet.rs#L249
  2. Implement EthernetProtocol::IGMP with underlying logic
  3. when IGMP (and multicast) is enabled we check dst_addr() against the list of multicast MAC addresses we are subscribed to (here: https://github.com/m-labs/smoltcp/blob/master/src/iface/ethernet.rs#L244 )
  4. appropriately handle timeouts (see https://en.wikipedia.org/wiki/Internet_Group_Management_Protocol ) - not sure if we need a separate timer or we should use the time supplied to iface.poll()

Does it sound like a good approach to you?

Infinite challenge ACK retransmit loop

Take 15ce667, apply the patch:

diff --git a/examples/loopback.rs b/examples/loopback.rs
index fa3806c..3305729 100644
--- a/examples/loopback.rs
+++ b/examples/loopback.rs
@@ -72 +72 @@ fn main() {
-    while !done && timestamp_ms < 500 {
+    while timestamp_ms < 5000 {
@@ -100,0 +101,3 @@ fn main() {
+            }
+
+            if socket.is_active() && done {

smoltcp as Redox network stack

Context: I've been working on reimplementing Redox's network stack with smoltcp, and it looks good so far. In the process, I've come up with a number of issues/ideas/proposals I want to discuss.

New features:

  1. smoltcp doesn't support loopback interface, an attempt to connect to 127.0.0.1 results in a flood of ARP requests asking for the MAC address of 127.0.0.1. The loopback example solves this by using a loopback device, which is not an option for smolnetd. I think smoltcp should support several devices, each with several subnets assigned to them. This way we would be able to add loopback interface by adding a loopback device with 127.0.0.1/8 assigned to it.
  2. smoltcp looks like a good place to implement DHCP since it already handles ARP, but I'm not sure how it should look like. Currently, Redox uses dhcpd daemon for this.
  3. ICMP sockets aka IPPROTO_ICMP would be quite handy for implementing a version of ping able to work without suid, but I'm not sure if they're useful for anything besides ping. Right now Redox has icmpd daemon.
  4. smoltcp doesn't check uniqueness of local ports when binding sockets and doesn't generate them when bind is called with port == 0, so smolnetd implements its own ports set. I think some form of it should be integrated into smoltcp itself, probably as part of the packet dispatch effort.

Improvements:

  1. It would be nice to be able to manipulate TTL of a socket since it's supported by TcpStream and UdpSocket.
  2. The current TCP accept/listen mechanism, while working, seems too limited. Namely, since smolnetd has to reopen a socket every time it accepts a connection, it's impossible to connect to the host during this period. I have no concrete proposal for this yet.
  3. I think ARP requests rate should be limited, it's too easy to get smoltcp flooding the network with ARP packets.

Optimizations:

  1. I still want to see #19 implemented.
  2. Currently smolnetd iterates over all the sockets after each poll to see if it should send an event/resume a blocked call. It would be handy if EthernetInterface::poll produced some kind of list of the sockets which state changed during the call.
  3. Right now smolnetd has to call SocketSet::prune after each SocketSet::release to free sockets, which means iteration over all the sockets in the set. I understand we can't free sockets in release because TCP sockets require shutdown, but maybe we should initiate the shutdown in release and then have SocketSet threat closed TCP sockets with refs == 0 as empty?

@whitequark Of all the issues, I think loopback interface is the most pressing right now, so I'd like to know what you think would be the best way to approach it.

cc @little-dude , @jackpot51

IPv6

Is IPv6 something this library will support, may support, or will never support?

ACK sequence number check too strict

In process():

// Every acknowledgement must be for transmitted but unacknowledged data

This is incorrect. In RFC 793 the ACK sequence number being out of that range would have little effect; the payload would still be processed. It's just that the window or SND.UNA would not be updated based on the ACK sequence number.

This was then made more strict in RFC 5961 (which is the version worth implementing).

The ACK value is considered acceptable only if it is in the range of
((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT). All incoming segments
whose ACK value doesn't satisfy the above condition MUST be discarded and
an ACK sent back. 

So the changes to the code would be: a) expanding the range of acceptable ACKs to cover up to one window's worth of already ACKed data; b) sending a challenge ACK instead of just dropping the packet.

This is not just a theoretical concern. The too-strict check will cause trouble for bidirectional traffic in networks with reordering.

DHCP support

Is DHCP support planned? And is there a way to implement DHCP on top of smoltcp, i.e. without writing own UDP packet construction code?

Build broken for `default-features=false`

Hi! First of all, thanks a lot for creating this library. It seems like a great fit for embedded and OS development.

Unfortunately, it no longer compiles with default-features=false, since
ManagedSlice::Owned is only available with use_std or use_collections.

error: no associated item named `Owned` found for type `managed::ManagedSlice<'_, _>` in the current scope
  --> /…/smoltcp-ebf9e93b1271bd34/874d192/src/socket/set.rs:64:13
   |
64 |             ManagedSlice::Owned(ref mut sockets) => {
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

(I think it would be a good idea to add an additional travis build config for default-features=false to prevent similar issues in the future.)

Implement ARP-related timers

RFC1122 mandates in very strong terms that the following should be implemented:

  • Cache entry invalidation timer, 60 seconds
  • Flood prevention timer, 1 second

socket::set::Item is not exported publicly

Right now socket::set::Item (used in SocketSet::new) is a public type inside the set module, but it is not publicly used in socket, so it's impossible to refer to this type.

I think this is basically the issue discussed here.

It means you can do this:

let mut sockets_storage = [None];
let mut sockets = SocketSet::new(&mut sockets_storage[..]);
sockets_storage[0] = Some(1);

and the compiler infers the type of sockets_storage OK, and gives this error:

expected type `smoltcp::socket::set::Item<'_, '_>`
found type `{integer}`

but if you try and use that type:

let mut sockets_storage: [Option<smoltcp::socket::set::Item>; 1] = [None; 1];

you get

error: module `set` is private

This makes it really hard to statically allocate all the storage for smoltcp, as far as I can tell. Is there a nice way to do that? I'm running into a bunch of various pain points trying to allocate the storage statically, but I can't have it live on the stack (since it needs to live beyond the function call setting it up) and I don't have a heap available. There's a lot of static mut X: Option<SliceArpCache> = None; and then setting it to Some(SliceArpCache::new(...)) during setup.

TCP hangs when sending

Hello,
actually I wanted to debug it, but need some help because it is disappears e.g. if writing to pcap or extensive logging is used.

The smoltcp server thread on tap0 will send in 64 byte steps, a Linux socket client thread will read only to ~292800 bytes instead of reading all 10000000 bytes. Smoltcp is handing out keep-alives from this point on. The minimal example is here:

https://github.com/pothos/taptest

cargo run --release should reproduce the behavior.

RUST_LOG=trace cargo run --release should let it sometimes complete (same as adding print statements in the sending server).
Uncommenting the pcap writer will let it complete reliably.

I have experienced it with other values as well, but hope that this combination also reproduces it on other machines.

Best regards,
Kai

Challenge ACKs are not always generated

From RFC 793:

If the connection is in a synchronized state (ESTABLISHED,
FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT),
any unacceptable segment (out of window sequence number or
unacceptible acknowledgment number) must elicit only an empty
acknowledgment segment containing the current send-sequence number
and an acknowledgment indicating the next sequence number expected
to be received, and the connection remains in the same state.

The server example panics after sending 9Mb from 6972

Scenario:

  • RUST_BACKTRACE=1 cargo run --example server -- tap0
  • nc 192.168.69.1 6972 > nc_out

Result:

[     3.605s] <- EthernetII src=2a-d3-30-70-6c-b6 dst=02-00-00-00-00-01 type=IPv4
                \ IPv4 src=192.168.69.100 dst=192.168.69.1 proto=TCP
                 \ TCP src=56036 dst=6972 seq=2966042689 ack=1329360159 win=65535 len=0
[     3.605s] (socket::tcp): [4]192.168.69.1:6972:192.168.69.100:56036: tx buffer: dequeueing 65535 octets (now 0)
thread 'main' panicked at 'assertion failed: acked.len() == ack_len', src/socket/tcp.rs:999:12
stack backtrace:
[...]
   5: std::panicking::begin_panic_new
             at /checkout/src/libstd/panicking.rs:553
   6: smoltcp::socket::tcp::TcpSocket::process
             at src/socket/tcp.rs:999
   7: <smoltcp::iface::ethernet::Interface<'a, 'b, 'c, DeviceT>>::process_tcp
             at ./src/iface/ethernet.rs:417
   8: <smoltcp::iface::ethernet::Interface<'a, 'b, 'c, DeviceT>>::process_ipv4
             at ./src/iface/ethernet.rs:315
   9: <smoltcp::iface::ethernet::Interface<'a, 'b, 'c, DeviceT>>::process_ethernet
             at ./src/iface/ethernet.rs:220
  10: <smoltcp::iface::ethernet::Interface<'a, 'b, 'c, DeviceT>>::socket_ingress
             at ./src/iface/ethernet.rs:144
  11: <smoltcp::iface::ethernet::Interface<'a, 'b, 'c, DeviceT>>::poll
             at ./src/iface/ethernet.rs:126
  12: server::main
             at examples/server.rs:184
[...]
ls -lh nc_out
-rw-r--r-- 1  9.7M Sep 12 21:52 nc_out

Wrapping `EthernetInterface`

Hi,

I'm trying to wrap smoltcp::iface::EthernetInterface like so:

extern crate smoltcp;
use std::fs::File;
use std::io::{self, Read, Write};

struct Device {
    network: File,
}

struct TxBuffer {
    buffer: Vec<u8>,
    network: File,
}

impl AsRef<[u8]> for TxBuffer {
    fn as_ref(&self) -> &[u8] { self.buffer.as_ref() }
}

impl AsMut<[u8]> for TxBuffer {
    fn as_mut(&mut self) -> &mut [u8] { self.buffer.as_mut() }
}

impl Drop for TxBuffer {
    fn drop(&mut self) { self.network.write(&mut self.buffer[..]).unwrap(); }
}

impl smoltcp::phy::Device for Device {
    type RxBuffer = Vec<u8>;
    type TxBuffer = TxBuffer;

    fn mtu(&self) -> usize { 1536 }

    fn receive(&mut self) -> Result<Self::RxBuffer, smoltcp::Error> {
        let mut buffer = vec![0; self.mtu()];
        let size = self.network.read(&mut buffer[..])?;
        buffer.resize(size, 0);
        Ok(buffer)
    }

    fn transmit(&mut self, length: usize) -> Result<Self::TxBuffer, smoltcp::Error> {
        Ok(TxBuffer {
            network:  self.network.try_clone()?,
            buffer: vec![0; length]
        })
    }
}

pub struct EthernetDevice<'a, 'b, 'c>(smoltcp::iface::EthernetInterface<'a, 'b, 'c, Device>);

impl<'a, 'b, 'c> EthernetDevice <'a, 'b, 'c> {
    pub fn new(network: File) -> Self {
        let device = Box::new(Device { network: network });
        let arp_cache = Box::new(smoltcp::iface::SliceArpCache::new(vec![Default::default(); 8])) as Box<smoltcp::iface::ArpCache>;
        let hardware_addr = smoltcp::wire::EthernetAddress([0x0, 0x0, 0x0, 0x0, 0x0, 0x0]);
        let ip_addresses = [smoltcp::wire::IpAddress::v4(192, 168, 0, 2)];
        EthernetDevice(smoltcp::iface::EthernetInterface::new(device, arp_cache, hardware_addr, ip_addresses))
    }
    // more stuff
}

This does not work:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'b` due to conflicting requirements
  --> new.rs:56:9
   |
56 |         EthernetDevice(smoltcp::iface::EthernetInterface::new(device, arp_cache, hardware_addr, ip_addresses))
   |         ^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'b as defined on the body at 51:38...
  --> new.rs:51:39
   |
51 |       pub fn new(network: File) -> Self {
   |  _______________________________________^ starting here...
52 | |         let device = Box::new(Device { network: network });
53 | |         let arp_cache = Box::new(smoltcp::iface::SliceArpCache::new(vec![Default::default(); 8])) as Box<smoltcp::iface::ArpCache>;
54 | |         let hardware_addr = smoltcp::wire::EthernetAddress([0x0, 0x0, 0x0, 0x0, 0x0, 0x0]);
55 | |         let ip_addresses = [smoltcp::wire::IpAddress::v4(192, 168, 0, 2)];
56 | |         EthernetDevice(smoltcp::iface::EthernetInterface::new(device, arp_cache, hardware_addr, ip_addresses))
57 | |     }
   | |_____^ ...ending here
note: ...so that expression is assignable (expected EthernetDevice<'a, 'b, 'c>, found EthernetDevice<'_, '_, '_>)
  --> new.rs:56:9
   |
56 |         EthernetDevice(smoltcp::iface::EthernetInterface::new(device, arp_cache, hardware_addr, ip_addresses))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `smoltcp::iface::Cache` will meet its required lifetime bounds
  --> new.rs:56:24
   |
56 |         EthernetDevice(smoltcp::iface::EthernetInterface::new(device, arp_cache, hardware_addr, ip_addresses))
   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I know this is not a smoltcp issue, but I have really don't know how to make the compiler happy here, so I was hoping you would be able to suggest something? Or am I trying to do something fundamentally wrong?

UDP sending reads uninitialised memory to check buffer length with unpredictable results

I've been chasing down intermittent panics "undersized payload: Truncated" while sending UDP packets and I think I've gotten to the bottom of it.

The gist is that in socket/udp.rs:264 when we try to make a new UdpPacket from payload, the payload is a TX buffer obtained from Device, with the Ethernet and IPv4 headers set up, but without any UDP header set yet. UdpPacket::new then makes a UdpPacket from this buffer and checks the buffer length is at least packet.len(), a method which reads the UDP header length field. However, this hasn't been set yet!

So, at startup this is often 0 and works fine. During most of my operations, this is set to the previous packet length (and they're all the same) and works fine. Once I have to respond to an ARP request, this is most recently set to 0xC0A8, the first two octets of my desktop's IP address. Since the buffer being transmitted is not that long, we get a panic.

Slightly longer explanation of where this comes from:

  1. User calls Interface::poll()
  2. poll calls Interface::emit() to send new packets
  3. Interface::emit() calls Socket::dispatch for each socket
  4. In socket/udp.rs:238 Socket::dispatch() we dequeue a PBuf and put it into a UdpRepr called repr
  5. Socket::dispatch() calls the emit closure from Interface::emit() with payload set to UdpRepr as an IpPayload
  6. Back in interface/ethernet.rs:383 Interface::emit(), we obtain a TX buffer from Device and turn it into a Frame
  7. The frame has its ethernet and IPv4 headers set and an Ipv4Packet made
  8. payload: IpPayload (our UdpRepr from before with the PBuf to send) has its emit() called with the Ipv4Packet payload buffer (inside the TX buffer from Device), called payload
  9. Now socket/udp.rs:264 impl IpPayload for UdpRepr emit() makes a new UdpPacket with buffer set to payload, which is the Ipv4Packet from 8 and has not had any UDP fields initialised
  10. Now wire/udp.rs:37 new() makes a Packet (UdpPacket) from buffer
  11. And finally calls packet.len(), which reads the LENGTH field from buffer

I think UdpPacket::new() shouldn't check packet.len() in this case, but I'm not sure how else it can find out what length UDP packet is meant to go into it. The IpPayload emit() in socket/udp.rs, on success, then calls UdpPacket::emit which will do a copy_from_slice to copy the PBuf into the TX buffer.

I also haven't checked to see if the same problem exists for TCP but I'm yet to hit any panics from it...

Hardware Checksum Calculation

I think the possibility to move checksum calculation to the hardware when possible,
would be neat.

E.g. With LWIP you can do

#define CHECKSUM_BY_HARDWARE
#define CHECKSUM_GEN_IP 0
#define CHECKSUM_GEN_UDP 0
#define CHECKSUM_GEN_TCP 0
#define CHECKSUM_GEN_ICMP 0
#define CHECKSUM_CHECK_IP 0
#define CHECKSUM_CHECK_UDP 0
#define CHECKSUM_CHECK_TCP 0
#define CHECKSUM_CHECK_ICMP 0

and use Checksum insertion control of STM32 to let the hardware handle those checksums.
You'd simply not calculate the checksum in the software &
the SoC will insert it before transmitting/receiving the frame.

[RFC] Packet dispatch

Problem

Currently, sloltcp uses iteration to dispatch ingress packets to sockets and to poll sockets for egress packets. While acceptable on the platforms without libstd and memory allocation, this approach is very inefficient on systems where growable containers are available.

Discussion

  1. The problem is two-fold: ingress packets should be dispatched by lookup tables based on their destination, while only "dirty" sockets should be polled for egress packets.
  2. After #18 is merged, there will be two kinds of sockets: l3 (raw) sockets should be dispatched to based on packet's IP version and IP protocol number, and l4 sockets (tcp and udp) should be dispatched to based on the dst port.
  3. Sockets can be bound to a specific IP address. As a result, a solution should support several sockets of the same type listening on the same port but bound to different IP addresses.
  4. A solution should keep the existing behavior when compiled without the std feature.

Proposal

  1. Dispatch packets to sockets based on the port/protocol number only, leaving the question of sockets being bound to IPs to the Socket::proccess method.
  2. Expand SocketSet by (conditionally) adding three std::HashMap's to it, for raw, tcp and udp sockets, and a Vec to keep dirty sockets' handlers.
  3. Hash map for raw sockets should have a type of std::HashMap<(RawSocketType, IpProtocol), Vec<Handle>>, hash maps for udp and tcp should have a type of std::HashMap<u16, Vec<Handle>> . Vec is required to support the possibility of several sockets listening to the same port/protocol on different addresses.
  4. Extend SocketSet::add to use socket's endpoint and ip_protocol methods to construct an index to insert socket's handler into the corresponding lookup table.
  5. Extend SocketSet::remove to remove socket from a lookup table based on socket's endpoint.
  6. Extend Set::IterMut to support construction from a mutable reference to ManagedSlice and an iterator of Handles, producing an iterator of Socket.
  7. Replace SocketSet::iter_mut with iter_raw(&mut self, ip_repr: &IpRepr) -> IterMut and iter_l4(&mut self, ip_repr: &IpRepr) -> IterMut, which should look up sockets based on ip_repr and return an iterator over the sockets found. With the std feature disabled, the functions should return iterators over the entire SocketSet.
  8. Add iter_dirty to SocketSet to produce an iterator of Socket by draining dirty_sockets vector, or an iterator over the entire SocketSet if std is disabled.
  9. Add mark_drity to SocketSet to mark a socket as dirty.
  10. Replace iter_mut in EthernetInterface::poll with iter_raw and iter_l4 and in EthernetInterface::emit with iter_dirty.

Questions

  1. The only breakage of the current public API would be the requirement to explicitly mark written-to sockets as dirty. This can be avoided by having SocketSet::get_mut mark each requested socket as dirty (which kinda defeats the purpose), by having a specialized method SocketSet::get_mut_for_write to do so, or by devising some smart handler type which would mark a socket as dirty on send.
  2. I'm not sure if the reference counting in Set::Item is relevant here.
  3. I think EthernetInterface::poll should be refactored into several methods to increase readability.

Bug in tcp option parsing?

I think there is a bug in this line in wire/tcp.rs:

length = *buffer.get(1).ok_or(Error::Truncated)? as usize;
let data = buffer.get(2..length).ok_or(Error::Truncated)?;

I think it should be buffer.get(2..(2+length)) instead, because otherwise we try to take the range 2..1 for an option with length 1.

Would a dedicated crate for packet decoding make sense?

Hi,

The wire module provides abstractions for packet decoding, which could also be useful outside of smoltcp. For example, I am considering implementing the OpenFlow protocol and I'd like to re-use what you already did for packet parsing and add support OpenFlow headers.

In case I'm unclear, I'd like to see something similar to Golang's gopacket/layers library.

ARP storm

smoltcp seems to be generating a bit of an ARP storm, at least when interacting with some security scan appliance.

Might be related to #25

@whitequark has the dump.

The `ping` example is currently broken.

Right now the ping example panics on iface.poll(&mut sockets, timestamp).expect("poll error") because of Error::Unaddressable "thrown" in EthernetInterface::lookup_hardware_addr, which "unrolls" all the way up to main.

The problem was introduced in 1ece71a when socket_egress stopped ignoring the error.

server example crashes when binding UDP socket

Git revision a1910ba. Commenting out the UDP part of the server.rs example makes the rest of the code work.

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Illegal', /checkout/src/libcore/result.rs:860:4
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
             at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::_print
             at /checkout/src/libstd/sys_common/backtrace.rs:71
   2: std::panicking::default_hook::{{closure}}
             at /checkout/src/libstd/sys_common/backtrace.rs:60
             at /checkout/src/libstd/panicking.rs:380
   3: std::panicking::default_hook
             at /checkout/src/libstd/panicking.rs:396
   4: std::panicking::rust_panic_with_hook
             at /checkout/src/libstd/panicking.rs:610
   5: std::panicking::begin_panic
             at /checkout/src/libstd/panicking.rs:571
   6: std::panicking::begin_panic_fmt
             at /checkout/src/libstd/panicking.rs:521
   7: rust_begin_unwind
             at /checkout/src/libstd/panicking.rs:497
   8: core::panicking::panic_fmt
             at /checkout/src/libcore/panicking.rs:71
   9: core::result::unwrap_failed
             at /checkout/src/libcore/macros.rs:31
  10: <core::result::Result<T, E>>::unwrap
             at /checkout/src/libcore/result.rs:738
  11: server::main
             at examples/server.rs:67
  12: __rust_maybe_catch_panic
             at /checkout/src/libpanic_unwind/lib.rs:98
  13: std::rt::lang_start
             at /checkout/src/libstd/panicking.rs:458
             at /checkout/src/libstd/panic.rs:361
             at /checkout/src/libstd/rt.rs:61
  14: main
  15: __libc_start_main
  16: _start

Implement ICMP sockets

Implement ICMP sockets that are somewhat equivalent to linux IPPROTO_ICMP sockets. Much of the implementation may be similar to the other socket types, but there will likely be a few crucial differences.

  • Icmpv4Socket::accepts is determined only by the source IP and destination IP. This means that if two separate icmp sockets sent from the same source to the same destination, things would get mixed up. Without access to the IP id, I don't see a way around this. If this direction is taken, a should_fail test with a note and an issue to make sure we don't lose track of this would be nice.
  • The send and send_slice methods should be somewhat similar to the current udp implementation where the destination address is an arg.
  • It might be more readable to use a recv_from method that takes the address to receive packets from as an arg instead of the typical recv function. If we really want to stick to recv instead of recv_from we could keep the destination that will be used by Icmpv4Socket::accepts from the previous send.

Related to: #50

Errors parsing ICMP Destination Unreachable

When smoltcp receives an ICMPv4 Destination Unreachable Port Unreachable, it tries to parse the payload as an IPv4 packet, but this often fails with Truncated because the original packet was much longer than whatever initial portion of it was included with the ICMP response. This error (from https://github.com/m-labs/smoltcp/blob/master/src/wire/icmpv4.rs#L384) then gets bubbled up to the top poll() call where it looks like a network error.

As far as I can tell it shouldn't actually be an error though, since the RFC says you just include the first 64 bytes (in practice apparently Linux does a few hundred?) of the original packet, but you don't expect to see all of it. smoltcp parsing an IPv4 packet errors out if the packet buffer isn't long enough to contain the length the IPv4 header promises, so the whole thing stops. Not sure what the neatest solution is, perhaps having a way to parse the IPv4 header without caring that the payload likely won't be long enough?

Revise errors returned from `TcpSocket::process()`

To summarize:

  • We should never return Err(Error::Malformed) for errors that depend on socket state (since it might simply be that we crashed and lost the state); only return that for packets that are invalid on their own, i.e. would not be accepted no matter what state the socket has.
  • Returning Err(Error::Rejected) from TcpSocket::process() is only really appropriate while we're determining which endpoint they're addressed to. Once we know it's addressed to us (having two open sockets with the same local and remote endpoints, and the remote endpoint is not unspecified) we should not go on iterating through the sockets and send RST immediately.

In detail:

  • Protocol check should be a debug_assert!() now.
  • If state is CLOSED or the destination does not match, we return Err(Error::Rejected); it might be a half-open connection but it also might simply be addressed to another socket.
  • If state is LISTEN and we get an ACK then we're probably a socket in backlog and it's actually destined to someone else, so return Err(Error::Rejected).
  • If state is SYN-SENT and we get an RST with an unacceptable ACK we return Err(Error::Dropped); it's probably a half-open connection or other anomaly, and if the remote end doesn't want to accept our packets we'll get a proper RST when our SYN is retransmitted.
  • RSTs in any other state need only have an acceptable SEQ.
  • Any packet except SYNs is malformed if it's not also an ACK, so return Err(Error::Malformed).
  • Unacceptable SEQs (so long as we're synchronized) should always trigger a challenge ACK.
  • Unacceptable ACKs should always trigger a challenge ACK.
  • Consider returning an Option<TcpRepr> from TcpSocket::process() so that we can bring reset generation together with the rest of the TCP code (and test it there too). This will still need to be exported so that EthernetInterface can respond with a reset e.g. even if we have no TCP sockets at all. It can also be used for generating challenge ACKs immediately and not on the next iteration of emission.

Broadcast IP resolving shouldn't happen via ARP cache

When sending an UDP broadcast packet via an UdpSocket,
I noticed that smoltcp attempted to check the ArpCache for 255.255.255.255
and therefore send an ARP request for that IP, which I believe to be unusual.

I think insteadof calling ArpCache::lookup a general lookup-function
should be called, which handles this (255.255.255.255 to MAC [0xff; 6] ) aswell
as other possible exceptions in the future.

Does that sound like the right approach?
I'd be fine with implementing it, but wanted some feedback first.

How to actively open a TCP connection?

After looking at the doc and at the code, I could not find out how to actively open a TCP connection. Is this implemented? If so, how can it be done?

If mac dest is not broadcast/multicast or configured on the device, the frame should be ignored

Currently, smoltcp does not check the destination MAC address.

Here is an example (it reuses examples/utils.rs), where we create a device with hardware address 00:00:00:00:00:01 and ip 10.0.0.1:

#[macro_use]
extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate smoltcp;

mod utils;

use std::str;
use smoltcp::phy::RawSocket;
use std::time::Instant;
use std::env;
use smoltcp::Error;
use smoltcp::wire::{EthernetAddress, IpAddress};
use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface};
use smoltcp::socket::{SocketSet};

fn main() {
    utils::setup_logging();

    let opts = getopts::Options::new();
    let matches = opts.parse(env::args().skip(1)).unwrap();
    let device = RawSocket::new(&matches.free[0]).unwrap();
    let startup_time = Instant::now();


    let arp_cache = SliceArpCache::new(vec![Default::default(); 8]);
    let mut iface = EthernetInterface::new(
        Box::new(device), Box::new(arp_cache) as Box<ArpCache>,
        EthernetAddress([0x00, 0x00, 0x00, 0x00, 0x00, 0x01]),
        [IpAddress::v4(10, 0, 0, 1)]);

    let mut sockets = SocketSet::new(vec![]);

    loop {
        let timestamp = Instant::now().duration_since(startup_time);
        let timestamp_ms = (timestamp.as_secs() * 1000) + (timestamp.subsec_nanos() / 1000000) as u64;
        match iface.poll(&mut sockets, timestamp_ms) {
            Ok(()) | Err(Error::Exhausted) => (),
            Err(e) => debug!("poll error: {}", e)
        }
    }
}

Now we can create a veth pair:

ip link add veth0 type veth peer name veth1
ip link set dev veth0 up
ip link set dev veth1 up

We run the client:

target/debug/examples/arp veth0

Let's send some ping with a wrong destination mac:

# we add a dummy ip in the 10.0.0.0/24 subnet on veth1 to force linux to send ping through it
ip addr add 10.0.0.10/24 dev veth1
# we add a static arp entry to force echo request to be sent with a wrong mac address
ip neigh change 10.0.0.1 dev veth1 lladdr aa:bb:cc:dd:ee:ff nud permanent

Now we can ping:

~ ❯❯❯ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.282 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.349 ms
^C
--- 10.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1023ms
rtt min/avg/max/mdev = 0.282/0.315/0.349/0.037 ms

Here is the packet capture, just change the extension to .pcap to open it.
out.txt

I'm not sure this is really an issue, and I could not find an RFC that explicitely states that a host should reject ethernet frames with a wrong destination address. Still, it seems to me that the correct behavior would be to ignore such packets.

Impossible to construct `phy::DeviceLimits`

The phy::DeviceLimits struct has a private dummy field, which makes meaningful construction impossible. The only way is through the Default implementation, but this yields a max_transmission_unit of 0…

A DeviceLimits struct is required for implementing the limits method of the phy::Device trait.

Support IP fragmentation

In a similar spirit, I would also like to implement support for IP fragmentation. The logic behind fragmentation is pretty straightforward, but the way to implement it less so.

So far it looks like it would need:

  • a queue for the fragmented IP packets, where they can be stored before reassembling and passing them to the higher layers
  • timers associated with each fragmented packet (similar to #25 ) so they can be dropped after a timeout

I think this means adding a queue to the https://github.com/m-labs/smoltcp/blob/master/src/iface/ethernet.rs

Thoughts?

IpEndpoint should have a FromStr implementation

IpEndpoint does not currently have a FromStr implementation or Parser::accept_ip_endpoint implementation.

Per the suggestion by @batonius.

... it's trivial to split an IPv4 endpoint at ':', IPv6 endpoints are quite tricky: https://tools.ietf.org/html/rfc5952#section-6 . I think the recommended style would be enough, it's the only one std::net::SocketAddrV6 supports: https://doc.rust-lang.org/nightly/src/std/net/parser.rs.html#280

Original comment: #72 (comment)

Server example does not work anymore

Hello,

I think change #57 requires the server example to be updated or something else is not correct?

RUST_BACKTRACE=1 cargo +stable run --example server tap0 
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/examples/server tap0`
[     0.001s] (socket::set): [0]: adding
[     0.001s] (socket::set): [1]: adding
[     0.001s] (socket::set): [2]: adding
[     0.001s] (socket::set): [3]: adding
[     0.001s] (socket::set): [4]: adding
[     0.001s] (socket::tcp): #1:*:6969: state=CLOSED=>LISTEN
[     0.001s] (socket::tcp): #2:*:6970: state=CLOSED=>LISTEN
[     0.001s] (socket::tcp): #3:*:6971: state=CLOSED=>LISTEN
[     0.001s] (socket::tcp): #4:*:6972: state=CLOSED=>LISTEN
[     0.001s] (iface::ethernet): cannot dispatch response packet: buffer space exhausted
thread 'main' panicked at 'poll error: Exhausted', /checkout/src/libcore/result.rs:906:4
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
             at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::_print
             at /checkout/src/libstd/sys_common/backtrace.rs:71
   2: std::panicking::default_hook::{{closure}}
             at /checkout/src/libstd/sys_common/backtrace.rs:60
             at /checkout/src/libstd/panicking.rs:381
   3: std::panicking::default_hook
             at /checkout/src/libstd/panicking.rs:397
   4: std::panicking::rust_panic_with_hook
             at /checkout/src/libstd/panicking.rs:611
   5: std::panicking::begin_panic
             at /checkout/src/libstd/panicking.rs:572
   6: std::panicking::begin_panic_fmt
             at /checkout/src/libstd/panicking.rs:522
   7: rust_begin_unwind
             at /checkout/src/libstd/panicking.rs:498
   8: core::panicking::panic_fmt
             at /checkout/src/libcore/panicking.rs:71
   9: core::result::unwrap_failed
             at /checkout/src/libcore/macros.rs:41
  10: <core::result::Result<T, E>>::expect
             at /checkout/src/libcore/result.rs:799
  11: server::main
             at examples/server.rs:188
  12: __rust_maybe_catch_panic
             at /checkout/src/libpanic_unwind/lib.rs:99
  13: std::rt::lang_start
             at /checkout/src/libstd/panicking.rs:459
             at /checkout/src/libstd/panic.rs:361
             at /checkout/src/libstd/rt.rs:61
  14: main
  15: __libc_start_main
  16: _start

Support for multiple interfaces

As mentioned in #50, I've been looking into support for multiple interfaces/devices. My overall plan is to leave the current EthernetInterface structure to represent a network interface with ARP cache, MAC/IP addresses, and a Device assigned to it, add InterfaceSet with a ManagedSlice of EthernetInterfaces and ipv4_gateway in it, and move all the packet processing logic from EthernetInterface to InterfaceSet.

I don't see any point in trying to do it statically, that means we should rely on trait objects a lot and have to accept the inevitable performance hit. The problem is that the current Device trait doesn't work well as a trait object because it uses the associated types by value. Since we need to call destructors on Rx/TxBuffer, and Box is the only way to own a trait object, refactoring Device::transmit to return something like Result<Box<AsMut[u8]>> looks promising, but using Box requires alloc, something we can't afford here.

All that means the Device trait should be redesigned into something more suitable for using as a trait object, maybe something like #49 (comment), but with the functions passed as function pointers directly to transmit/receive, if it makes any sense. Maybe we could have Device to own the latest received packet and have a method to return a slice to it, and use a functional argument for transmit.

I'm not sure if I should go ahead with this approach, so I wonder if you have any ideas.

Add the ability to set the TTL value for a UDP/TCP socket

I've recently started to look into adding the ability to set the TTL value for a socket.

This should be a trivial change. The following is my plan of attack.

  • Add a Option<u8> to Ipv4Repr and the Unspecified Repr
    • Corresponds to TTL for ipv4 and the hop limit for ipv6 (when ipv6 is implemented)
    • On lower ensure that the ttl is not lost
    • Add getters and setters.
  • Add a Option<u8> member to TcpSocket
  • Use the configured value On emiting the ipv4 packet
    • set the TTL to the recommended value of 64 if the optional value is None
    • use the configured value if Some

Due to the simplicity of this change there are minimal open questions.

  • Is hop limit (ipv6) or ttl (ipv4) a better/more readable name? At the moment I'm thinking I'll go with TTL.

Comments and critiques are welcomed!

Related to: #50

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.