diff --git a/CHANGELOG.md b/CHANGELOG.md index 1887f92..8895fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Line wrap the file at 100 chars. Th ## [Unreleased] +- Fix `std::net::SocketAddr` conversion to `libc::sockaddr` ## [0.6.0] - 2024-01-31 ### Changed diff --git a/system-configuration/src/network_reachability.rs b/system-configuration/src/network_reachability.rs index 2770ee3..d52e868 100644 --- a/system-configuration/src/network_reachability.rs +++ b/system-configuration/src/network_reachability.rs @@ -344,24 +344,35 @@ impl NetworkReachabilityCallbackContext< /// libc::sockaddr_in6, depending on the passed in standard library SocketAddr. fn to_c_sockaddr(addr: SocketAddr) -> Box { let ptr = match addr { + // See reference conversion from socket2: + // https://github.com/rust-lang/socket2/blob/3a938932829ea6ee3025d2d7a86c7b095c76e6c3/src/sockaddr.rs#L277-L287 + // https://github.com/rust-lang/socket2/blob/3a938932829ea6ee3025d2d7a86c7b095c76e6c3/src/sys/unix.rs#L1356-L1363 SocketAddr::V4(addr) => Box::into_raw(Box::new(libc::sockaddr_in { sin_len: std::mem::size_of::() as u8, - sin_family: libc::AF_INET as u8, - sin_port: addr.port(), - sin_addr: libc::in_addr { - s_addr: u32::from(*addr.ip()), + sin_family: libc::AF_INET as libc::sa_family_t, + sin_port: addr.port().to_be(), + sin_addr: { + // `s_addr` is stored as BE on all machines, and the array is in BE order. + // So the native endian conversion method is used so that it's never + // swapped. + libc::in_addr { + s_addr: u32::from_ne_bytes(addr.ip().octets()), + } }, - sin_zero: [0i8; 8], + sin_zero: Default::default(), })) as *mut c_void, + // See reference conversion from socket2: + // https://github.com/rust-lang/socket2/blob/3a938932829ea6ee3025d2d7a86c7b095c76e6c3/src/sockaddr.rs#L314-L331 + // https://github.com/rust-lang/socket2/blob/3a938932829ea6ee3025d2d7a86c7b095c76e6c3/src/sys/unix.rs#L1369-L1373 SocketAddr::V6(addr) => Box::into_raw(Box::new(libc::sockaddr_in6 { sin6_len: std::mem::size_of::() as u8, - sin6_family: libc::AF_INET6 as u8, - sin6_port: addr.port(), - sin6_flowinfo: 0, + sin6_family: libc::AF_INET6 as libc::sa_family_t, + sin6_port: addr.port().to_be(), + sin6_flowinfo: addr.flowinfo(), sin6_addr: libc::in6_addr { s6_addr: addr.ip().octets(), }, - sin6_scope_id: 0, + sin6_scope_id: addr.scope_id(), })) as *mut c_void, }; @@ -373,7 +384,10 @@ mod test { use super::*; use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop}; - use std::ffi::CString; + use std::{ + ffi::CString, + net::{Ipv4Addr, Ipv6Addr}, + }; #[test] fn test_network_reachability_from_addr() { @@ -434,6 +448,38 @@ mod test { } } + #[test] + fn test_sockaddr_local_to_dns_google_pair_reachability() { + let sockaddrs = [ + "[2001:4860:4860::8844]:443".parse::().unwrap(), + "8.8.4.4:443".parse().unwrap(), + ]; + for remote_addr in sockaddrs { + match std::net::TcpStream::connect(remote_addr) { + Err(_) => { + let local_addr = if remote_addr.is_ipv4() { + SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0) + } else { + SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0) + }; + let reachability = + SCNetworkReachability::from_addr_pair(local_addr, remote_addr); + let reachability_flags = reachability.reachability().unwrap(); + // Verify that not established tcp connection path is reported as not reachable. + assert!(!reachability_flags.contains(ReachabilityFlags::REACHABLE)); + } + Ok(tcp) => { + let local = tcp.local_addr().unwrap(); + let remote = tcp.peer_addr().unwrap(); + let reachability = SCNetworkReachability::from_addr_pair(local, remote); + let reachability_flags = reachability.reachability().unwrap(); + // Verify established tcp connection path is reported as reachable. + assert!(reachability_flags.contains(ReachabilityFlags::REACHABLE)); + } + } + } + } + #[test] fn test_reachability_ref_from_host() { let valid_inputs = vec!["example.com", "host-in-local-network", "en0"];