Files
RedBear-OS/src/header/arpa_inet/mod.rs
T
vasilito 1fb1638615 relibc: round 17 — IPv6 inet_ntop + netinet/ip.h + getaddrinfo hints + FIONREAD + accept4
Per NETWORKING-AND-DRIVERS-CODE-ASSESSMENT-2026-07-27.md §15
Tier A + Tier B quick wins (Q3/Q4/Q5/Q6/Q2):

- arpa_inet::inet_ntop: add IPv6 branch (RFC 5952). Real
  longest-run '::' compression with tie-break; rejects
  AFs other than INET/INET6; proper ENOSPC on size<46.
  9 unit tests cover unspecified, loopback, routable,
  link-local, no-compression, tie-break, trailing-run,
  size-too-small, and unsupported-family cases.

- netinet_ip: populate the 3-line skeleton with the
  glibc netinet/ip.h constant set (IP_OPTIONS, IP_TTL,
  IP_HDRINCL, IP_RECVOPTS, IP_RECVDSTADDR, IP_PKTINFO,
  IP_RECVPKTINFO, IP_MINTTL, IP_RECVERR, IP_DONTFRAG,
  IP_PMTUDISC_*, IP_BOUND_IF, IP_ORIGDSTADDR, etc.) plus
  struct ip (BSD) and struct iphdr (Linux) and four
  accessors. 5 unit tests cover roundtrip / alignment /
  constant values / PMTUDISC distinctness.

- netdb::lookup: add lookup_host_v6 (AAAA DNS query type
  0x001c, 16-byte answer parse) and parse_ipv6_string
  delegating to inet_pton(AF_INET6, ...). Shared
  lookup_host_query helper for A + AAAA. lookup_host
  refactored to share the query path.

- netdb::getaddrinfo: full POSIX hints implementation.
  Honors ai_family (AF_UNSPEC / AF_INET / AF_INET6),
  ai_socktype, ai_protocol. Validates flag combinations
  (EAI_BADFLAGS for unknown bits, AI_NUMERICSERV with
  no service, AI_CANONNAME with no node). Implements
  AI_NUMERICHOST (v4 and v6), AI_NUMERICSERV (with
  numeric and getservbyname fallback), AI_PASSIVE
  (0.0.0.0 vs 127.0.0.1 default), AI_CANONNAME (sets
  canonname on first emitted entry), AI_V4MAPPED
  (synthesize ::ffff:a.b.c.d from A results when no
  AAAA and AF_INET6 requested). Emits IPv6 entries with
  proper sockaddr_in6 (16 bytes) when v6 results exist.
  9 unit tests cover numeric-v4, socktype/protocol
  filter, unknown-flag rejection, canonname-without-node
  rejection, numerichost+unparseable, numericserv
  rejection, numericserv+numeric success, IPv6 parse
  roundtrip, garbage rejection, and IPv4-mapped-IPv6
  construction.

- sys_ioctl::ioctl_inner: add FIONREAD handler that
  attempts dup_read(fd, 'fionread', &count). Returns
  ENOTTY if the scheme does not advertise a fionread
  path (consistent with Linux's TIOCNOTTY behavior for
  unsupported ioctls).

- PalSocket::accept4: new trait method. Linux impl uses
  __NR_accept4 (288 on x86_64, 242 on aarch64) via
  sc::syscall5 with EINVAL on unknown flag bits. Redox
  impl falls back to accept() + fcntl() for SOCK_CLOEXEC
  / SOCK_NONBLOCK (mirrors the Linux 2.6.27 pre-syscall
  situation; the race is documented). Public C-callable
  accept4 added in header/sys_socket/mod.rs. Closes
  the #1 gap for Qt / glib compatibility.

Three pre-existing errors in ld_so/dso.rs and ld_so/linker.rs
remain in the working tree but are unrelated to this commit.
2026-07-27 23:00:28 +09:00

578 lines
18 KiB
Rust

//! `arpa/inet.h` implementation.
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/arpa_inet.h.html>.
use core::{
ptr, slice,
str::{self, FromStr},
};
use crate::{
c_str::CStr,
header::{
bits_arpainet::ntohl,
errno::{EAFNOSUPPORT, ENOSPC},
netinet_in::{INADDR_NONE, in_addr, in_addr_t, in6_addr},
sys_socket::{
constants::{AF_INET, AF_INET6},
socklen_t,
},
},
io::Write,
platform::{
self,
types::{c_char, c_int, c_void},
},
raw_cell::RawCell,
};
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/inet_addr.html>.
///
/// Converts the string pointed to by `cp`, in the standard IPv4 dotted
/// decimal notation, to an integer value suitable for use as an Internet
/// address.
///
/// # Deprecated
/// The `inet_addr()` function was marked obsolescent in the Open Group Base
/// Specifications Issue 8.
///
/// Applications should prefer `inet_pton()` over `inet_addr()` for the
/// following reasons:
/// - The return value from `inet_addr()` when converting 255.255.255.255 is
/// indistinguishable from an error.
/// - The `inet_pton()` function supports multiple address families.
/// - The alternative textual representations supported by `inet_addr()` (but
/// not `inet_pton()`) are often used maliciously to confuse or mislead
/// users (e.g, for phishing).
#[deprecated]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_addr(cp: *const c_char) -> in_addr_t {
let mut val: in_addr = in_addr { s_addr: 0 };
if unsafe { inet_aton(cp, &raw mut val) } > 0 {
val.s_addr
} else {
INADDR_NONE
}
}
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/inet_aton.3.html>.
///
/// Converts the Internet host address `cp` from the IPv4 numbers-and-dots
/// notation into binary form (in network byte order) and stores it in the
/// structure that `inp` points to.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_int {
let cp_cstr = unsafe { CStr::from_ptr(cp) };
let parts = unsafe { str::from_utf8_unchecked(cp_cstr.to_bytes()).split('.') };
let count = parts.clone().count();
if count > 4 {
return 0;
}
let mut result = 0;
let mut parts_iter = parts.peekable();
let mut index = 0u32;
while index < 4
&& let Some(part) = parts_iter.next()
{
if let Ok(parsed_value) = {
if let Some(hex_or_oct) = part.strip_prefix('0')
&& part.len() > 1
{
match hex_or_oct.bytes().next() {
Some(b'x' | b'X') => u32::from_str_radix(&hex_or_oct[1..], 16),
// While it is true that C2Y accepts 0o and 0O as octal prefixes, C17 doesn't
// The POSIX spec defers to C17
// see https://pubs.opengroup.org/onlinepubs/9799919799/functions/inet_addr.html
_ => u32::from_str_radix(hex_or_oct, 8),
}
} else {
part.parse::<u32>()
}
} {
if parts_iter.peek().is_some() {
// this is not the last part
if parsed_value > 0xff {
return 0;
}
result += parsed_value << (24 - index * 8);
} else {
// this is the last part
if index > 0 && parsed_value >= 1 << (32 - index * 8) {
return 0;
} else {
result += parsed_value;
}
}
} else {
return 0;
}
index += 1;
}
unsafe { (*inp.cast::<in_addr>()).s_addr = result.to_be() };
1
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xns/inet_lnaof.html>.
///
/// Takes an Internet host address specified by `in` and extracts the local
/// network address part, in host byte order.
///
/// # Deprecation
/// The `inet_lnaof()` function was specified in Networking Services Issue 5,
/// but not in the Open Group Base Specifications Issue 6 and later.
#[deprecated]
#[unsafe(no_mangle)]
pub extern "C" fn inet_lnaof(r#in: in_addr) -> in_addr_t {
if r#in.s_addr >> 24 < 128 {
r#in.s_addr & 0xff_ffff
} else if r#in.s_addr >> 24 < 192 {
r#in.s_addr & 0xffff
} else {
r#in.s_addr & 0xff
}
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xns/inet_makeaddr.html>.
///
/// Takes the Internet network number specified by `net` and the local network
/// address specified by `lna`, both in host byte order, and constructs an
/// Internet address from them.
///
/// # Deprecation
/// The `inet_makeaddr()` function was specified in Networking Services Issue
/// 5, but not in the Open Group Base Specifications Issue 6 and later.
#[deprecated]
#[unsafe(no_mangle)]
pub extern "C" fn inet_makeaddr(net: in_addr_t, lna: in_addr_t) -> in_addr {
let mut output: in_addr = in_addr { s_addr: 0 };
if net < 256 {
output.s_addr = lna | net << 24;
} else if net < 65536 {
output.s_addr = lna | net << 16;
} else {
output.s_addr = lna | net << 8;
}
output
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xns/inet_netof.html>.
///
/// Takes an Internet host address specified by `in` and extracts the network
/// number part, in host byte order.
///
/// # Deprecation
/// The `inet_netof()` function was specified in Networking Services Issue 5,
/// but not in the Open Group Base Specifications Issue 6 and later.
#[deprecated]
#[unsafe(no_mangle)]
pub extern "C" fn inet_netof(r#in: in_addr) -> in_addr_t {
if r#in.s_addr >> 24 < 128 {
r#in.s_addr & 0xff_ffff
} else if r#in.s_addr >> 24 < 192 {
r#in.s_addr & 0xffff
} else {
r#in.s_addr & 0xff
}
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xns/inet_network.html>.
///
/// Converts the string pointed to by `cp`, in the Internet standard dot
/// notation, to an integer value suitable for use as an Internet network
/// number.
///
/// # Deprecation
/// The `inet_network()` function was specified in Networking Services Issue 5,
/// but not in the Open Group Base Specifications Issue 6 and later.
#[deprecated]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_network(cp: *const c_char) -> in_addr_t {
ntohl(unsafe {
#[allow(deprecated)]
inet_addr(cp)
})
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/inet_addr.html>.
///
/// Converts the Internet host address specified by `in` to a string in the
/// Internet standard dot notation.
///
/// # Deprecation
/// The `inet_ntoa()` function was marked obsolescent in the Open Group Base
/// Specifications Issue 8.
///
/// Applications should prefer `inet_ntop()` over `inet_ntoa()` as it supports
/// multiple address families and is thread-safe.
#[deprecated]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_ntoa(r#in: in_addr) -> *mut c_char {
static NTOA_ADDR: RawCell<[c_char; 16]> = RawCell::new([0; 16]);
unsafe {
let ptr = inet_ntop(
AF_INET,
ptr::from_ref::<in_addr>(&r#in).cast::<c_void>(),
NTOA_ADDR.unsafe_mut().as_mut_ptr(),
NTOA_ADDR.unsafe_ref().len() as socklen_t,
);
// Mutable pointer is required, inet_ntop returns destination as const pointer
ptr.cast_mut()
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/inet_ntop.html>.
///
/// Converts a numeric address into a text string suitable for presentation.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_ntop(
af: c_int,
src: *const c_void,
dst: *mut c_char,
size: socklen_t,
) -> *const c_char {
if af == AF_INET {
if (size as usize) < 16 {
platform::ERRNO.set(ENOSPC);
return ptr::null();
}
let s_addr = unsafe {
slice::from_raw_parts(
ptr::from_ref(&(*(src.cast::<in_addr>())).s_addr).cast::<u8>(),
4,
)
};
let mut w = platform::StringWriter(dst, size as usize);
let _ = write!(
w,
"{}.{}.{}.{}\0",
s_addr[0], s_addr[1], s_addr[2], s_addr[3]
);
dst
} else if af == AF_INET6 {
// IPv6 textual representation per RFC 5952:
// - 8 groups of 4 lowercase hex digits separated by colons
// - Longest run of consecutive all-zero groups compressed to "::"
// - Ties broken by first occurrence
// - Single zero group is NOT compressed
// INET6_ADDRSTRLEN == 46 (39 chars + trailing NUL + slack)
if (size as usize) < crate::header::netinet_in::INET6_ADDRSTRLEN as usize {
platform::ERRNO.set(ENOSPC);
return ptr::null();
}
let bytes = unsafe {
slice::from_raw_parts(
ptr::from_ref(&(*(src.cast::<in6_addr>())).s6_addr).cast::<u8>(),
16,
)
};
let mut groups = [0u16; 8];
for (i, g) in groups.iter_mut().enumerate() {
*g = u16::from_be_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
}
let mut best_start = usize::MAX;
let mut best_len = 0usize;
let mut cur_start = usize::MAX;
let mut cur_len = 0usize;
for i in 0..8 {
if groups[i] == 0 {
if cur_start == usize::MAX {
cur_start = i;
cur_len = 1;
} else {
cur_len += 1;
}
if cur_len > best_len {
best_len = cur_len;
best_start = cur_start;
}
} else {
cur_start = usize::MAX;
cur_len = 0;
}
}
if best_len < 2 {
best_start = usize::MAX;
}
let mut w = platform::StringWriter(dst, size as usize);
let mut i = 0usize;
let mut after_ellipsis = false;
while i < 8 {
if i == best_start {
let _ = write!(w, "::");
after_ellipsis = true;
i += best_len;
continue;
}
if !after_ellipsis && i > 0 {
let _ = write!(w, ":");
}
let _ = write!(w, "{:x}", groups[i]);
after_ellipsis = false;
i += 1;
}
let _ = write!(w, "\0");
dst
} else {
platform::ERRNO.set(EAFNOSUPPORT);
ptr::null()
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/inet_ntop.html>.
///
/// Converts an address in its standard text presentation form into its
/// numeric binary form.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn inet_pton(af: c_int, src: *const c_char, dst: *mut c_void) -> c_int {
if af == AF_INET {
let s_addr = unsafe {
slice::from_raw_parts_mut(
ptr::from_mut(&mut (*dst.cast::<in_addr>()).s_addr).cast::<u8>(),
4,
)
};
let src_cstr = unsafe { CStr::from_ptr(src) };
let mut octets = unsafe { str::from_utf8_unchecked(src_cstr.to_bytes()).split('.') };
for part in s_addr.iter_mut().take(4) {
if let Some(n) = octets
.next()
.filter(|x| x.len() <= 3)
.and_then(|x| u8::from_str(x).ok())
{
*part = n;
} else {
return 0;
}
}
if octets.next().is_none() {
1 // Success
} else {
0
}
} else if af == AF_INET6 {
let src_str = unsafe { str::from_utf8_unchecked(CStr::from_ptr(src).to_bytes()) };
let mut chunks = vec![src_str];
let colons = src_str.bytes().filter(|&c| c == b':').count();
if !(2..=7).contains(&colons) {
return 0;
}
let dots = src_str.bytes().filter(|&c| c == b'.').count();
if dots != 0 && dots != 3 {
return 0;
}
let double_colon = src_str.find("::");
if colons < 2
|| double_colon.is_some() && ((dots == 0 && colons > 7) || (dots == 3 && colons > 6))
|| double_colon.is_none() && ((dots == 0 && colons != 7) || (dots == 3 && colons != 6))
{
return 0;
}
if dots == 3
&& let Some(first_dot) = src_str.find('.')
&& let Some(last_colon) = src_str.find(':')
&& last_colon > first_dot
{
return 0;
}
if let Some(first) = src_str.find("::")
&& let Some(last) = src_str.rfind("::")
{
// :: is allowed only once
if first != last {
return 0;
}
chunks = vec![&src_str[..first], &src_str[(first + 2)..]]
}
let s6_addr = unsafe {
slice::from_raw_parts_mut(
ptr::from_mut(&mut (*dst.cast::<in6_addr>()).s6_addr).cast::<u16>(),
8,
)
};
s6_addr.iter_mut().for_each(|w| *w = 0);
for (count, &chunk) in chunks
.iter()
.enumerate()
.filter(|&(_, &chunk)| !chunk.is_empty())
{
let mut parts = s6_addr
.iter_mut()
.skip(count * (8 - chunk.split(':').count() - dots / 3));
let mut words = chunk.split(':');
while let Some(word) = words.next()
&& let Some(part) = parts.next()
{
if word.is_empty() {
break;
} else if word.len() <= 4
&& let Some(n) = u16::from_str_radix(word, 16).ok()
{
*part = n.to_be();
} else if word.contains('.') {
let bytes =
unsafe { slice::from_raw_parts_mut(ptr::from_mut(part).cast::<u8>(), 4) };
let mut octets = word.split('.');
for byte in bytes {
if let Some(octet) = octets.next() {
if octet.len() > 1 && octet.starts_with('0') {
return 0;
}
if let Ok(value) = u8::from_str(octet) {
*byte = value
} else {
return 0;
}
}
}
} else {
return 0;
}
}
}
1 // Success
} else {
platform::ERRNO.set(EAFNOSUPPORT);
-1
}
}
#[cfg(test)]
mod tests {
use super::*;
fn format_v6(bytes: [u8; 16]) -> Option<alloc::string::String> {
let addr = in6_addr { s6_addr: bytes };
let mut buf = [0u8; 64];
let ret = unsafe {
inet_ntop(
AF_INET6,
ptr::from_ref(&addr).cast::<c_void>(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len() as socklen_t,
)
};
if ret.is_null() {
None
} else {
let nul = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
core::str::from_utf8(&buf[..nul])
.ok()
.map(|s| alloc::string::String::from(s))
}
}
#[test]
fn inet_ntop_v6_unspecified() {
assert_eq!(format_v6([0u8; 16]).as_deref(), Some("::"));
}
#[test]
fn inet_ntop_v6_loopback() {
let mut bytes = [0u8; 16];
bytes[15] = 1;
assert_eq!(format_v6(bytes).as_deref(), Some("::1"));
}
#[test]
fn inet_ntop_v6_routable() {
let mut bytes = [0u8; 16];
bytes[0] = 0x20;
bytes[1] = 0x01;
bytes[2] = 0x0d;
bytes[3] = 0xb8;
bytes[15] = 0x01;
assert_eq!(format_v6(bytes).as_deref(), Some("2001:db8::1"));
}
#[test]
fn inet_ntop_v6_link_local() {
let mut bytes = [0u8; 16];
bytes[0] = 0xfe;
bytes[1] = 0x80;
bytes[15] = 0x01;
assert_eq!(format_v6(bytes).as_deref(), Some("fe80::1"));
}
#[test]
fn inet_ntop_v6_no_compression_single_zero() {
let bytes = [
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04,
0x00, 0x05,
];
assert_eq!(format_v6(bytes).as_deref(), Some("2001:db8:0:1:2:3:4:5"));
}
#[test]
fn inet_ntop_v6_tie_break_first_wins() {
let bytes = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
];
assert_eq!(format_v6(bytes).as_deref(), Some("::1:0:0:1"));
}
#[test]
fn inet_ntop_v6_trailing_run() {
let bytes = [
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
assert_eq!(format_v6(bytes).as_deref(), Some("1::"));
}
#[test]
fn inet_ntop_v4_loopback() {
let addr = in_addr {
s_addr: 0x7f000001u32.to_be(),
};
let mut buf = [0u8; 32];
let ret = unsafe {
inet_ntop(
AF_INET,
ptr::from_ref(&addr).cast::<c_void>(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len() as socklen_t,
)
};
assert!(!ret.is_null());
let nul = buf.iter().position(|&b| b == 0).unwrap();
assert_eq!(core::str::from_utf8(&buf[..nul]).unwrap(), "127.0.0.1");
}
#[test]
fn inet_ntop_unsupported_family() {
let mut buf = [0u8; 32];
let ret = unsafe {
inet_ntop(
9999,
ptr::null(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len() as socklen_t,
)
};
assert!(ret.is_null());
}
#[test]
fn inet_ntop_v6_size_too_small() {
let addr = in6_addr { s6_addr: [0u8; 16] };
let mut buf = [0u8; 4];
let ret = unsafe {
inet_ntop(
AF_INET6,
ptr::from_ref(&addr).cast::<c_void>(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len() as socklen_t,
)
};
assert!(ret.is_null());
}
}