Featured image of post smoltcp

smoltcp

appropriate lost property of rust by learning smoltcp

glossary

  • ingress, 入站
  • egress, 出站
  • repr: representation
  • cfg: configuration

pub crate

1
pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(1_000);

在你的代码中, pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(1_000); 表示 DISCOVERY_SILENT_TIME 常量在定义它的 crate 内部是可用的,但在其他 crate 中是不可用的。 这样可以帮助你控制 API 的可见性,确保只有内部代码可以使用某些功能。

struct in enum

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum NeighborState {
    /// Socket can be polled immediately.
    #[default]
    Active,
    /// Socket should not be polled until either `silent_until` passes or
    /// `neighbor` appears in the neighbor cache.
    Waiting {
        neighbor: IpAddress,   // size=0x17
        silent_until: Instant, // size=0x08
    },
}

这里的 Waiting 相当于是:NeighborState 中定义的 private 的 struct.

match if

1
2
3
4
5
match self.neighbor_state {
    NeighborState::Active => socket_poll_at,
    NeighborState::Waiting { neighbor, .. } if has_neighbor(neighbor) => socket_poll_at,
    NeighborState::Waiting { silent_until, .. } => PollAt::Time(silent_until),
}

这里看似很矛盾的一种情况:Waiting 有两条匹配,但是仔细看一下,the second pattern has an if, if true, then: choose the second pattern, else choose the last pattern.

Socket

1
2
3
4
5
6
pub enum Socket<'a> {
    Raw(raw::Socket<'a>),
    Icmp(icmp::Socket<'a>),
    Udp(udp::Socket<'a>),
    Tcp(tcp::Socket<'a>),
}

icmp::Socket

icmp, internet control message protocol.

endpoint: 一个特定的网络地址和端口的组合,用于表示一个通信的终点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pub struct Socket<'a> {
    rx_buffer: PacketBuffer<'a>,
    tx_buffer: PacketBuffer<'a>,
    /// The endpoint this socket is communicating with
    endpoint: Endpoint,
    /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
    hop_limit: Option<u8>,
    // rx_waker: WakerRegistration,
    // tx_waker: WakerRegistration,
}

pub enum Endpoint {
    #[default] Unspecified,
    Ident(u16),
    Udp(IpListenEndpoint),
}

pub struct ListenEndpoint {
    pub addr: Option<Address>,
    pub port: u16,
}

pub enum Address {
    Ipv4(Ipv4Address),
    Ipv6(Ipv6Address),
}

// defined in core::net
pub struct Ipv4Addr {
    octets: [u8; 4],
}

icmp::PacketBuffer

1
2
/// An ICMP packet ring buffer.
pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, IpAddress>;

crate::storage::PacketBuffer

1
2
3
4
5
/// An UDP packet ring buffer.
pub struct PacketBuffer<'a, H: 'a> {
    metadata_ring: RingBuffer<'a, PacketMetadata<H>>,
    payload_ring: RingBuffer<'a, u8>,
}

这里 H 是一个泛型参数,表示 H 必须与生命周期 ‘a 相关联。 在这个 icmp 例子中,H 就是 IpAddress, 这也就是以为这一条约束:IpAddress 的生命周期必须比 PacketBuffer 长 (包含相等情况). 在这种情况下是平凡的,因为 IpAddress 实现了 Copy, 这意味着在创建 PacketBuffer 结构体的时候,IpAddress 复制了一份, PacketBuffer 拥有新创建的 IpAddress, 因此 IpAddress 的生命周期应该与 PacketBuffer 的生命周期是一样长的,这满足约束。

我们还可以进行推广:只要 H 是所有权传递的,那么约束自然会被满足。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub struct PacketMetadata<H> {
    size: usize,
    header: Option<H>,
}

impl<H> PacketMetadata<H> {
    // ...
    fn packet(size: usize, header: H) -> PacketMetadata<H> {
        PacketMetadata {
            size: size,
            header: Some(header),
        }
    }
    // ...
}

impl<'a, H> PacketBuffer<'a, H> {
    /// Enqueue a single packet with the given header into the buffer, and
    /// return a reference to its payload, or return `Err(Full)`
    /// if the buffer is full.
    pub fn enqueue(&mut self, size: usize, header: H) -> Result<&mut [u8], Full> {
		// ...
        *self.metadata_ring.enqueue_one()? = PacketMetadata::packet(size, header);
		// ...
    }
}

maches!

1
2
3
if matches!(self.caps.medium, Medium::Ethernet) {
    total_len = EthernetFrame::<&[u8]>::buffer_len(total_len);
}

equales to below

1
2
3
4
5
6
if match (self.caps.medium) {
    Medium::Ethernet => true,
    _ => false,
} {
    total_len = EthernetFrame::<&[u8]>::buffer_len(total_len);
}

其中,typeof(medium) = Medium, which is a enum contains Medium::Ethernet variant

associated type

关联类型。

1
2
3
4
5
6
7
8
// smoltcp/src/phy/mod.rs
pub trait Device {
    type RxToken<'a>: RxToken where Self: 'a;
    type TxToken<'a>: TxToken where Self: 'a;
    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>>;
    fn capabilities(&self) -> DeviceCapabilities;
}

need to fill the RxToken in impl. A typical example is: TryFrom, which we should fill the Error in out impl.

这里值得注意的是:type RxToken<'a>: RxToken where Self: 'a 中的 'a 应该是来自于 impl 的

声明周期换名

这里有生命周期:‘a, 其实这里应该是有两种生命周期,下面我们来换个名

1
2
3
4
5
6
7
8
// smoltcp/src/phy/mod.rs
pub trait Device {
    type RxToken<'a>: RxToken where Self: 'a;
    type TxToken<'b>: TxToken where Self: 'b;
    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>>;
    fn capabilities(&self) -> DeviceCapabilities;
}

生命周期推断

1
fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;

这里的两个 '_ 其实可以推断出来,来自于 &self

tuntap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub struct RxToken {
    buffer: Vec<u8>,
}
pub struct TxToken {
    lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
}
impl Device for TunTapInterface {
    type RxToken<'a> = RxToken;
    type TxToken<'a> = TxToken;
    // ...
}

可见,RxToken, TxToken 并没有生命周期的约束,这种情况是平凡的。

拍案:没懂,感觉 生命周期比 xx 长,这个概念比较奇怪,可能还是 “生命周期有关联” 这个说法更好一些。

configuration

1
2
3
4
#[cfg(all(
    any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
    unix
))]

all, any, 一种连词。

发现 cfg 只是 configuration 的意思,我还以为是 conditional xxx (条件编译).

闭包

Fn

1
// TODO:

FnOnce

1
// TODO:

FnMut

1
2
3
let mut lambda = | xx : T | {
    // ...
};

Range

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// smoltcp/src/wire/mod.rs
mod field {
    pub type Field = ::core::ops::Range<usize>;
    pub type Rest = ::core::ops::RangeFrom<usize>;
}

// example
mod field {
    use crate::wire::field::*;

    pub const DESTINATION: Field = 0..6;
    pub const SOURCE: Field = 6..12;
    pub const ETHERTYPE: Field = 12..14;
    pub const PAYLOAD: Rest = 14..;
}

注意到一个有趣的事情:import 的时候使用的是绝对路径。

AsRef

1
2
3
4
5
6
7
8
9
// src/rust/library/core/convert/mod.rs
pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

// smoltcp/src/wire/ethernet.rs
pub struct Frame<T: AsRef<[u8]>> {
    buffer: T,
}

non_exhaustive

1
2
3
4
5
6
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
#[non_exhaustive]
pub struct PacketMeta {
    pub id: u32,
}

the struct/enum tagged with #[non_exhaustive] means: we COULDNOT construct the data in this way:

1
2
3
let meta = PacketMet {
    id: xxx
};

we should use Default construct and modify the fields instead:

1
2
let mut meta = PacketMeta::default();
meta.id = xxx;

macro_use

发现了一个操蛋的事情。这可能与 rust 编译器实现有关,顺序扫描几遍…, sad. 如果 #[macro_use] mod macros;pub mod phy; 互换位置, 那么在引用了宏的地方就会报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//# src/lib.rs
#[macro_use]
mod macros; // this should be previous than the mod phy; fuck rust
// ...
pub mod phy;

//# src/macros.rs
macro_rules! net_log {
    (trace, $($arg:expr),*) => { println!($($arg),*) };
    (debug, $($arg:expr),*) => { println!($($arg),*) };
}

macro_rules! net_debug {
    ($($arg:expr),*) => (net_log!(debug, $($arg),*));
}

//# src/phy/tuntap_interface.rs
impl phy::TxToken for TxToken {
    fn consume<R, F>(self, len: usize, f: F) -> R
    where
        F: FnOnce(&mut [u8]) -> R,
    {
        // ...
        net_debug!("phy: tx failed due to WouldBlock");
        // ...
    }
}

高阶函数

拍案: 发现 smoltcp 有许多这种: 传入一个函数处理的例子, 比方说 update_ip_addrs. 传入一个函数指针, 然后让函数指针处理列表的内容.

1
2
3
4
5
6
impl Interface {
    pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr>)>(&mut self, f: F) {
        f(&mut self.inner.ip_addrs); // update ip_addrs
        // ...
    }
}

haskell 中这种高阶函数更是常见

1
2
3
4
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith _ _ [] = []
zipWith _ [] _ = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys

smoltcp/src/phy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 [wangfiox@gentoo] in phy ± |main |
 tree .
.
├── fault_injector.rs // 故障注入, 用于模拟丢包的情况, for test
├── fuzz_injector.rs // 模糊测试注入, for test
├── loopback.rs
├── mod.rs
├── pcap_writer.rs // 用于数据包捕获的设备, 将网络数据包以 libpcap 格式写入到一个输出目标
├── raw_socket.rs
├── sys
   ├── bpf.rs
   ├── linux.rs
   ├── mod.rs
   ├── raw_socket.rs
   └── tuntap_interface.rs
├── tracer.rs
└── tuntap_interface.rs

这个

struct size=0

used for tag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//# smoltcp/src/iface/route.rs

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RouteTableFull;

impl core::fmt::Display for RouteTableFull {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Route table full")
    }
}

defmt