Skip to main content

zenith_linux/
netif.rs

1//! 网络接口抽象(Linux 平台)
2//!
3//! 封装 `libc::if_nametoindex` 等 Linux 特有系统调用为 safe API,
4//! 严格遵守 AGENT.md §1.1 "所有 unsafe 必须封装在 zenith-linux 内部"。
5//!
6//! # 安全保证
7//! 本模块所有 `unsafe` 块均集中在 `libc` FFI 调用,对外提供 `Option` 接口。
8//! 调用方零 unsafe 即可完成网卡名→ifindex 的转换。
9
10#![allow(unsafe_code)]
11
12use std::ffi::CString;
13use std::io;
14
15/// 网卡名最大长度(含 NUL 终止符)
16///
17/// Linux 限制 `IFNAMSIZ` = 16,包含 NUL 终止符,故有效网卡名 ≤ 15 字节。
18const IFNAMSIZ: usize = 16;
19
20/// 错误类型
21#[derive(Debug, thiserror::Error)]
22pub enum NetIfError {
23    /// 网卡名包含内部 NUL 字节
24    #[error("interface name contains NUL byte")]
25    InvalidName,
26    /// 网卡名过长(超过 IFNAMSIZ-1)
27    #[error("interface name too long (max {} bytes)", IFNAMSIZ - 1)]
28    NameTooLong,
29    /// `if_nametoindex` 系统调用失败(网卡不存在或权限不足)
30    #[error("if_nametoindex failed for {name:?}: {source}")]
31    NotFound {
32        /// 出错的网卡名
33        name: String,
34        /// 底层 IO 错误(errno 转换)
35        #[source]
36        source: io::Error,
37    },
38}
39
40/// 将网卡名转换为内核 ifindex
41///
42/// # 参数
43/// * `name` - 网卡名(如 `"eth0"`、`"lo"`)
44///
45/// # 返回
46/// * `Ok(u32)` - 网卡对应的 ifindex(≥1,1 通常为 lo)
47/// * `Err(NetIfError)` - 名称非法或网卡不存在
48///
49/// # 安全
50/// 内部 `unsafe` 块仅用于 `libc::if_nametoindex` FFI 调用:
51/// - `CString::new` 已确保 NUL 终止
52/// - `as_ptr()` 返回的指针在 FFI 调用期间保持有效
53/// - `libc::if_nametoindex` 不持有指针所有权,仅读取
54///
55/// # 示例
56/// ```no_run
57/// use zenith_linux::if_nametoindex;
58/// let idx = if_nametoindex("lo").expect("lo must exist");
59/// assert!(idx >= 1);
60/// ```
61#[cfg(target_os = "linux")]
62pub fn if_nametoindex(name: &str) -> Result<u32, NetIfError> {
63    // 边界检查:网卡名 ≤ IFNAMSIZ-1(不含 NUL 终止符)
64    if name.len() >= IFNAMSIZ {
65        return Err(NetIfError::NameTooLong);
66    }
67
68    // CString::new 在名称含内部 NUL 时返回 Err
69    let c_name = CString::new(name).map_err(|_| NetIfError::InvalidName)?;
70
71    // SAFETY:
72    // 1. c_name 是 CString,保证 NUL 终止且无内部 NUL;
73    // 2. as_ptr() 返回的 *const c_str 在 CString 存活期间保持有效;
74    // 3. libc::if_nametoindex 是 POSIX 标准函数,仅读取字符串,不持有指针所有权;
75    // 4. 返回值 0 表示失败(errno 已设置),非 0 即合法 ifindex。
76    let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
77    if idx == 0 {
78        // SAFETY: libc::__errno_location 在 Linux glibc/musl 上返回线程局部 errno 指针
79        let err = io::Error::last_os_error();
80        return Err(NetIfError::NotFound {
81            name: name.to_string(),
82            source: err,
83        });
84    }
85    Ok(idx)
86}
87
88/// 非 Linux 平台占位实现
89#[cfg(not(target_os = "linux"))]
90pub fn if_nametoindex(_name: &str) -> Result<u32, NetIfError> {
91    Err(NetIfError::NotFound {
92        name: _name.to_string(),
93        source: io::Error::new(io::ErrorKind::Unsupported, "non-Linux platform"),
94    })
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_lo_ifindex() {
103        // lo 在所有 Linux 系统上必须存在且 ifindex=1
104        match if_nametoindex("lo") {
105            Ok(idx) => {
106                assert_eq!(idx, 1, "lo must have ifindex=1");
107            }
108            Err(NetIfError::NotFound { .. }) => {
109                // 极少数容器环境可能无 lo,但 errno 必须正确分类
110            }
111            Err(e) => panic!("unexpected error for lo: {:?}", e),
112        }
113    }
114
115    #[test]
116    fn test_nonexistent_iface() {
117        // 网卡名必须 ≤ IFNAMSIZ-1=15 字节,否则会先返回 NameTooLong
118        let result = if_nametoindex("zz_nx99");
119        assert!(matches!(result, Err(NetIfError::NotFound { .. })));
120    }
121
122    #[test]
123    fn test_name_too_long() {
124        // 15 字节合法,16 字节超限
125        let long_name = "a".repeat(IFNAMSIZ);
126        assert_eq!(long_name.len(), IFNAMSIZ);
127        let result = if_nametoindex(&long_name);
128        assert!(matches!(result, Err(NetIfError::NameTooLong)));
129    }
130
131    #[test]
132    fn test_name_with_nul() {
133        let result = if_nametoindex("eth\0");
134        assert!(matches!(result, Err(NetIfError::InvalidName)));
135    }
136
137    #[test]
138    fn test_boundary_name_length() {
139        // 15 字节网卡名是合法上限(虽然通常不存在)
140        let max_name = "a".repeat(IFNAMSIZ - 1);
141        // 不应返回 NameTooLong(即便网卡不存在也应返回 NotFound)
142        let result = if_nametoindex(&max_name);
143        assert!(!matches!(result, Err(NetIfError::NameTooLong)));
144    }
145}