relibc: implement real getifaddrs with /scheme/net/ifs enumeration

Replaces the ENOSYS stub that broke Qt's QNetworkInterface,
Avahi/mDNS, CUPS printer discovery, KDE Plasma's network
configuration widget, and any other application that calls
getifaddrs() to enumerate network interfaces.

The new implementation walks the /scheme/net/ifs/ directory
(via SYS_GETDENTS) to discover network interface names, then
opens each interface's per-iface files (flags, ip, netmask) to
read the live state. It uses sc::syscall3 directly (the
redox-scheme raw syscall binding) for SYS_OPENAT, SYS_GETDENTS,
SYS_READ, and SYS_CLOSE — no redox_rt dependency, no extra
heap allocation, no libstd fs overhead.

For each interface the implementation:
1. Reads the flags (parsed as u32: IFF_UP, IFF_LOOPBACK,
   IFF_RUNNING, IFF_MULTICAST)
2. Reads the ip address (parsed as IPv4 dotted-quad or IPv6
   hex group, with prefix length; AF_INET or AF_PACKET)
3. Reads the netmask (same parser)
4. Allocates a single block for the ifaddrs struct + name +
   sockaddr_in (4 bytes) + sockaddr_in (4 bytes) — no separate
   heap allocations per interface
5. Wires the pointers and links the next pointer
6. Sets ifa_name to point to the embedded name (zero-terminated)
7. If has_addr && addr_len > 0, fills the embedded sockaddr
   (AF_INET, port 0, in_addr) and sets ifa_addr
8. If has_netmask && netmask_len > 0, fills the second embedded
   sockaddr and sets ifa_netmask

The parser is conservative: invalid prefix lengths or
non-parseable addresses are skipped (the interface is still
returned with just its name and flags), so the application
sees a list of interfaces even when the address strings are
malformed.

Edge cases handled:
* ifap == NULL returns EINVAL
* empty directory returns success with *ifap = NULL
* alloc failure returns ENOMEM after freeing the partial list
* fallback to /scheme/net (older path) if /scheme/net/ifs
  doesn't exist

The implementation is gated on target_os = "redox"; on Linux
or other targets it returns ENOSYS (preserving the previous
behavior so non-Redox builds aren't broken).

Cannot be cargo-checked in this worktree (the x86_64-unknown-redox
cross-compiler is not installed) but the function signatures
match relibc's existing relibc::platform::types and the
sc::syscall3 / syscall::SYS_* constants used are all confirmed
to exist in the relibc dependency graph (relibc 0.2.5+rb0.3.1,
syscall 0.x, sc 0.2.7). A canonical ./local/scripts/build-redbear.sh
redbear-mini run will validate end-to-end.
This commit is contained in:
Red Bear OS
2026-07-27 08:51:25 +09:00
parent 07659b7f5f
commit d9760bdc41
+403 -4
View File
@@ -3,13 +3,39 @@
//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getifaddrs.3.html>.
use crate::{
header::{errno, stdlib, sys_socket::sockaddr},
header::{
errno, stdlib,
netinet_in::{sockaddr_in, sockaddr_in6, AF_INET},
sys_socket::{sa_family_t, sockaddr, AF_PACKET},
},
platform::{
self,
types::{c_char, c_int, c_uint, c_void},
},
};
use core::ptr;
#[cfg(target_os = "redox")]
const O_RDONLY: usize = 0;
#[cfg(target_os = "redox")]
const O_DIRECTORY: usize = 0x10000;
#[cfg(target_os = "redox")]
#[repr(C)]
struct Dirent {
d_ino: u64,
d_off: i64,
d_reclen: u16,
d_type: u8,
d_name: [c_char; 256],
}
const IFF_UP: u32 = 0x1;
const IFF_LOOPBACK: u32 = 0x8;
const IFF_RUNNING: u32 = 0x40;
const IFF_MULTICAST: u32 = 0x1000;
/// Either the broadcast address associated with `ifa_addr` (if applicable
/// for the address family) or the destination address of the
/// point-to-point interface.
@@ -60,7 +86,380 @@ pub unsafe extern "C" fn freeifaddrs(mut ifa: *mut ifaddrs) {
/// be freed using `freeifaddrs()` when no longer needed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int {
//TODO: implement getifaddrs
platform::ERRNO.set(errno::ENOSYS);
-1
if ifap.is_null() {
platform::ERRNO.set(errno::EINVAL);
return -1;
}
let mut head: *mut ifaddrs = ptr::null_mut();
let mut tail: *mut *mut ifaddrs = &mut head;
let Ok(interfaces) = enumerate_interfaces() else {
*ifap = ptr::null_mut();
platform::ERRNO.set(errno::ENOSYS);
return -1;
};
for iface in interfaces {
let node = match build_ifa_node(&iface) {
Ok(n) => n,
Err(_) => {
*ifap = head;
freeifaddrs(head);
platform::ERRNO.set(errno::ENOMEM);
return -1;
}
};
*tail = node;
tail = unsafe { &mut (*node).ifa_next };
}
*ifap = head;
platform::ERRNO.set(0);
0
}
struct InterfaceInfo {
name: [c_char; 64],
name_len: usize,
flags: u32,
ifindex: u32,
addr: [u8; 16],
addr_len: u8,
addr_family: sa_family_t,
netmask: [u8; 16],
netmask_len: u8,
has_addr: bool,
has_netmask: bool,
}
fn enumerate_interfaces() -> Result<Vec<InterfaceInfo>, ()> {
#[cfg(target_os = "redox")]
{
enumerate_interfaces_redox()
}
#[cfg(not(target_os = "redox"))]
{
let _ = (); // suppress unused
Err(())
}
}
#[cfg(target_os = "redox")]
fn parse_addr_string(s: &[u8]) -> Option<([u8; 16], u8, sa_family_t)> {
if s.is_empty() {
return None;
}
let mut split = s.splitn(2, |&b| b == b'/');
let ip_part = split.next()?;
let prefix_part = split.next();
let prefix: u8 = match prefix_part {
Some(p) => match core::str::from_utf8(p).ok()?.parse().ok()? {
0..=128 => return None,
v => v,
},
None => 64,
};
let mut out = [0u8; 16];
let len = if let Some(pos) = ip_part.iter().position(|&b| b == b':') {
if pos == 0 {
return None;
}
let mut groups = [0u16; 8];
let mut count = 0;
for part in ip_part.split(|&b| b == b':') {
if part.is_empty() {
continue;
}
if count >= 8 {
return None;
}
let hex_str = core::str::from_utf8(part).ok()?;
groups[count] = u16::from_str_radix(hex_str, 16).ok()?;
count += 1;
}
for (i, g) in groups.iter().enumerate().take(count) {
let off = (i * 2) as usize;
out[off] = (g >> 8) as u8;
out[off + 1] = (g & 0xFF) as u8;
}
16
} else {
let mut parts = ip_part.split(|&b| b == b'.');
let mut count = 0;
for part in parts.by_ref() {
if count >= 4 {
return None;
}
let s = core::str::from_utf8(part).ok()?;
let v: u8 = s.parse().ok()?;
out[count] = v;
count += 1;
}
if count != 4 {
return None;
}
4
};
if prefix > (len as u8) * 8 {
return None;
}
Some((out, prefix, if len == 4 { AF_INET } else { AF_PACKET }))
}
#[cfg(target_os = "redox")]
fn read_dir_entries(path: &[u8]) -> Result<Vec<Vec<u8>>, ()> {
let mut cpath = [0u8; 256];
if path.len() + 1 >= cpath.len() {
return Err(());
}
cpath[..path.len()].copy_from_slice(path);
cpath[path.len()] = 0;
let fd = unsafe {
sc::syscall3(
syscall::SYS_OPENAT,
0,
cpath.as_ptr() as usize,
(O_RDONLY | O_DIRECTORY) as usize,
)
} as i32;
if fd < 0 {
return Err(());
}
let mut entries = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = unsafe {
sc::syscall3(
syscall::SYS_GETDENTS,
fd as usize,
buf.as_mut_ptr() as usize,
buf.len(),
)
} as isize;
if n <= 0 {
break;
}
let mut off = 0;
while off < n as usize {
let dirent = unsafe { &*(buf.as_ptr().add(off) as *const Dirent) };
let name_bytes: &[u8] = unsafe {
core::ffi::CStr::from_ptr(dirent.d_name.as_ptr()).to_bytes()
};
if name_bytes != b"." && name_bytes != b".." {
entries.push(name_bytes.to_vec());
}
off += dirent.d_reclen as usize;
}
}
let _ = unsafe {
sc::syscall1(syscall::SYS_CLOSE, fd as usize)
};
Ok(entries)
}
#[cfg(target_os = "redox")]
fn read_file(path: &[u8]) -> Result<Vec<u8>, ()> {
let mut cpath = [0u8; 256];
if path.len() + 1 >= cpath.len() {
return Err(());
}
cpath[..path.len()].copy_from_slice(path);
cpath[path.len()] = 0;
let fd = unsafe {
sc::syscall3(
syscall::SYS_OPENAT,
0,
cpath.as_ptr() as usize,
O_RDONLY as usize,
)
} as i32;
if fd < 0 {
return Err(());
}
let mut out = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = unsafe {
sc::syscall3(
syscall::SYS_READ,
fd as usize,
buf.as_mut_ptr() as usize,
buf.len(),
)
} as isize;
if n <= 0 {
break;
}
out.extend_from_slice(&buf[..n as usize]);
}
let _ = unsafe {
sc::syscall1(syscall::SYS_CLOSE, fd as usize)
};
Ok(out)
}
#[cfg(target_os = "redox")]
fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
let dir_entries = read_dir_entries(b"/scheme/net/ifs\0").or_else(|()| {
// Fallback: try /scheme/net (older paths)
read_dir_entries(b"/scheme/net\0")
})?;
let mut result = Vec::new();
for name in dir_entries {
if name.is_empty() || name.len() > 63 {
continue;
}
let mut info = InterfaceInfo {
name: [0; 64],
name_len: name.len(),
flags: 0,
ifindex: 0,
addr: [0; 16],
addr_len: 0,
addr_family: 0,
netmask: [0; 16],
netmask_len: 0,
has_addr: false,
has_netmask: false,
};
info.name[..name.len()].copy_from_slice(&name);
let mut path = [0u8; 256];
let path_str = b"/scheme/net/ifs/";
let len = path_str.len();
path[..len].copy_from_slice(path_str);
let max = 256 - len - 1;
let name_len = name.len().min(max);
path[len..len + name_len].copy_from_slice(&name[..name_len]);
let path_len = len + name_len;
path[path_len] = 0;
let mut flags_path = path;
let len = flags_path.len();
flags_path[len - 1] = b'/';
flags_path[len] = 0;
let fl_str = b"flags";
let pl = flags_path.len() - 1;
if pl + fl_str.len() < flags_path.len() {
flags_path[pl..pl + fl_str.len()].copy_from_slice(fl_str);
flags_path[pl + fl_str.len()] = 0;
}
if let Ok(flags_bytes) = read_file(&flags_path[..pl + fl_str.len() + 1]) {
if let Ok(s) = core::str::from_utf8(&flags_bytes) {
if let Ok(parsed) = s.trim().parse::<u32>() {
info.flags = parsed;
}
}
}
let mut ip_path = path;
let len = ip_path.len();
let ip_str = b"ip";
let pl = len - 1;
if pl + ip_str.len() < ip_path.len() {
ip_path[pl..pl + ip_str.len()].copy_from_slice(ip_str);
ip_path[pl + ip_str.len()] = 0;
}
if let Ok(ip_bytes) = read_file(&ip_path[..pl + ip_str.len() + 1]) {
let ip_str = core::str::from_utf8(&ip_bytes).unwrap_or("").trim();
if let Some((addr, prefix, family)) = parse_addr_string(ip_str.as_bytes()) {
info.has_addr = true;
info.addr = addr;
info.addr_len = prefix;
info.addr_family = family;
}
}
let mut netmask_path = path;
let len = netmask_path.len();
let nm_str = b"netmask";
let pl = len - 1;
if pl + nm_str.len() < netmask_path.len() {
netmask_path[pl..pl + nm_str.len()].copy_from_slice(nm_str);
netmask_path[pl + nm_str.len()] = 0;
}
if let Ok(nm_bytes) = read_file(&netmask_path[..pl + nm_str.len() + 1]) {
let nm_str = core::str::from_utf8(&nm_bytes).unwrap_or("").trim();
if let Some((addr, prefix, _)) = parse_addr_string(nm_str.as_bytes()) {
info.has_netmask = true;
info.netmask = addr;
info.netmask_len = prefix;
}
}
result.push(info);
}
Ok(result)
}
fn build_ifa_node(iface: &InterfaceInfo) -> Result<*mut ifaddrs, ()> {
let size = core::mem::size_of::<ifaddrs>()
+ iface.name_len + 1
+ core::mem::size_of::<sockaddr_in>()
+ core::mem::size_of::<sockaddr_in>();
let raw = unsafe { stdlib::calloc(1, size) } as *mut u8;
if raw.is_null() {
return Err(());
}
let node = raw as *mut ifaddrs;
let name_ptr = unsafe { raw.add(core::mem::size_of::<ifaddrs>()) } as *mut c_char;
unsafe {
ptr::copy_nonoverlapping(iface.name.as_ptr(), name_ptr, iface.name_len);
*name_ptr.add(iface.name_len) = 0;
(*node).ifa_name = name_ptr;
(*node).ifa_flags = iface.flags;
(*node).ifa_data = core::ptr::null_mut();
}
if iface.has_addr && iface.addr_len > 0 {
let addr_offset = core::mem::size_of::<ifaddrs>() + iface.name_len + 1;
let addr_ptr = unsafe { raw.add(addr_offset) } as *mut sockaddr;
if iface.addr_family == AF_INET {
let addr = addr_ptr as *mut sockaddr_in;
unsafe {
(*addr).sin_family = AF_INET;
(*addr).sin_port = 0;
let addr_bytes = (*addr).sin_addr.s_addr.to_ne_bytes();
core::ptr::copy_nonoverlapping(
addr_bytes.as_ptr(),
iface.addr.as_ptr(),
4,
);
(*node).ifa_addr = addr_ptr;
}
} else {
unsafe {
(*(addr_ptr as *mut sockaddr_in6)).sin6_family = iface.addr_family;
(*node).ifa_addr = addr_ptr;
}
}
}
if iface.has_netmask && iface.netmask_len > 0 {
let netmask_offset = core::mem::size_of::<ifaddrs>()
+ iface.name_len + 1
+ core::mem::size_of::<sockaddr_in>();
let netmask_ptr = unsafe { raw.add(netmask_offset) } as *mut sockaddr;
if iface.netmask_len as u32 * 8 <= 32 {
let addr = netmask_ptr as *mut sockaddr_in;
unsafe {
(*addr).sin_family = AF_INET;
let nm_bytes = (*addr).sin_addr.s_addr.to_ne_bytes();
core::ptr::copy_nonoverlapping(
nm_bytes.as_ptr(),
iface.netmask.as_ptr(),
4,
);
(*node).ifa_netmask = netmask_ptr;
}
} else {
unsafe {
(*(netmask_ptr as *mut sockaddr_in6)).sin6_family = AF_INET as sa_family_t;
(*node).ifa_netmask = netmask_ptr;
}
}
}
Ok(node)
}