#ipv4-address #tcp-ip #tcp-socket #tcp #ip-address #tcp-udp #ip

no-std smoltcp

专为无内存堆的裸机、实时系统设计的TCP/IP协议栈

20个版本 (10个重大变更)

0.11.0 2023年12月23日
0.10.0 2023年6月26日
0.9.1 2023年2月8日
0.8.2 2022年11月27日
0.1.0 2016年12月28日

#1 in #tcp-socket

Download history 17193/week @ 2024-04-22 13437/week @ 2024-04-29 16503/week @ 2024-05-06 17520/week @ 2024-05-13 15715/week @ 2024-05-20 15955/week @ 2024-05-27 14954/week @ 2024-06-03 16003/week @ 2024-06-10 15656/week @ 2024-06-17 18785/week @ 2024-06-24 15704/week @ 2024-07-01 19834/week @ 2024-07-08 23663/week @ 2024-07-15 24805/week @ 2024-07-22 35254/week @ 2024-07-29 39967/week @ 2024-08-05

125,047 个月下载量
用于 73 个crate (45个直接使用)

0BSD 许可证

1.5MB
39K SLoC

smoltcp

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

smoltcp 是一个独立的事件驱动TCP/IP协议栈,专为裸机、实时系统设计。其设计目标是简单性和健壮性。设计反目标是避免复杂的编译时计算,例如宏或类型技巧,即使这会影响性能。

smoltcp 完全不需要堆分配,有广泛的文档,且可以在稳定的Rust 1.65及以后版本编译。

smoltcp 在与Linux TCP协议栈的环回模式下测试时,实现了~Gbps的吞吐量

功能

smoltcp 缺少许多广泛部署的功能,通常是因为尚未有人实现它们。为了设定正确的期望,列出了已实现和未实现的功能。

媒体层

支持3种介质。

  • 以太网
    • 支持常规的以太网II帧。
    • 支持单播、广播和多播数据包。
    • 支持ARP数据包(包括免费请求和响应)。
    • ARP请求的发送速率不超过每秒一个。
    • 缓存的ARP条目在1分钟后过期。
    • 不支持 802.3帧和802.1Q。
    • 不支持 长帧。
  • IP
    • 支持单播、广播和多播数据包。
  • IEEE 802.15.4
    • 仅支持数据帧。

IP层

IPv4

  • 生成并验证IPv4头部校验和。
  • 每个套接字可配置IPv4的生存时间值,默认为64。
  • 支持IPv4默认网关。
  • 支持通过默认网关或CIDR路由表路由出站IPv4数据包。
  • 支持IPv4分片和重组。
  • 不支持 IPv4选项,并且会静默忽略。

IPv6

  • 每个套接字可配置IPv6的跳数限制值,默认为64。
  • 支持通过默认网关或CIDR路由表路由出站IPv6数据包。
  • 支持IPv6逐跳头部。
  • 当遇到未识别的IPv6下一个头部时,将生成ICMPv6参数问题消息。
  • 在响应未知的IPv6逐跳选项时,**不**生成ICMPv6参数问题消息。

6LoWPAN

  • 实现了RFC6282
  • 支持分片,如RFC4944中定义。
  • 支持UDP头部压缩/解压缩。
  • 支持扩展头部压缩/解压缩。
  • **不支持**未压缩的IPv6扩展头部。

IP多播

IGMP

支持IGMPv1和IGMPv2协议,并且提供了IPv4多播。

  • 在等于最大响应时间除以要报告的组数的时间间隔内发送成员报告。

ICMP层

ICMPv4

支持ICMPv4协议,并提供了ICMP套接字。

  • 支持ICMPv4头部校验和。
  • 响应回显请求生成ICMPv4回显应答。
  • ICMP套接字可以监听ICMPv4端口不可达消息或具有给定IPv4标识符字段的任何ICMPv4消息。
  • 当收到ICMPv4协议不可达消息时,**不**将其传递给上层。
  • **不**生成ICMPv4参数问题消息。

ICMPv6

支持ICMPv6协议,并提供了ICMP套接字。

  • 支持ICMPv6头部校验和。
  • 响应回显请求生成ICMPv6回显应答。
  • 当收到ICMPv6协议不可达消息时,**不**将其传递给上层。

NDISC

  • 在响应邻居请求时生成邻居通告消息。
  • **不**生成或读取路由通告消息。
  • **不**生成或读取路由请求消息。
  • **不**生成或读取重定向头部消息。

UDP层

支持IPv4和IPv6上的UDP协议,并提供了UDP套接字。

  • 始终生成并验证头部校验和。
  • 在没有监听套接字的端口接收到数据包时,生成ICMP目标不可达消息。

TCP层

支持IPv4和IPv6上的TCP协议,并提供了服务器和客户端TCP套接字。

  • 生成并验证头部校验和。
  • 协商最大分段大小。
  • 协商窗口缩放。
  • 在不等待确认的情况下发送多个数据包。
  • 支持支持最多4个或32个序列空间间隔的无序分段的重组。
  • 可以以可配置的间隔发送保活数据包。
  • 重传超时从RTT估计开始,每次加倍。
  • 时间等待超时有固定间隔10秒。
  • 用户超时有一个可配置的间隔。
  • 支持带可配置延迟的延迟确认。
  • 实现了Nagle算法。
  • **不**实现选择性确认。
  • **不**实现避免愚蠢窗口综合征。
  • **不**实现拥塞控制。
  • **不**支持时间戳。
  • **忽略**紧急指针。
  • **不**实现探测零窗口。
  • **不**实现数据包化层路径MTU发现(PLPMTU)。

安装

要在项目中使用smoltcp库,请将以下内容添加到Cargo.toml

[dependencies]
smoltcp = "0.10.0"

默认配置假设为托管环境,便于评估。您可能希望禁用默认功能并逐个进行配置

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

功能标志

功能 std

std 功能允许通过依赖 boxed::Boxvec::Vec 使用由网络堆栈拥有的对象和切片。

此功能默认启用。

功能 alloc

alloc 功能允许通过依赖 alloc 库中的集合来使用网络堆栈拥有的对象。这仅在 nightly rustc 上有效。

此功能默认禁用。

功能 log

log 功能允许通过 log 库 在网络堆栈中记录事件。正常事件(例如缓冲区级别或 TCP 状态变化)使用 TRACE 日志级别发出。异常事件(例如格式错误的数据包)使用 DEBUG 日志级别发出。

此功能默认启用。

功能 defmt

defmt 功能允许通过 defmt 库 记录事件。

此功能默认禁用,并且不能与 log 同时使用。

功能 verbose

verbose 功能允许记录事件,其中日志记录本身可能会产生非常高的开销。例如,每当应用程序从套接字读取或写入至少 1 字节时发出日志行可能会压倒应用程序逻辑,除非使用了 BufReaderBufWriter,而这在无堆栈系统上当然不可用。

此功能默认禁用。

功能 phy-raw_socketphy-tuntap_interface

分别启用 smoltcp::phy::RawSocketsmoltcp::phy::TunTapInterface

这些功能默认启用。

功能 socket-rawsocket-udpsocket-tcpsocket-icmpsocket-dhcpv4socket-dns

启用相应的套接字类型。

这些功能默认启用。

功能 proto-ipv4proto-ipv6proto-sixlowpan

分别启用 IPv4IPv66LoWPAN

配置

smoltcp 有一些配置设置是在编译时设置的,影响缓冲区的大小和数量。

它们可以通过两种方式设置

  • 通过 Cargo 功能:启用一个像 <name>- 这样的功能。 name 必须是小写字母,并使用破折号代替下划线。例如,iface-max-addr-count-3。只有一组值可用,检查 Cargo.toml 以获取列表。
  • 通过构建时的环境变量:设置名为 SMOLTCP_value 的变量。例如,SMOLTCP_IFACE_MAX_ADDR_COUNT=3 cargo build。您也可以在 [env] 部分中设置它们,例如在 .cargo/config.toml 中。可以设置任何值,与 Cargo 功能不同。

环境变量优先于 Cargo 功能。如果为同一设置启用了具有不同值的两个 Cargo 功能,则编译失败。

IFACE_MAX_ADDR_COUNT

一个接口可以分配的IP地址的最大数量(包括IPv4和IPv6地址)。默认:2。

IFACE_MAX_MULTICAST_GROUP_COUNT

一个接口可以加入的最大多播组数量。默认:4。

IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT

一个接口可以分配的6LoWPAN地址上下文的最大数量。默认:4。

IFACE_NEIGHBOR_CACHE_COUNT

邻居缓存(也称为“ARP缓存”或“ARP表”)中保留的“IP地址 -> 硬件地址”条目数量。默认:4。

IFACE_MAX_ROUTE_COUNT

一个接口可以添加的最大路由数量。包括默认路由。包括IPv4和IPv6。默认:2。

FRAGMENTATION_BUFFER_SIZE

用于分片大于MTU的出站数据包的缓冲区大小。大于此设置的包将丢弃而不是分片。默认:1500。

ASSEMBLER_MAX_SEGMENT_COUNT

汇编器可以保持的非连续段的最大数量。用于数据包重组和TCP流重组。默认:4。

REASSEMBLY_BUFFER_SIZE

用于重组(解分片)入站数据包的缓冲区大小。如果重组的数据包大于此设置,则将丢弃而不是重组。默认:1500。

REASSEMBLY_BUFFER_COUNT

重组缓冲区数量,即同时可以重组的不同入站数据包的数量。默认:1。

DNS_MAX_RESULT_COUNT

保留给定DNS查询地址结果的最大数量。例如,如果设置为2,且查询的名称有4个A记录,则只返回前两个。默认:1。

DNS_MAX_SERVER_COUNT

一个DNS套接字中可以配置的最大DNS服务器数量。默认:1。

DNS_MAX_NAME_SIZE

可以查询的DNS名称的最大长度。默认:255。

IPV6_HBH_MAX_OPTIONS

IPv6 Hop-by-Hop头可以保留的最大解析选项数量。默认:1。

托管使用示例

smoltcp是一个独立的网络栈,需要能够传输和接收原始帧。为了测试目的,我们将使用常规操作系统,并在用户空间进程中运行smoltcp。目前仅支持Linux。

在*nix操作系统上,通常需要超级用户权限才能发送和接收原始帧,但在Linux上可以创建一个持久性tap接口,该接口可以被特定用户操作

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

可以通过启用tap接口的路由,让smoltcp访问互联网

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

桥接连接

除了上面的路由连接外,您还可以设置桥接(交换)连接。这将使smoltcp直接与您的局域网通信,使用真实的ARP等。这是运行DHCP示例所需的。

注意:在这种情况下,示例的IP配置必须与您的局域网匹配!

注意:这仅适用于实际的以太网连接。它不会在WiFi连接上工作。

# 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

拆除

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

故障注入

为了演示smoltcp对不良网络条件的响应,所有示例都实现了通过命令行选项提供的故障注入

  • --drop-chance选项随机丢弃数据包,给定概率以百分比表示。
  • --corrupt-chance选项随机突变数据包中的一个八位字节,给定概率以百分比表示。
  • --size-limit选项丢弃大于指定大小的数据包。
  • --tx-rate-limit--rx-rate-limit选项设置令牌桶速率限制器中的令牌数量,以每桶的数据包数量表示。
  • --shaping-interval选项设置令牌桶速率限制器的填充间隔,以毫秒表示。

对于 --drop-chance--corrupt-chance 的一个良好的起始值是 15%。对于 --?x-rate-limit 的一个良好起始值是 4,而 --shaping-interval 是 50 毫秒。

请注意,由故障注入器丢弃的包仍然会被跟踪;rx: randomly dropping a packet 消息表示它上面的包被丢弃,而 tx: randomly dropping a packet 消息表示它下面的包被丢弃。

数据包转储

所有示例都提供了一个 --pcap 选项,该选项写入一个包含 smoltcp 所见每个数据包视图的 libpcap 文件。

examples/tcpdump.rs

examples/tcpdump.rs 是一个 tcpdump 工具的小型克隆。

与其它示例不同,它使用原始套接字,因此可以在常规接口上使用,例如 eth0wlan0,以及我们上面创建的 tap0 接口。

阅读其 源代码,然后运行它:

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

examples/httpclient.rs

examples/httpclient.rs 模拟一个可以发起 HTTP 请求的网络主机。

该主机被分配了硬件地址 02-00-00-00-00-02,IPv4 地址 192.168.69.1 和 IPv6 地址 fdaa::1

阅读其 源代码,然后运行它:

cargo run --example httpclient -- --tap tap0 ADDRESS URL

例如

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

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

它连接到指定的地址(不是主机名)和 URL,并打印任何返回的响应数据。TCP 套接字缓冲区限制为 1024 字节,以便使数据包跟踪更有趣。

examples/ping.rs

examples/ping.rs 使用原始套接字实现了 ping 工具的最小版本。

该主机被分配了硬件地址 02-00-00-00-00-02 和 IPv4 地址 192.168.69.1

阅读其 源代码,然后运行它:

cargo run --example ping -- --tap tap0 ADDRESS

它以一秒的间隔发送一系列 4 个 ICMP ECHO_REQUEST 包到指定的地址,并打印出每个有效的 ECHO_RESPONSE 接收到的状态行。

第一个 ECHO_REQUEST 包预计会丢失,因为启动后 arp_cache 是空的;ECHO_REQUEST 包被丢弃,并发送了 ARP 请求。

目前尚未实现子网掩码,因此此示例唯一能够到达的地址是 tap 接口的另一端,192.168.69.100。它无法到达自身,因为进入 tap 接口的数据包不会回环。

examples/server.rs

examples/server.rs 模拟一个可以响应基本请求的网络主机。

主机被分配了硬件地址 02-00-00-00-00-00-01 和 IPv4 地址 192.168.69.1

阅读其 源代码,然后运行它

cargo run --example server -- --tap tap0

它响应

  • ping(ping 192.168.69.1);
  • 端口 6969 上的 UDP 数据包(socat stdio udp4-connect:192.168.69.1:6969 <<<"abcdefg"),其中它将无限期地以输入块的反向响应;
  • 端口 6969 上的 TCP 连接(socat stdio tcp4-connect:192.168.69.1:6969),其中它将对任何传入连接响应 "hello" 并立即关闭它;
  • 端口 6970 上的 TCP 连接(socat stdio tcp4-connect:192.168.69.1:6970 <<<"abcdefg"),其中它将以输入块的反向无限期响应。
  • 端口 6971 上的 TCP 连接(socat stdio tcp4-connect:192.168.69.1:6971 </dev/urandom),这将消耗数据。此外,此端口启用了存活数据包(每 1 秒一次)和用户超时(2 秒),尝试使用故障注入触发它们。
  • 端口 6972 上的 TCP 连接(socat stdio tcp4-connect:192.168.69.1:6972 >/dev/null),这将提供数据。

除了端口 6971 上的套接字之外,缓冲区长度仅为 64 字节,以便于测试资源耗尽条件。

examples/client.rs

examples/client.rs 模拟了一个可以发起基本请求的网络主机。

主机被分配了硬件地址 02-00-00-00-00-02 和 IPv4 地址 192.168.69.2

阅读其 源代码,然后运行

cargo run --example client -- --tap tap0 ADDRESS PORT

它连接到指定的地址(不是主机名)和端口(例如 socat stdio tcp4-listen:1234),并将无限期地响应输入的反转块。

examples/benchmark.rs

examples/benchmark.rs 实现了一个简单的吞吐量基准测试。

阅读其 源代码,然后运行

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

它从不同的线程建立到自身的连接,并单方向读取或写入大量数据。

典型结果(在 Intel Core i7-7500U CPU 和 Linux 4.9.65 x86_64 内核上运行在戴尔 XPS 13 9360 笔记本电脑上)如下

$ 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

裸机使用示例

不使用主机操作系统服务的示例不如使用服务的示例具有说明性。因此,只提供了一个此类示例。

examples/loopback.rs

examples/loopback.rssmoltcp 配置为通过环回接口与自身通信。虽然它不需要 std,但此示例仍需要 alloc 功能才能运行,以及 logproto-ipv4socket-tcp

阅读其 源代码,然后运行,不带 std

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

... 或者带 std(在这种情况下无需显式列出功能)

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

它打开服务器和客户端 TCP 套接字,并传输数据块。您可以通过在 Wireshark 中打开 loopback.pcap 来检查数据包交换。

如果启用 std 功能,它将打印日志和数据包转储,并且可以进行故障注入;否则,将不显示任何内容,不接受任何选项。

许可证

smoltcp 在 0-clause BSD 许可证条款下分发。

有关详细信息,请参阅 LICENSE-0BSD

依赖关系

~0.6–0.9MB
~19K SLoC