diff --git a/src/header/arpa_inet/mod.rs b/src/header/arpa_inet/mod.rs index b2dbfaa8c3..ae3baf387b 100644 --- a/src/header/arpa_inet/mod.rs +++ b/src/header/arpa_inet/mod.rs @@ -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::())).s_addr).cast::(), @@ -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::())).s6_addr).cast::(), + 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 { + let addr = in6_addr { s6_addr: bytes }; + let mut buf = [0u8; 64]; + let ret = unsafe { + inet_ntop( + AF_INET6, + ptr::from_ref(&addr).cast::(), + buf.as_mut_ptr().cast::(), + 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::(), + buf.as_mut_ptr().cast::(), + 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::(), + 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::(), + buf.as_mut_ptr().cast::(), + buf.len() as socklen_t, + ) + }; + assert!(ret.is_null()); + } +} diff --git a/src/header/netdb/lookup.rs b/src/header/netdb/lookup.rs index 143fc0b834..6957eca6f0 100644 --- a/src/header/netdb/lookup.rs +++ b/src/header/netdb/lookup.rs @@ -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; +pub type LookupHostV6 = Vec; + +/// 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 { 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 { + 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, 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::(); + 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::(); + let packet_data_box = packet_data.into_boxed_slice(); + let packet_data_ptr = Box::into_raw(packet_data_box).cast::(); - 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::(); - 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::(); - - 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::(); + + 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 { + 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::(), + ptr::from_mut(&mut addr).cast::(), + ) + }; + if result == 1 { Some(addr) } else { None } } pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs index 0a772bfa4f..2e6c8557c2 100644 --- a/src/header/netdb/mod.rs +++ b/src/header/netdb/mod.rs @@ -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::() { - 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::() { + Ok(n) => port = n, + Err(_) => return EAI_NONAME, + } + } else { + match svc_bytes.parse::() { + 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 = Vec::new(); + let mut v6_results: Vec = 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::(); + 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::(); - let ai_addrlen = mem::size_of::() 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::() 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::(); + + 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::() 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::()) }; + 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::(), + buf.as_mut_ptr().cast::(), + 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::(), + buf.as_mut_ptr().cast::(), + 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"); + } +} diff --git a/src/header/netinet_ip/cbindgen.toml b/src/header/netinet_ip/cbindgen.toml index 6dcb1636e7..fcb3293718 100644 --- a/src/header/netinet_ip/cbindgen.toml +++ b/src/header/netinet_ip/cbindgen.toml @@ -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 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 \ No newline at end of file diff --git a/src/header/netinet_ip/mod.rs b/src/header/netinet_ip/mod.rs index 30ddb6ebf4..2b1182452f 100644 --- a/src/header/netinet_ip/mod.rs +++ b/src/header/netinet_ip/mod.rs @@ -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 `` 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_HEADER_LENGTH); + } + + #[test] + fn iphdr_alignment_is_one_byte() { + assert_eq!(core::mem::align_of::(), 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 + ); + } + } + } +} \ No newline at end of file diff --git a/src/header/sys_ioctl/redox/mod.rs b/src/header/sys_ioctl/redox/mod.rs index 6d222df7c9..4521cf29e1 100644 --- a/src/header/sys_ioctl/redox/mod.rs +++ b/src/header/sys_ioctl/redox/mod.rs @@ -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::() }; dup_read(fd, "ptsname", name)?; } + FIONREAD => { + let available = unsafe { &mut *out.cast::() }; + // 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)?; diff --git a/src/header/sys_socket/mod.rs b/src/header/sys_socket/mod.rs index 8d1e2557c1..76024dd5d3 100644 --- a/src/header/sys_socket/mod.rs +++ b/src/header/sys_socket/mod.rs @@ -185,6 +185,28 @@ pub unsafe extern "C" fn accept( ) } +/// Non-POSIX; see . +/// +/// 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 . #[unsafe(no_mangle)] pub unsafe extern "C" fn bind( diff --git a/src/platform/linux/socket.rs b/src/platform/linux/socket.rs index e9a997d291..630d15f125 100644 --- a/src/platform/linux/socket.rs +++ b/src/platform/linux/socket.rs @@ -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 { + // 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(()) diff --git a/src/platform/pal/socket.rs b/src/platform/pal/socket.rs index 259e724eae..f8b286c790 100644 --- a/src/platform/pal/socket.rs +++ b/src/platform/pal/socket.rs @@ -16,6 +16,16 @@ pub trait PalSocket: Pal { address_len: *mut socklen_t, ) -> Result; + /// 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; + /// 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<()>; diff --git a/src/platform/redox/socket.rs b/src/platform/redox/socket.rs index f55397c94d..fe0b9c72bf 100644 --- a/src/platform/redox/socket.rs +++ b/src/platform/redox/socket.rs @@ -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 { + // 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 }) {