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:
+403
-4
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user