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
125,047 个月下载量
用于 73 个crate (45个直接使用)
1.5MB
39K SLoC
smoltcp
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
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::Box
和 vec::Vec
使用由网络堆栈拥有的对象和切片。
此功能默认启用。
功能 alloc
alloc
功能允许通过依赖 alloc
库中的集合来使用网络堆栈拥有的对象。这仅在 nightly rustc 上有效。
此功能默认禁用。
功能 log
log
功能允许通过 log 库 在网络堆栈中记录事件。正常事件(例如缓冲区级别或 TCP 状态变化)使用 TRACE 日志级别发出。异常事件(例如格式错误的数据包)使用 DEBUG 日志级别发出。
此功能默认启用。
功能 defmt
defmt
功能允许通过 defmt 库 记录事件。
此功能默认禁用,并且不能与 log
同时使用。
功能 verbose
verbose
功能允许记录事件,其中日志记录本身可能会产生非常高的开销。例如,每当应用程序从套接字读取或写入至少 1 字节时发出日志行可能会压倒应用程序逻辑,除非使用了 BufReader
或 BufWriter
,而这在无堆栈系统上当然不可用。
此功能默认禁用。
功能 phy-raw_socket
和 phy-tuntap_interface
分别启用 smoltcp::phy::RawSocket
和 smoltcp::phy::TunTapInterface
。
这些功能默认启用。
功能 socket-raw
、socket-udp
、socket-tcp
、socket-icmp
、socket-dhcpv4
、socket-dns
启用相应的套接字类型。
这些功能默认启用。
功能 proto-ipv4
、proto-ipv6
和 proto-sixlowpan
配置
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 工具的小型克隆。
与其它示例不同,它使用原始套接字,因此可以在常规接口上使用,例如 eth0
或 wlan0
,以及我们上面创建的 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.rs 将 smoltcp 配置为通过环回接口与自身通信。虽然它不需要 std
,但此示例仍需要 alloc
功能才能运行,以及 log
、proto-ipv4
和 socket-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