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>.
|
//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getifaddrs.3.html>.
|
||||||
|
|
||||||
use crate::{
|
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::{
|
platform::{
|
||||||
self,
|
self,
|
||||||
types::{c_char, c_int, c_uint, c_void},
|
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
|
/// Either the broadcast address associated with `ifa_addr` (if applicable
|
||||||
/// for the address family) or the destination address of the
|
/// for the address family) or the destination address of the
|
||||||
/// point-to-point interface.
|
/// 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.
|
/// be freed using `freeifaddrs()` when no longer needed.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int {
|
pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int {
|
||||||
//TODO: implement getifaddrs
|
if ifap.is_null() {
|
||||||
platform::ERRNO.set(errno::ENOSYS);
|
platform::ERRNO.set(errno::EINVAL);
|
||||||
-1
|
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