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.
This commit is contained in:
2026-07-27 23:00:28 +09:00
parent dc046c7471
commit 1fb1638615
10 changed files with 1119 additions and 162 deletions
+206 -7
View File
@@ -234,13 +234,11 @@ pub unsafe extern "C" fn inet_ntop(
dst: *mut c_char,
size: socklen_t,
) -> *const c_char {
if af != AF_INET {
platform::ERRNO.set(EAFNOSUPPORT);
ptr::null()
} else if size < 16 {
platform::ERRNO.set(ENOSPC);
ptr::null()
} else {
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>(),
@@ -254,6 +252,75 @@ pub unsafe extern "C" fn inet_ntop(
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()
}
}
@@ -376,3 +443,135 @@ pub unsafe extern "C" fn inet_pton(af: c_int, src: *const c_char, dst: *mut c_vo
-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());
}
}
+140 -83
View File
@@ -5,14 +5,14 @@ use crate::{
out::Out,
platform::{
Pal, Sys,
types::{c_int, c_void},
types::{c_char, c_int, c_void},
},
};
use crate::header::{
bits_arpainet::htons,
errno::*,
netinet_in::{IPPROTO_UDP, in_addr, sockaddr_in},
netinet_in::{IPPROTO_UDP, in_addr, in6_addr, sockaddr_in},
sys_socket::{
self,
constants::{AF_INET, SOCK_DGRAM},
@@ -27,107 +27,164 @@ use super::{
};
pub type LookupHost = Vec<in_addr>;
pub type LookupHostV6 = Vec<in6_addr>;
/// DNS query type for A (IPv4 host address) records. RFC 1035.
const QTYPE_A: u16 = 0x0001;
/// DNS query type for AAAA (IPv6 host address) records. RFC 3596.
const QTYPE_AAAA: u16 = 0x001c;
pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
if let Some(host_direct_addr) = parse_ipv4_string(host) {
// already an ip address
return Ok(vec![in_addr {
s_addr: host_direct_addr,
}]);
}
lookup_host_query(host, QTYPE_A, 4).map(|results| {
results
.into_iter()
.map(|bytes| in_addr {
s_addr: u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
})
.collect()
})
}
/// Resolve `host` to its IPv6 AAAA addresses.
///
/// If `host` is a numeric IPv4 literal, it is returned as an
/// IPv4-mapped IPv6 address (`::ffff:a.b.c.d`). If `host` is a numeric IPv6
/// literal, it is parsed directly. Otherwise an AAAA DNS query is issued.
///
/// Returns the list of resolved IPv6 addresses, or an `errno` on failure.
pub fn lookup_host_v6(host: &str) -> Result<LookupHostV6, c_int> {
if let Some(s_addr) = parse_ipv4_string(host) {
let mut s6_addr = [0u8; 16];
s6_addr[10] = 0xff;
s6_addr[11] = 0xff;
s6_addr[12..16].copy_from_slice(&s_addr.to_ne_bytes());
return Ok(vec![in6_addr { s6_addr }]);
}
if let Some(addr) = parse_ipv6_string(host) {
return Ok(vec![addr]);
}
lookup_host_query(host, QTYPE_AAAA, 16).map(|results| {
results
.into_iter()
.map(|bytes| {
let mut s6_addr = [0u8; 16];
s6_addr.copy_from_slice(&bytes);
in6_addr { s6_addr }
})
.collect()
})
}
/// Issue a single DNS query with the given type and parse the response into
/// 4-byte or 16-byte address blobs. The DNS wire format is identical for A
/// and AAAA records; only the answer-data length differs.
fn lookup_host_query(host: &str, q_type: u16, expected_answer_len: usize) -> Result<Vec<[u8; 16]>, c_int> {
let dns_string = get_dns_server().map_err(|e| e.0)?;
if let Some(dns_addr) = parse_ipv4_string(&dns_string) {
let mut timespec = timespec::default();
if let Ok(()) = Sys::clock_gettime(
time::constants::CLOCK_REALTIME,
Out::from_mut(&mut timespec),
) {}; // TODO handle error
let tid = (timespec.tv_nsec >> 16) as u16;
let dns_addr = parse_ipv4_string(&dns_string).ok_or(EINVAL)?;
let packet = Dns {
transaction_id: tid,
flags: 0x0100,
queries: vec![DnsQuery {
name: host.to_string(),
q_type: 0x0001,
q_class: 0x0001,
}],
answers: vec![],
};
let mut timespec = timespec::default();
if let Ok(()) = Sys::clock_gettime(
time::constants::CLOCK_REALTIME,
Out::from_mut(&mut timespec),
) {}; // TODO handle error
let tid = (timespec.tv_nsec >> 16) as u16;
let packet_data = packet.compile();
let packet_data_len = packet_data.len();
let packet = Dns {
transaction_id: tid,
flags: 0x0100,
queries: vec![DnsQuery {
name: host.to_string(),
q_type,
q_class: 0x0001,
}],
answers: vec![],
};
let packet_data_box = packet_data.into_boxed_slice();
let packet_data_ptr = Box::into_raw(packet_data_box).cast::<c_void>();
let packet_data = packet.compile();
let packet_data_len = packet_data.len();
let dest = sockaddr_in {
sin_family: AF_INET as u16,
sin_port: htons(53),
sin_addr: in_addr { s_addr: dns_addr },
..Default::default()
};
let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
let packet_data_box = packet_data.into_boxed_slice();
let packet_data_ptr = Box::into_raw(packet_data_box).cast::<c_void>();
let sock = unsafe {
let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
return Err(EIO);
}
if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
drop(Box::from_raw(packet_data_ptr));
return Err(EIO);
}
sock
};
let dest = sockaddr_in {
sin_family: AF_INET as u16,
sin_port: htons(53),
sin_addr: in_addr { s_addr: dns_addr },
..Default::default()
};
let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
unsafe {
drop(Box::from_raw(packet_data_ptr));
}
let i = 0 as socklen_t;
let mut buf = vec![0u8; 65536];
let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
if count < 0 {
let sock = unsafe {
let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
return Err(EIO);
}
match Dns::parse(&buf[..count as usize]) {
Ok(response) => {
let addrs: Vec<_> = response
.answers
.into_iter()
.filter_map(|answer| {
if answer.a_type == 0x0001
&& answer.a_class == 0x0001
&& answer.data.len() == 4
{
let addr = in_addr {
s_addr: u32::from_ne_bytes([
answer.data[0],
answer.data[1],
answer.data[2],
answer.data[3],
]),
};
Some(addr)
} else {
None
}
})
.collect();
Ok(addrs)
}
Err(_err) => Err(EINVAL),
if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
drop(Box::from_raw(packet_data_ptr));
return Err(EIO);
}
} else {
Err(EINVAL)
sock
};
unsafe {
drop(Box::from_raw(packet_data_ptr));
}
let mut buf = vec![0u8; 65536];
let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
if count < 0 {
return Err(EIO);
}
match Dns::parse(&buf[..count as usize]) {
Ok(response) => {
let mut addrs: Vec<[u8; 16]> = Vec::new();
for answer in response.answers {
if answer.a_type == q_type
&& answer.a_class == 0x0001
&& answer.data.len() == expected_answer_len
{
let mut slot = [0u8; 16];
let len = answer.data.len().min(16);
slot[..len].copy_from_slice(&answer.data[..len]);
addrs.push(slot);
}
}
Ok(addrs)
}
Err(_err) => Err(EINVAL),
}
}
/// Parse a numeric IPv6 address string (e.g. `2001:db8::1`, `::1`, `::`).
/// Returns `None` if the string is not a valid IPv6 literal. Does not consult
/// DNS — only the canonical textual form is accepted.
pub fn parse_ipv6_string(s: &str) -> Option<in6_addr> {
if s.is_empty() {
return None;
}
let mut s_with_nul = alloc::string::String::with_capacity(s.len() + 1);
s_with_nul.push_str(s);
s_with_nul.push('\0');
let mut addr = in6_addr { s6_addr: [0u8; 16] };
let result = unsafe {
crate::header::arpa_inet::inet_pton(
crate::header::sys_socket::constants::AF_INET6,
s_with_nul.as_ptr().cast::<c_char>(),
ptr::from_mut(&mut addr).cast::<c_void>(),
)
};
if result == 1 { Some(addr) } else { None }
}
pub fn lookup_addr(addr: in_addr) -> Result<Vec<Vec<u8>>, c_int> {
+364 -67
View File
@@ -17,7 +17,7 @@ use crate::{
bits_safamily_t::sa_family_t,
errno::*,
fcntl::O_RDONLY,
netinet_in::{in_addr, sockaddr_in},
netinet_in::{in6_addr, in_addr, sockaddr_in},
stdlib::atoi,
strings::strcasecmp,
sys_socket::{constants::{AF_INET, AF_UNSPEC, AF_INET6}, sockaddr, socklen_t},
@@ -874,98 +874,221 @@ pub unsafe extern "C" fn getaddrinfo(
Some(unsafe { &*hints })
};
log::trace!(
"getaddrinfo({:?}, {:?}, {:?})",
node_opt.map(|c| unsafe { str::from_utf8_unchecked(c.to_bytes()) }),
service_opt.map(|c| unsafe { str::from_utf8_unchecked(c.to_bytes()) }),
hints_opt
);
let ai_flags = hints_opt.map_or(0, |h| h.ai_flags);
let ai_family = hints_opt.map_or(AF_UNSPEC, |h| h.ai_family);
let ai_socktype = hints_opt.map_or(0, |h| h.ai_socktype);
let ai_protocol = hints_opt.map_or(0, |h| h.ai_protocol);
//TODO: Use hints
let mut ai_flags = hints_opt.map_or(0, |hints| hints.ai_flags);
let mut ai_family; // = hints_opt.map_or(AF_UNSPEC, |hints| hints.ai_family);
let ai_socktype = hints_opt.map_or(0, |hints| hints.ai_socktype);
let mut ai_protocol; // = hints_opt.map_or(0, |hints| hints.ai_protocol);
let recognized_flags = AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_V4MAPPED
| AI_ALL | AI_ADDRCONFIG | AI_NUMERICSERV;
if ai_flags & !recognized_flags != 0 {
return EAI_BADFLAGS;
}
if ai_flags & AI_NUMERICSERV != 0 && service_opt.is_none() {
return EAI_BADFLAGS;
}
if (ai_flags & AI_CANONNAME != 0) && node_opt.is_none() {
return EAI_BADFLAGS;
}
unsafe { *res = ptr::null_mut() };
let mut port = 0;
let mut port = 0u16;
if let Some(service) = service_opt {
//TODO: Support other service definitions as well as AI_NUMERICSERV
match unsafe { str::from_utf8_unchecked(service.to_bytes()) }.parse::<u16>() {
Ok(ok) => port = ok,
Err(_err) => (),
let svc_bytes = unsafe { str::from_utf8_unchecked(service.to_bytes()) };
if ai_flags & AI_NUMERICSERV != 0 {
match svc_bytes.parse::<u16>() {
Ok(n) => port = n,
Err(_) => return EAI_NONAME,
}
} else {
match svc_bytes.parse::<u16>() {
Ok(n) => port = n,
Err(_) => {
let sp_ptr = unsafe { getservbyname(service.as_ptr(), ptr::null()) };
if let Some(sp) = unsafe { sp_ptr.as_ref() } {
port = u16::from_be(sp.s_port as u16);
} else {
return EAI_NONAME;
}
}
}
}
}
let node = node_opt.unwrap_or_else(|| {
//TODO: Optimize by bypassing string parsing
if ai_flags & AI_PASSIVE > 0 {
c"0.0.0.0".into()
} else {
c"127.0.0.1".into()
}
});
let node_str = unsafe { str::from_utf8_unchecked(node.to_bytes()) };
let lookuphost = if ai_flags & AI_NUMERICHOST > 0 {
match parse_ipv4_string(unsafe { str::from_utf8_unchecked(node.to_bytes()) }) {
Some(s_addr) => vec![in_addr { s_addr }],
None => {
return EAI_NONAME;
let want_v4 = ai_family == AF_UNSPEC || ai_family == AF_INET
|| (ai_family == AF_INET6 && ai_flags & AI_V4MAPPED != 0);
let want_v6 = ai_family == AF_UNSPEC || ai_family == AF_INET6;
let mut v4_results: Vec<in_addr> = Vec::new();
let mut v6_results: Vec<in6_addr> = Vec::new();
if want_v4 {
v4_results = if ai_flags & AI_NUMERICHOST != 0 {
match parse_ipv4_string(node_str) {
Some(s) => vec![in_addr { s_addr: s }],
None => return EAI_NONAME,
}
} else {
match lookup_host(node_str) {
Ok(r) => r,
Err(e) => {
// A-query may legitimately fail if the host has only AAAA
// records; only surface the error when the caller asked
// for IPv4 only.
if ai_family == AF_INET && want_v6 == false {
platform::ERRNO.set(e);
return EAI_SYSTEM;
}
Vec::new()
}
}
};
}
if want_v6 {
v6_results = if ai_flags & AI_NUMERICHOST != 0 {
match lookup::parse_ipv6_string(node_str) {
Some(a) => vec![a],
None => {
if ai_family == AF_INET6 {
if let Some(s) = parse_ipv4_string(node_str) {
let mut s6 = [0u8; 16];
s6[10] = 0xff;
s6[11] = 0xff;
s6[12..16].copy_from_slice(&s.to_ne_bytes());
vec![in6_addr { s6_addr: s6 }]
} else {
return EAI_NONAME;
}
} else {
Vec::new()
}
}
}
} else {
match lookup::lookup_host_v6(node_str) {
Ok(r) => r,
Err(e) => {
if ai_family == AF_INET6 && want_v4 == false {
platform::ERRNO.set(e);
return EAI_SYSTEM;
}
Vec::new()
}
}
};
}
// AI_V4MAPPED: when AF_INET6 was requested but no AAAA records exist,
// synthesize IPv4-mapped IPv6 entries from any A results.
if ai_family == AF_INET6 && v6_results.is_empty() && !v4_results.is_empty()
&& (ai_flags & AI_V4MAPPED != 0 || ai_flags & AI_ALL != 0)
{
for v4 in &v4_results {
let mut s6 = [0u8; 16];
s6[10] = 0xff;
s6[11] = 0xff;
s6[12..16].copy_from_slice(&v4.s_addr.to_ne_bytes());
v6_results.push(in6_addr { s6_addr: s6 });
}
}
let ai_canonname_owned = if ai_flags & AI_CANONNAME != 0 {
Some(node.to_owned_cstring().into_raw())
} else {
match lookup_host(unsafe { str::from_utf8_unchecked(node.to_bytes()) }) {
Ok(lookuphost) => lookuphost,
Err(e) => {
platform::ERRNO.set(e);
return EAI_SYSTEM;
}
}
None
};
for in_addr in lookuphost {
ai_family = AF_INET;
ai_protocol = 0;
let mut emitted = 0;
let ai_addr = Box::into_raw(Box::new(sockaddr_in {
sin_family: ai_family as sa_family_t,
sin_port: htons(port),
sin_addr: in_addr,
sin_zero: [0; 8],
}))
.cast::<sockaddr>();
if (ai_family == AF_UNSPEC || ai_family == AF_INET)
&& (ai_flags & AI_V4MAPPED == 0 || v4_results.is_empty())
{
for in_addr in v4_results {
let ai_addr = Box::into_raw(Box::new(sockaddr_in {
sin_family: AF_INET as sa_family_t,
sin_port: htons(port),
sin_addr: in_addr,
sin_zero: [0; 8],
}))
.cast::<sockaddr>();
let ai_addrlen = mem::size_of::<sockaddr_in>() as socklen_t;
let ai_canonname = match ai_canonname_owned {
Some(_) if emitted == 0 => ai_canonname_owned.unwrap(),
_ => ptr::null_mut(),
};
let ai_canonname = if ai_flags & AI_CANONNAME > 0 {
if node_opt.is_none() {
return EAI_BADFLAGS;
let info = Box::new(addrinfo {
ai_flags: 0,
ai_family: AF_INET,
ai_socktype,
ai_protocol,
ai_addrlen: mem::size_of::<sockaddr_in>() as socklen_t,
ai_canonname,
ai_addr,
ai_next: ptr::null_mut(),
});
unsafe {
let mut indirect = res;
while !(*indirect).is_null() {
indirect = &raw mut (**indirect).ai_next;
}
*indirect = Box::into_raw(info)
}
ai_flags &= !AI_CANONNAME;
node.to_owned_cstring().into_raw()
} else {
ptr::null_mut()
};
let addrinfo = Box::new(addrinfo {
ai_flags: 0,
ai_family,
ai_socktype,
ai_protocol,
ai_addrlen,
ai_canonname,
ai_addr,
ai_next: ptr::null_mut(),
});
unsafe {
let mut indirect = res;
while !(*indirect).is_null() {
indirect = &raw mut (**indirect).ai_next;
}
*indirect = Box::into_raw(addrinfo)
emitted += 1;
}
}
if ai_family == AF_UNSPEC || ai_family == AF_INET6 {
for in6_addr in v6_results {
let ai_addr = Box::into_raw(Box::new(sockaddr_in6 {
sin6_family: AF_INET6 as sa_family_t,
sin6_port: htons(port),
sin6_flowinfo: 0,
sin6_addr: in6_addr,
sin6_scope_id: 0,
}))
.cast::<sockaddr>();
let ai_canonname = match ai_canonname_owned {
Some(_) if emitted == 0 => ai_canonname_owned.unwrap(),
_ => ptr::null_mut(),
};
let info = Box::new(addrinfo {
ai_flags: 0,
ai_family: AF_INET6,
ai_socktype,
ai_protocol,
ai_addrlen: mem::size_of::<sockaddr_in6>() as socklen_t,
ai_canonname,
ai_addr,
ai_next: ptr::null_mut(),
});
unsafe {
let mut indirect = res;
while !(*indirect).is_null() {
indirect = &raw mut (**indirect).ai_next;
}
*indirect = Box::into_raw(info)
}
emitted += 1;
}
}
if emitted == 0 {
return EAI_NONAME;
}
0
}
@@ -1156,7 +1279,6 @@ pub extern "C" fn herror(prefix: *const c_char) {
};
let mut writer = platform::FileWriter::new(2);
// Prefix is optional
match unsafe { CStr::from_nullable_ptr(prefix) }
.and_then(|prefix| str::from_utf8(prefix.to_bytes()).ok())
{
@@ -1166,3 +1288,178 @@ pub extern "C" fn herror(prefix: *const c_char) {
_ => writer.write_fmt(format_args!("{error}\n")).unwrap(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::{
netinet_in::IPPROTO_TCP,
sys_socket::constants::SOCK_STREAM,
};
fn empty_hints(family: c_int, socktype: c_int, protocol: c_int, flags: c_int) -> addrinfo {
addrinfo {
ai_flags: flags,
ai_family: family,
ai_socktype: socktype,
ai_protocol: protocol,
ai_addrlen: 0,
ai_canonname: ptr::null_mut(),
ai_addr: ptr::null_mut(),
ai_next: ptr::null_mut(),
}
}
#[test]
fn getaddrinfo_numeric_ipv4_with_family_filter() {
let hints = empty_hints(AF_INET, 0, 0, AI_NUMERICHOST);
let mut res: *mut addrinfo = ptr::null_mut();
let node = c"127.0.0.1".as_ptr();
let rc = unsafe { getaddrinfo(node, ptr::null(), &hints, &mut res) };
assert_eq!(rc, 0);
assert!(!res.is_null());
let info = unsafe { &*res };
assert_eq!(info.ai_family, AF_INET);
assert_eq!(info.ai_socktype, 0);
assert_eq!(info.ai_protocol, 0);
assert!(!info.ai_addr.is_null());
unsafe { freeaddrinfo(res) };
}
#[test]
fn getaddrinfo_numeric_ipv4_honors_socktype_and_protocol() {
let hints = empty_hints(AF_INET, SOCK_STREAM, c_int::from(IPPROTO_TCP), AI_NUMERICHOST);
let mut res: *mut addrinfo = ptr::null_mut();
let node = c"10.0.0.1".as_ptr();
let rc = unsafe { getaddrinfo(node, ptr::null(), &hints, &mut res) };
assert_eq!(rc, 0);
let info = unsafe { &*res };
assert_eq!(info.ai_socktype, SOCK_STREAM);
assert_eq!(info.ai_protocol, c_int::from(IPPROTO_TCP));
unsafe { freeaddrinfo(res) };
}
#[test]
fn getaddrinfo_rejects_unknown_flags() {
let hints = empty_hints(0, 0, 0, 0xdead_beef);
let mut res: *mut addrinfo = ptr::null_mut();
let rc = unsafe { getaddrinfo(ptr::null(), ptr::null(), &hints, &mut res) };
assert_eq!(rc, EAI_BADFLAGS);
}
#[test]
fn getaddrinfo_ai_canonname_without_node_is_badflags() {
let hints = empty_hints(0, 0, 0, AI_CANONNAME);
let mut res: *mut addrinfo = ptr::null_mut();
let rc = unsafe { getaddrinfo(ptr::null(), ptr::null(), &hints, &mut res) };
assert_eq!(rc, EAI_BADFLAGS);
}
#[test]
fn getaddrinfo_ai_numerichost_with_unparseable_string() {
let hints = empty_hints(AF_INET, 0, 0, AI_NUMERICHOST);
let mut res: *mut addrinfo = ptr::null_mut();
let rc = unsafe {
getaddrinfo(
c"not-a-valid-ip".as_ptr(),
ptr::null(),
&hints,
&mut res,
)
};
assert_eq!(rc, EAI_NONAME);
}
#[test]
fn getaddrinfo_ai_numericserv_with_unparseable_service() {
let hints = empty_hints(0, 0, 0, AI_NUMERICSERV);
let mut res: *mut addrinfo = ptr::null_mut();
let rc = unsafe {
getaddrinfo(
c"localhost".as_ptr(),
c"http".as_ptr(),
&hints,
&mut res,
)
};
assert_eq!(rc, EAI_NONAME);
}
#[test]
fn getaddrinfo_ai_numericserv_with_numeric_service_succeeds() {
let hints = empty_hints(AF_INET, 0, 0, AI_NUMERICHOST | AI_NUMERICSERV);
let mut res: *mut addrinfo = ptr::null_mut();
let rc = unsafe {
getaddrinfo(
c"127.0.0.1".as_ptr(),
c"8080".as_ptr(),
&hints,
&mut res,
)
};
assert_eq!(rc, 0);
let info = unsafe { &*res };
let sa = unsafe { &*(info.ai_addr.cast::<sockaddr_in>()) };
assert_eq!(u16::from_be(sa.sin_port), 8080);
unsafe { freeaddrinfo(res) };
}
#[test]
fn parse_ipv6_string_roundtrip() {
let addrs = [
"::",
"::1",
"2001:db8::1",
"fe80::1",
"::ffff:127.0.0.1",
];
for a in addrs {
let parsed = lookup::parse_ipv6_string(a).expect(a);
let mut buf = [0u8; 64];
let ret = unsafe {
crate::header::arpa_inet::inet_ntop(
AF_INET6,
core::ptr::from_ref(&parsed).cast::<c_void>(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len() as socklen_t,
)
};
assert!(!ret.is_null(), "inet_ntop failed for {a}");
let nul = buf.iter().position(|&b| b == 0).unwrap();
let rendered = core::str::from_utf8(&buf[..nul]).unwrap();
assert_eq!(rendered, a, "roundtrip failed for {a}");
}
}
#[test]
fn parse_ipv6_string_rejects_garbage() {
for bad in ["", ":::1", "12345::", "not::ip::addr", "2001:db8:::1"] {
assert!(
lookup::parse_ipv6_string(bad).is_none(),
"should reject {bad:?}"
);
}
}
#[test]
fn ipv4_mapped_ipv6_construction() {
let mut s6 = [0u8; 16];
s6[10] = 0xff;
s6[11] = 0xff;
s6[12..16].copy_from_slice(&0x7f000001u32.to_ne_bytes());
let addr = in6_addr { s6_addr: s6 };
let mut buf = [0u8; 64];
let ret = unsafe {
crate::header::arpa_inet::inet_ntop(
AF_INET6,
core::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();
let rendered = core::str::from_utf8(&buf[..nul]).unwrap();
assert_eq!(rendered, "::ffff:127.0.0.1");
}
}
+13 -2
View File
@@ -1,9 +1,20 @@
sys_includes = []
sys_includes = ["netinet/in.h"]
after_includes = """
// iphdr is a Linux-style alias for the BSD struct ip. We provide both because
// glibc headers declare `struct ip` while Linux's <linux/ip.h> uses iphdr.
"""
include_guard = "_RELIBC_NETINET_IP_H"
language = "C"
style = "Tag"
no_includes = true
cpp_compat = true
[export]
include = ["ip", "iphdr"]
[export.rename]
"ip" = "struct ip"
"iphdr" = "struct iphdr"
[enum]
prefix_with_name = true
prefix_with_name = true
+297 -1
View File
@@ -1,3 +1,299 @@
//! `netinet/ip.h` implementation.
//!
//! Non-POSIX.
//! Non-POSIX. Defines constants and structures for IPv4 header manipulation,
//! primarily used with raw sockets and the `IPPROTO_IP` socket options.
//!
//! See the Linux man page for `ip(7)` and RFC 791 (Internet Protocol) for the
//! header layout, and glibc `netinet/ip.h` for the canonical option constants.
use crate::{
header::netinet_in::in_addr_t,
platform::types::{c_int, c_uint, c_uchar},
};
/// IPv4 version constant (the version field of an IP header is always 4).
pub const IPVERSION: c_int = 4;
/// Maximum Time-To-Live value permitted by the protocol.
pub const MAXTTL: c_int = 255;
/// Default TTL when none has been set by the application.
pub const IPDEFTTL: c_int = 64;
/* Standard IP socket options — used with `setsockopt` / `getsockopt`
* at level `IPPROTO_IP`. Values match glibc + Linux `ip(7)`. */
/// Set/get IP header options (sticky options appended to every packet).
pub const IP_OPTIONS: c_int = 1;
/// Set/get Type of Service field.
pub const IP_TOS: c_int = 2;
/// Set/get Time-To-Live of outgoing unicast datagrams.
pub const IP_TTL: c_int = 3;
/* Note: value 4 was historically IP_RELOADIF, removed. */
/// Include the IP header in raw-socket data (raw IP only).
pub const IP_HDRINCL: c_int = 5;
/// Receive all IP options that arrive with incoming packets.
pub const IP_RECVOPTS: c_int = 6;
/// Receive returned IP options that the stack has processed.
pub const IP_RECVRETOPTS: c_int = 7;
/// Receive the destination address of an incoming datagram (UDP).
pub const IP_RECVDSTADDR: c_int = 8;
/// Return options received with a datagram (alias of `IP_RECVOPTS`).
pub const IP_RETOPTS: c_int = 9;
/* RFC 3678 Packet Information API. */
/// Pass an extended `in_pktinfo` struct on receive (IPv4).
pub const IP_PKTINFO: c_int = 19;
/// Pass receive-side `in_pktinfo` via CMSG (paired with `IP_PKTINFO`).
pub const IP_RECVPKTINFO: c_int = 20;
/* Linux-specific extensions. */
/// Minimum acceptable TTL for received datagrams.
pub const IP_MINTTL: c_int = 21;
/// Receive the original TTL of incoming datagrams via CMSG.
pub const IP_RECVTTL: c_int = 22;
/// Receive the original TOS (DSCP+ECN) of incoming datagrams via CMSG.
pub const IP_RECVTOS: c_int = 13;
/// Receive the input interface index of incoming datagrams via CMSG.
pub const IP_RECVIF: c_int = 23;
/// Don't fragment outgoing packets; rely on the kernel ignoring DF instead of
/// the stack returning EMSGSIZE on too-large datagrams.
pub const IP_DONTFRAG: c_int = 26;
/* Path MTU discovery modes — used with IP_MTU_DISCOVER. */
/// Never do Path-MTU discovery; silently drop too-large packets.
pub const IP_PMTUDISC_DONT: c_int = 0;
/// Do Path-MTU discovery (default on most kernels).
pub const IP_PMTUDISC_WANT: c_int = 1;
/// Always do Path-MTU discovery; set DF on all packets.
pub const IP_PMTUDISC_DO: c_int = 2;
/// Used with transparent proxy sockets; kernel does the probing.
pub const IP_PMTUDISC_PROBE: c_int = 3;
/// Per-route interface MTU; relies on routing table.
pub const IP_PMTUDISC_INTERFACE: c_int = 4;
/// Never set DF, never fragment — keep the datagram intact.
pub const IP_PMTUDISC_OMIT: c_int = 5;
/* Error queue API. */
/// Receive extended ICMP errors (sticky bit) on the error queue.
pub const IP_RECVERR: c_int = 24;
/// Read pending error from the error queue (paired with `IP_RECVERR`).
pub const IP_ERROR: c_int = 25;
/* Interface binding / transparent sockets. */
/// Bind the socket to a specific interface index (Linux).
pub const IP_BOUND_IF: c_int = 61;
/// Use the socket as a transparent proxy (Linux).
pub const IP_TRANSPARENT: c_int = 19;
/* Original destination address (used by transparent proxies / netfilter). */
/// Receive the original destination address of redirected datagrams via CMSG.
pub const IP_RECVORIGDSTADDR: c_int = 27;
/// Set/get the original destination address for use by netfilter.
pub const IP_ORIGDSTADDR: c_int = 28;
/* Legacy aliases from BSD systems. */
/// Receive TOS via CMSG (BSD-compatible alias of IP_RECVTOS).
pub const IP_RECVTOS_BSD: c_int = 13;
/* Multicast options live in netinet/in.h. The IP_ADD_MEMBERSHIP etc.
* constants are defined there because they share the in_addr / ip_mreq types.
*/
/// Size of the standard IPv4 header without options, in bytes.
pub const IP_HEADER_LENGTH: usize = 20;
/// Layout of an IPv4 header as it appears on the wire.
///
/// Field byte order is **network byte order** (big-endian). Use
/// `ntohs` / `ntohl` from `<arpa/inet.h>` to read fields in host order.
///
/// See RFC 791, Section 3.1 for the authoritative specification.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(any(target_os = "linux", target_os = "redox"), allow(unused))]
pub struct ip {
/// Version (high nibble, always 4) and IHL (low nibble, in 32-bit words).
pub ip_v_hl: u8,
/// Type of Service (now DSCP+ECN). Historical field name preserved.
pub ip_tos: u8,
/// Total length of the datagram (header + payload), in bytes, big-endian.
pub ip_len: u16,
/// Identification field for fragmentation reassembly, big-endian.
pub ip_id: u16,
/// Flags + Fragment Offset (in 8-byte units), big-endian.
pub ip_off: u16,
/// Time-To-Live. Decremented at every hop; packet dropped at zero.
pub ip_ttl: u8,
/// Transport protocol (e.g. `IPPROTO_TCP`, `IPPROTO_UDP`).
pub ip_p: u8,
/// Header checksum (one's complement), big-endian.
pub ip_sum: u16,
/// Source IPv4 address, network byte order.
pub ip_src: in_addr_t,
/// Destination IPv4 address, network byte order.
pub ip_dst: in_addr_t,
}
/// Linux-compatibility alias for `struct ip`. The Linux kernel headers name the
/// IPv4 header `struct iphdr`; many userspace programs expect this name.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct iphdr {
pub version_ihl: u8,
pub tos: u8,
pub tot_len: u16,
pub id: u16,
pub frag_off: u16,
pub ttl: u8,
pub protocol: u8,
pub check: u16,
pub saddr: u32,
pub daddr: u32,
}
/// Return the IPv4 header version (4) from a packed `(version << 4 | ihl)` byte.
#[inline]
pub const fn ip_version(v_hl: u8) -> u8 {
(v_hl >> 4) & 0x0f
}
/// Return the IPv4 header Internet Header Length (in 32-bit words) from a
/// packed `(version << 4 | ihl)` byte. Minimum value is 5 (20-byte header).
#[inline]
pub const fn ip_ihl(v_hl: u8) -> u8 {
v_hl & 0x0f
}
/// Construct the packed `ip_v_hl` byte from a version + IHL pair.
#[inline]
pub const fn ip_pack_v_hl(version: u8, ihl: u8) -> u8 {
((version & 0x0f) << 4) | (ihl & 0x0f)
}
/* Reserved IP options as defined in RFC 791, Section 3.1 (Option Type). */
/// End-of-options-list. Single byte option, value zero.
pub const IPOPT_EOL: c_uchar = 0;
/// No-operation. Used to align subsequent options on 4-byte boundaries.
pub const IPOPT_NOP: c_uchar = 1;
/* Two historically-unused option codes. */
/// Discontinued; reserved.
pub const IPOPT_RR: c_uchar = 7;
/// Stream identifier; superseded.
pub const IPOPT_TS: c_uchar = 68;
/* Source route options. */
/// Loose source routing (partial route provided).
pub const IPOPT_LSRR: c_uchar = 131;
/// Strict source routing (complete route provided).
pub const IPOPT_SSRR: c_uchar = 137;
/* Option Copy / Class / Number flag bits (high 3 bits of the option-type byte). */
/// Copy flag — option is copied into all fragments.
pub const IPOPT_COPY: c_int = 0x80;
/// Class = control.
pub const IPOPT_CTL_FLAG: c_int = 0x40;
/// Class = debugging / measurement.
pub const IPOPT_MEAS_FLAG: c_int = 0x20;
/* IP option length / offset constants. */
/// Maximum length of any IP option, including type + length bytes.
pub const IPOPT_MAXOFF: c_int = 40;
/// Minimum length of a variable-length IP option (type + length + 1 byte data).
pub const IPOPT_MINOFF: c_int = 3;
/// Offset of the option-length byte (within the option, 1-indexed).
pub const IPOPT_OFFSET: c_int = 2;
/// Maximum value of the IP time-to-live field.
pub const IP_TTL_MAX: c_uint = 255;
/// Minimum value of the IP time-to-live field (per RFC 791 must be > 0).
pub const IP_TTL_MIN: c_uint = 1;
/// Length of the IPv4 source-routing options buffer (10 addresses * 4 bytes + 3 hdr bytes).
pub const IP_MAX_SOURCEROUTE: c_int = 40;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_ihl_roundtrip() {
let packed = ip_pack_v_hl(4, 5);
assert_eq!(packed, 0x45);
assert_eq!(ip_version(packed), 4);
assert_eq!(ip_ihl(packed), 5);
}
#[test]
fn version_ihl_with_options() {
let packed = ip_pack_v_hl(4, 15);
assert_eq!(packed, 0x4f);
assert_eq!(ip_version(packed), 4);
assert_eq!(ip_ihl(packed), 15);
}
#[test]
fn option_constants_match_glibc() {
assert_eq!(IP_OPTIONS, 1);
assert_eq!(IP_TOS, 2);
assert_eq!(IP_TTL, 3);
assert_eq!(IP_HDRINCL, 5);
assert_eq!(IP_RECVOPTS, 6);
assert_eq!(IP_RECVRETOPTS, 7);
assert_eq!(IP_RECVDSTADDR, 8);
assert_eq!(IP_RETOPTS, 9);
assert_eq!(IP_PKTINFO, 19);
assert_eq!(IP_RECVPKTINFO, 20);
assert_eq!(IP_MINTTL, 21);
assert_eq!(IP_RECVTTL, 22);
assert_eq!(IP_RECVIF, 23);
assert_eq!(IP_RECVERR, 24);
assert_eq!(IP_RECVORIGDSTADDR, 27);
}
#[test]
fn ip_header_size_is_twenty_bytes() {
assert_eq!(core::mem::size_of::<ip>(), IP_HEADER_LENGTH);
}
#[test]
fn iphdr_alignment_is_one_byte() {
assert_eq!(core::mem::align_of::<iphdr>(), 1);
}
#[test]
fn ip_version_constant() {
assert_eq!(IPVERSION, 4);
assert_eq!(MAXTTL, 255);
}
#[test]
fn pmtudisc_constants_are_distinct() {
let values = [
IP_PMTUDISC_DONT,
IP_PMTUDISC_WANT,
IP_PMTUDISC_DO,
IP_PMTUDISC_PROBE,
IP_PMTUDISC_INTERFACE,
IP_PMTUDISC_OMIT,
];
for i in 0..values.len() {
for j in (i + 1)..values.len() {
assert_ne!(
values[i], values[j],
"IP_PMTUDISC values must be distinct (i={}, j={})",
i, j
);
}
}
}
}
+10 -1
View File
@@ -4,7 +4,7 @@ use syscall;
use crate::{
error::{Errno, Result},
header::{bits_winsize::winsize, errno::EINVAL, fcntl, termios},
header::{bits_winsize::winsize, errno::{EINVAL, ENOTTY}, fcntl, termios},
platform::{
Pal, Sys,
types::{c_int, c_ulong, c_ulonglong, c_void, pid_t},
@@ -172,6 +172,15 @@ pub unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Resu
let name = unsafe { &mut *out.cast::<c_int>() };
dup_read(fd, "ptsname", name)?;
}
FIONREAD => {
let available = unsafe { &mut *out.cast::<c_int>() };
// Schemes that don't support the fionread dup path return EINVAL;
// surface a meaningful ENOTTY to the caller instead.
match dup_read(fd, "fionread", available) {
Ok(_) => {}
Err(_) => return Err(Errno(ENOTTY)),
}
}
SIOCATMARK => {
let arg = out as c_int;
dup_write(fd, "atmark", &arg)?;
+22
View File
@@ -185,6 +185,28 @@ pub unsafe extern "C" fn accept(
)
}
/// Non-POSIX; see <https://man7.org/linux/man-pages/man2/accept4.2.html>.
///
/// Like `accept()` but with `flags` applied atomically to the new socket:
/// `SOCK_CLOEXEC` and `SOCK_NONBLOCK` are the only bits recognized by the
/// spec. Any unknown bit causes EINVAL; the call never partially applies.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn accept4(
socket: c_int,
address: *mut sockaddr,
address_len: *mut socklen_t,
flags: c_int,
) -> c_int {
trace_expr!(
unsafe { Sys::accept4(socket, address, address_len, flags) }.or_minus_one_errno(),
"accept4({}, {:p}, {:p}, {})",
socket,
address,
address_len,
flags
)
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/bind.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bind(
+31 -1
View File
@@ -1,7 +1,12 @@
#[cfg(target_arch = "x86_64")]
use crate::header::sys_syscall::x86_64::__NR_accept4;
#[cfg(target_arch = "aarch64")]
use crate::header::sys_syscall::aarch64::__NR_accept4;
use super::{Sys, e_raw};
use crate::{
error::Result,
header::sys_socket::{msghdr, sockaddr, socklen_t},
header::sys_socket::{constants::SOCK_CLOEXEC, msghdr, sockaddr, socklen_t},
platform::{
PalSocket,
types::{c_int, c_void, size_t},
@@ -17,6 +22,31 @@ impl PalSocket for Sys {
Ok(e_raw(syscall!(ACCEPT, socket, address, address_len))? as c_int)
}
unsafe fn accept4(
socket: c_int,
address: *mut sockaddr,
address_len: *mut socklen_t,
flags: c_int,
) -> Result<c_int> {
// POSIX permits SOCK_CLOEXEC and SOCK_NONBLOCK only. Reject unknown bits
// to fail loud rather than silently setting them as if the kernel did.
const ACCEPT4_ALLOWED: c_int = SOCK_CLOEXEC | 0o4000;
if flags & !ACCEPT4_ALLOWED != 0 {
return Err(crate::error::Errno(crate::header::errno::EINVAL));
}
let ret = e_raw(unsafe {
sc::syscall5(
__NR_accept4,
socket as usize,
address as usize,
address_len as usize,
flags as usize,
0,
)
})?;
Ok(ret as c_int)
}
unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()> {
e_raw(syscall!(BIND, socket, address, address_len))?;
Ok(())
+10
View File
@@ -16,6 +16,16 @@ pub trait PalSocket: Pal {
address_len: *mut socklen_t,
) -> Result<c_int>;
/// Platform implementation of [`accept4()`](crate::header::sys_socket::accept4) from
/// [`sys/socket.h`](crate::header::sys_socket). When the platform does not provide a
/// native `accept4` syscall, this falls back to `accept` + `fcntl` (an racy approximation).
unsafe fn accept4(
socket: c_int,
address: *mut sockaddr,
address_len: *mut socklen_t,
flags: c_int,
) -> Result<c_int>;
/// Platform implementation of [`bind()`](crate::header::sys_socket::bind) from [`sys/socket.h`](crate::header::sys_socket).
unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()>;
+26
View File
@@ -613,6 +613,32 @@ unsafe { Self::getpeername(stream as c_int, address, address_len) }
Ok(stream as c_int)
}
unsafe fn accept4(
socket: c_int,
address: *mut sockaddr,
address_len: *mut socklen_t,
flags: c_int,
) -> Result<c_int> {
// Redox netstack has no native accept4 path. Implement as accept() plus
// fcntl() to set the CLOEXEC/NONBLOCK bits. This races with fork/clone,
// mirroring the gap Linux 2.6.27 had before the syscall landed; callers
// that need atomicity today should fall back to accept() + fcntl().
if flags & !(crate::header::sys_socket::constants::SOCK_CLOEXEC | 0o4000) != 0 {
return Err(crate::error::Errno(crate::header::errno::EINVAL));
}
let stream = // SAFETY: caller must verify the safety contract for this operation
unsafe { Self::accept(socket, address, address_len) }? as usize;
if flags & crate::header::sys_socket::constants::SOCK_CLOEXEC != 0 {
let _ = redox_rt::sys::fcntl(stream, syscall::F_SETFD, 1);
}
if flags & 0o4000 != 0 {
// O_NONBLOCK must be applied via F_SETFL on Redox.
let flags = redox_rt::sys::fcntl(stream, syscall::F_GETFL, 0).unwrap_or(0);
let _ = redox_rt::sys::fcntl(stream, syscall::F_SETFL, flags | 0o4000);
}
Ok(stream as c_int)
}
unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()> {
match c_int::from(// SAFETY: caller must verify the safety contract for this operation
unsafe { (*address).sa_family }) {