Skip to main content

c/
ifaddrs.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// getifaddrs() via rtnetlink, plus if_nametoindex/if_indextoname/
5// if_nameindex backed by /sys/class/net.
6
7#[repr(C)]
8pub struct IfAddrs {
9    pub ifa_next: *mut IfAddrs,
10    pub ifa_name: *mut c_char,
11    pub ifa_flags: c_uint,
12    pub ifa_addr: *mut c_void,
13    pub ifa_netmask: *mut c_void,
14    pub ifa_ifu: *mut c_void,
15    pub ifa_data: *mut c_void,
16}
17
18#[repr(C, align(8))]
19#[derive(Copy, Clone)]
20struct SockAny {
21    data: [u8; 64],
22}
23
24#[repr(C)]
25struct IfaStorage {
26    ifa: IfAddrs,
27    hash_next: *mut IfaStorage,
28    addr: SockAny,
29    netmask: SockAny,
30    ifu: SockAny,
31    index: c_uint,
32    name: [c_char; 17],
33}
34
35const IFADDRS_HASH_SIZE: usize = 64;
36
37struct IfaCtx {
38    first: *mut IfAddrs,
39    last: *mut IfAddrs,
40    hash: [*mut IfaStorage; IFADDRS_HASH_SIZE],
41}
42
43#[repr(C)]
44struct NlMsgHdr {
45    len: u32,
46    type_: u16,
47    flags: u16,
48    seq: u32,
49    pid: u32,
50}
51
52#[repr(C)]
53struct NlRta {
54    len: u16,
55    type_: u16,
56}
57
58#[repr(C)]
59struct IfInfoMsg {
60    family: u8,
61    pad: u8,
62    type_: u16,
63    index: i32,
64    flags: u32,
65    change: u32,
66}
67
68#[repr(C)]
69struct IfAddrMsg {
70    family: u8,
71    prefixlen: u8,
72    flags: u8,
73    scope: u8,
74    index: u32,
75}
76
77#[repr(C)]
78struct SockAddrNl {
79    family: u16,
80    pad: u16,
81    pid: u32,
82    groups: u32,
83}
84
85const NLMSG_ERROR: u16 = 2;
86const NLMSG_DONE: u16 = 3;
87const RTM_NEWLINK: u16 = 16;
88const RTM_NEWADDR: u16 = 20;
89const RTM_GETLINK: u16 = 18;
90const RTM_GETADDR: u16 = 22;
91const IFLA_ADDRESS: u16 = 1;
92const IFLA_BROADCAST: u16 = 2;
93const IFLA_IFNAME: u16 = 3;
94const IFA_ADDRESS: u16 = 1;
95const IFA_LOCAL: u16 = 2;
96const IFA_LABEL: u16 = 3;
97const IFA_BROADCAST: u16 = 4;
98
99unsafe fn copy_addr(
100    r: *mut *mut c_void,
101    af: c_int,
102    sa: *mut SockAny,
103    addr: *const u8,
104    addrlen: usize,
105    ifindex: c_int,
106) {
107    let base = sa as *mut u8;
108    let dst: *mut u8;
109    let len: usize;
110    if af == AF_INET {
111        dst = base.add(4);
112        len = 4;
113    } else if af == AF_INET6 {
114        dst = base.add(8);
115        len = 16;
116        let linklocal = addrlen >= 2 && *addr == 0xfe && ((*addr.add(1) & 0xc0) == 0x80);
117        let mclink = addrlen >= 2 && *addr == 0xff && ((*addr.add(1) & 0x0f) == 2);
118        if linklocal || mclink {
119            *(base.add(24) as *mut u32) = ifindex as u32;
120        }
121    } else {
122        return;
123    }
124    if addrlen < len { return; }
125    *(base as *mut u16) = af as u16;
126    core::ptr::copy_nonoverlapping(addr, dst, len);
127    *r = sa as *mut c_void;
128}
129
130unsafe fn gen_netmask(r: *mut *mut c_void, af: c_int, sa: *mut SockAny, prefixlen: c_int) {
131    let mut addr = [0u8; 16];
132    let mut pl = prefixlen;
133    if pl > 128 { pl = 128; }
134    let i = (pl / 8) as usize;
135    for k in 0..i { addr[k] = 0xff; }
136    if i < 16 {
137        addr[i] = (0xffu16 << (8 - (pl % 8))) as u8;
138    }
139    copy_addr(r, af, sa, addr.as_ptr(), 16, 0);
140}
141
142unsafe fn copy_lladdr(
143    r: *mut *mut c_void,
144    sa: *mut SockAny,
145    addr: *const u8,
146    addrlen: usize,
147    ifindex: c_int,
148    hatype: u16,
149) {
150    if addrlen > 24 { return; }
151    let base = sa as *mut u8;
152    *(base as *mut u16) = 17; // AF_PACKET
153    *(base.add(2) as *mut u16) = 0;
154    *(base.add(4) as *mut i32) = ifindex;
155    *(base.add(8) as *mut u16) = hatype;
156    *base.add(10) = 0;
157    *base.add(11) = addrlen as u8;
158    core::ptr::copy_nonoverlapping(addr, base.add(12), addrlen);
159    *r = sa as *mut c_void;
160}
161
162unsafe fn parse_rtattrs<F: FnMut(u16, *const u8, usize)>(
163    hdr: *const NlMsgHdr,
164    payload_off: usize,
165    mut cb: F,
166) {
167    let start = hdr as *const u8;
168    let end = start.add((*hdr).len as usize);
169    let mut p = start.add(payload_off);
170    while p.add(4) <= end {
171        let rta = p as *const NlRta;
172        let alen = (*rta).len as usize;
173        if alen < 4 || p.add(alen) > end { break; }
174        let atype = (*rta).type_ & 0x3fff;
175        cb(atype, p.add(4), alen - 4);
176        p = p.add((alen + 3) & !3usize);
177    }
178}
179
180unsafe fn netlink_msg_to_ifaddr(ctx: *mut IfaCtx, hdr: *const NlMsgHdr) -> c_int {
181    let htype = (*hdr).type_;
182    let ifs: *mut IfaStorage;
183    let mut ifs0: *mut IfaStorage = core::ptr::null_mut();
184    if htype == RTM_NEWLINK {
185        let hb = hdr as *const u8;
186        let payload = hb.add(16) as *const IfInfoMsg;
187        ifs = crate::calloc(1, core::mem::size_of::<IfaStorage>()) as *mut IfaStorage;
188        if ifs.is_null() { return -1; }
189        (*ifs).index = (*payload).index as c_uint;
190        (*ifs).ifa.ifa_flags = (*payload).flags;
191        let index = (*payload).index;
192        let hatype = (*payload).type_;
193        parse_rtattrs(hdr, 16 + core::mem::size_of::<IfInfoMsg>(), |atype, data, len| {
194            match atype {
195                IFLA_IFNAME => {
196                    if len < (*ifs).name.len() {
197                        core::ptr::copy_nonoverlapping(data, (*ifs).name.as_mut_ptr() as *mut u8, len);
198                        (*ifs).name[len] = 0;
199                        (*ifs).ifa.ifa_name = (*ifs).name.as_mut_ptr();
200                    }
201                }
202                IFLA_ADDRESS => {
203                    copy_lladdr(&mut (*ifs).ifa.ifa_addr, &mut (*ifs).addr, data, len, index, hatype);
204                }
205                IFLA_BROADCAST => {
206                    copy_lladdr(&mut (*ifs).ifa.ifa_ifu, &mut (*ifs).ifu, data, len, index, hatype);
207                }
208                _ => {}
209            }
210        });
211        if !(*ifs).ifa.ifa_name.is_null() {
212            let bucket = ((*ifs).index as usize) % IFADDRS_HASH_SIZE;
213            (*ifs).hash_next = (*ctx).hash[bucket];
214            (*ctx).hash[bucket] = ifs;
215        }
216    } else {
217        let payload = (hdr as *const u8).add(16) as *const IfAddrMsg;
218        let index = (*payload).index;
219        let bucket = (index as usize) % IFADDRS_HASH_SIZE;
220        let mut it = (*ctx).hash[bucket];
221        while !it.is_null() {
222            if (*it).index == index { ifs0 = it; break; }
223            it = (*it).hash_next;
224        }
225        if ifs0.is_null() { return 0; }
226        ifs = crate::calloc(1, core::mem::size_of::<IfaStorage>()) as *mut IfaStorage;
227        if ifs.is_null() { return -1; }
228        (*ifs).index = index as c_uint;
229        (*ifs).ifa.ifa_name = (*ifs0).ifa.ifa_name;
230        (*ifs).ifa.ifa_flags = (*ifs0).ifa.ifa_flags;
231        let family = (*payload).family as c_int;
232        let prefixlen = (*payload).prefixlen as c_int;
233        parse_rtattrs(hdr, 16 + core::mem::size_of::<IfAddrMsg>(), |atype, data, len| {
234            match atype {
235                IFA_ADDRESS => {
236                    if !(*ifs).ifa.ifa_addr.is_null() {
237                        copy_addr(&mut (*ifs).ifa.ifa_ifu, family, &mut (*ifs).ifu, data, len, index as c_int);
238                    } else {
239                        copy_addr(&mut (*ifs).ifa.ifa_addr, family, &mut (*ifs).addr, data, len, index as c_int);
240                    }
241                }
242                IFA_BROADCAST => {
243                    copy_addr(&mut (*ifs).ifa.ifa_ifu, family, &mut (*ifs).ifu, data, len, index as c_int);
244                }
245                IFA_LOCAL => {
246                    if !(*ifs).ifa.ifa_addr.is_null() {
247                        (*ifs).ifu = (*ifs).addr;
248                        (*ifs).ifa.ifa_ifu = &mut (*ifs).ifu as *mut SockAny as *mut c_void;
249                        (*ifs).addr = SockAny { data: [0; 64] };
250                    }
251                    copy_addr(&mut (*ifs).ifa.ifa_addr, family, &mut (*ifs).addr, data, len, index as c_int);
252                }
253                IFA_LABEL => {
254                    if len < (*ifs).name.len() {
255                        core::ptr::copy_nonoverlapping(data, (*ifs).name.as_mut_ptr() as *mut u8, len);
256                        (*ifs).name[len] = 0;
257                        (*ifs).ifa.ifa_name = (*ifs).name.as_mut_ptr();
258                    }
259                }
260                _ => {}
261            }
262        });
263        if !(*ifs).ifa.ifa_addr.is_null() {
264            gen_netmask(&mut (*ifs).ifa.ifa_netmask, family, &mut (*ifs).netmask, prefixlen);
265        }
266    }
267
268    if !(*ifs).ifa.ifa_name.is_null() {
269        if (*ctx).first.is_null() {
270            (*ctx).first = &mut (*ifs).ifa;
271        } else {
272            (*(*ctx).last).ifa_next = &mut (*ifs).ifa;
273        }
274        (*ctx).last = &mut (*ifs).ifa;
275    } else {
276        crate::free(ifs as *mut c_void);
277    }
278    0
279}
280
281unsafe fn netlink_enumerate(
282    fd: c_int,
283    seq: u32,
284    msgtype: u16,
285    cb: unsafe fn(*mut IfaCtx, *const NlMsgHdr) -> c_int,
286    ctx: *mut IfaCtx,
287) -> c_int {
288    let mut req = [0u8; 20];
289    let h = req.as_mut_ptr() as *mut NlMsgHdr;
290    (*h).len = 20;
291    (*h).type_ = msgtype;
292    (*h).flags = 0x0301; // NLM_F_DUMP | NLM_F_REQUEST
293    (*h).seq = seq;
294    (*h).pid = 0;
295    req[16] = 0; // rtgen_family = AF_UNSPEC
296    let sa = SockAddrNl { family: 16, pad: 0, pid: 0, groups: 0 };
297    let sent = crate::sendto(
298        fd,
299        req.as_ptr() as *const c_void,
300        req.len(),
301        0,
302        &sa as *const SockAddrNl as *const crate::sockaddr,
303        core::mem::size_of::<SockAddrNl>() as u32,
304    );
305    if sent < 0 { return -1; }
306    let mut buf = [0u8; 8192];
307    loop {
308        let n = crate::recv(fd, buf.as_mut_ptr() as *mut c_void, buf.len(), 0);
309        if n <= 0 { return -1; }
310        let mut off = 0usize;
311        while off + 16 <= n as usize {
312            let hdr = buf.as_ptr().add(off) as *const NlMsgHdr;
313            let len = (*hdr).len as usize;
314            if len < 16 || off + len > n as usize { break; }
315            if (*hdr).type_ == NLMSG_DONE { return 0; }
316            if (*hdr).type_ == NLMSG_ERROR { return -1; }
317            let r = cb(ctx, hdr);
318            if r != 0 { return r; }
319            off += (len + 3) & !3usize;
320        }
321    }
322}
323
324#[no_mangle]
325pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut IfAddrs) -> c_int {
326    if ifap.is_null() { ERRNO!() = EINVAL; return -1; }
327    *ifap = core::ptr::null_mut();
328    let mut ctx: IfaCtx = core::mem::zeroed();
329    let fd = crate::socket(16, 3 | 0x80000, 0); // AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC
330    if fd < 0 { return -1; }
331    let mut r = netlink_enumerate(fd, 1, RTM_GETLINK, netlink_msg_to_ifaddr, &mut ctx);
332    if r == 0 {
333        r = netlink_enumerate(fd, 2, RTM_GETADDR, netlink_msg_to_ifaddr, &mut ctx);
334    }
335    crate::close(fd);
336    if r == 0 {
337        *ifap = ctx.first;
338    } else {
339        freeifaddrs(ctx.first);
340    }
341    r
342}
343
344#[no_mangle]
345pub unsafe extern "C" fn freeifaddrs(mut ifa: *mut IfAddrs) {
346    while !ifa.is_null() {
347        let next = (*ifa).ifa_next;
348        crate::free(ifa as *mut c_void);
349        ifa = next;
350    }
351}
352
353// ------------------------------------------------------------------
354// Interface names via /sys/class/net
355// ------------------------------------------------------------------
356
357unsafe fn sys_read_ifindex(name: *const c_char) -> u32 {
358    let mut path = [0 as c_char; 128];
359    let mut o = 0usize;
360    for &c in b"/sys/class/net/" { path[o] = c as c_char; o += 1; }
361    let n = crate::strlen(name);
362    if o + n + 12 >= path.len() { return 0; }
363    core::ptr::copy_nonoverlapping(name, path.as_mut_ptr().add(o), n);
364    o += n;
365    for &c in b"/ifindex\0" { path[o] = c as c_char; o += 1; }
366    let fd = crate::open(path.as_ptr(), 0, 0);
367    if fd < 0 { return 0; }
368    let mut buf = [0u8; 32];
369    let n = crate::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len() - 1);
370    crate::close(fd);
371    if n <= 0 { return 0; }
372    buf[n as usize] = 0;
373    crate::strtoul(buf.as_ptr() as *const c_char, core::ptr::null_mut(), 10) as u32
374}
375
376#[no_mangle]
377pub unsafe extern "C" fn if_nametoindex(ifname: *const c_char) -> c_uint {
378    if ifname.is_null() { return 0; }
379    sys_read_ifindex(ifname)
380}
381
382#[repr(align(8))]
383struct DirentBuf([u8; 4096]);
384
385unsafe fn list_net_names<F: FnMut(*const c_char, usize)>(mut cb: F) {
386    let fd = crate::open(b"/sys/class/net\0".as_ptr() as *const c_char, crate::O_DIRECTORY, 0);
387    if fd < 0 { return; }
388    let mut buf = DirentBuf([0u8; 4096]);
389    let name_off = core::mem::offset_of!(linux_dirent64, d_name);
390    loop {
391        let n = <Arch as Syscalls>::syscall3(
392            SYS_GETDENTS64,
393            fd as i64,
394            buf.0.as_mut_ptr() as i64,
395            buf.0.len() as i64,
396        ) as isize;
397        if n <= 0 { break; }
398        let mut off = 0usize;
399        while off + name_off < n as usize {
400            let ent = buf.0.as_ptr().add(off) as *const linux_dirent64;
401            let reclen = (*ent).d_reclen as usize;
402            if reclen == 0 { break; }
403            let name = buf.0.as_ptr().add(off + name_off) as *const c_char;
404            let len = crate::strlen(name);
405            if len > 0 && *name != b'.' as c_char {
406                cb(name, len);
407            }
408            off += reclen;
409        }
410    }
411    crate::close(fd);
412}
413
414#[no_mangle]
415pub unsafe extern "C" fn if_indextoname(ifindex: c_uint, ifname: *mut c_char) -> *mut c_char {
416    if ifindex == 0 || ifname.is_null() { ERRNO!() = 19; return core::ptr::null_mut(); }
417    let mut found = false;
418    list_net_names(|name, len| {
419        if found { return; }
420        if sys_read_ifindex(name) == ifindex {
421            core::ptr::copy_nonoverlapping(name, ifname, len + 1);
422            found = true;
423        }
424    });
425    if found { ifname } else { ERRNO!() = 19; core::ptr::null_mut() }
426}
427
428#[repr(C)]
429pub struct IfNameIndex {
430    pub if_index: c_uint,
431    pub if_name: *mut c_char,
432}
433
434struct NameList {
435    arr: *mut IfNameIndex,
436    count: usize,
437    cap: usize,
438}
439
440#[no_mangle]
441pub unsafe extern "C" fn if_nameindex() -> *mut IfNameIndex {
442    let mut list = NameList {
443        arr: core::ptr::null_mut(),
444        count: 0,
445        cap: 0,
446    };
447    list.arr = crate::calloc(8, core::mem::size_of::<IfNameIndex>()) as *mut IfNameIndex;
448    if list.arr.is_null() { return core::ptr::null_mut(); }
449    list.cap = 8;
450    list_net_names(|name, len| {
451        let idx = sys_read_ifindex(name);
452        if idx == 0 { return; }
453        if list.count + 1 >= list.cap {
454            let ncap = list.cap * 2;
455            let na = crate::realloc(list.arr as *mut c_void, ncap * core::mem::size_of::<IfNameIndex>()) as *mut IfNameIndex;
456            if na.is_null() { return; }
457            list.arr = na;
458            list.cap = ncap;
459        }
460        let copy = crate::malloc(len + 1) as *mut c_char;
461        if copy.is_null() { return; }
462        core::ptr::copy_nonoverlapping(name, copy, len + 1);
463        (*list.arr.add(list.count)).if_index = idx;
464        (*list.arr.add(list.count)).if_name = copy;
465        list.count += 1;
466    });
467    if list.count == 0 {
468        crate::free(list.arr as *mut c_void);
469        return core::ptr::null_mut();
470    }
471    (*list.arr.add(list.count)).if_index = 0;
472    (*list.arr.add(list.count)).if_name = core::ptr::null_mut();
473    list.arr
474}
475
476#[no_mangle]
477pub unsafe extern "C" fn if_freenameindex(mut ptr: *mut IfNameIndex) {
478    if ptr.is_null() { return; }
479    let base = ptr;
480    while (*ptr).if_index != 0 && !(*ptr).if_name.is_null() {
481        crate::free((*ptr).if_name as *mut c_void);
482        ptr = ptr.add(1);
483    }
484    crate::free(base as *mut c_void);
485}