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:
+206
-7
@@ -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
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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<()>;
|
||||
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
Reference in New Issue
Block a user