use std::io;
use std::mem;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
pub fn sockaddr_to_std(
storage: &libc::sockaddr_storage,
len: libc::socklen_t,
) -> io::Result<SocketAddr> {
let len = usize::try_from(len).unwrap_or(0);
let family = i32::from(storage.ss_family);
if family == libc::AF_INET {
if len < mem::size_of::<libc::sockaddr_in>() {
return Err(io::Error::other("sockaddr_in shorter than expected"));
}
let sin = unsafe {
std::ptr::read_unaligned(std::ptr::from_ref(storage).cast::<libc::sockaddr_in>())
};
let ip = Ipv4Addr::from(sin.sin_addr.s_addr.to_ne_bytes());
let port = u16::from_be(sin.sin_port);
return Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)));
}
if family == libc::AF_INET6 {
if len < mem::size_of::<libc::sockaddr_in6>() {
return Err(io::Error::other("sockaddr_in6 shorter than expected"));
}
let sin6 = unsafe {
std::ptr::read_unaligned(std::ptr::from_ref(storage).cast::<libc::sockaddr_in6>())
};
let ip = Ipv6Addr::from(sin6.sin6_addr.s6_addr);
let port = u16::from_be(sin6.sin6_port);
return Ok(SocketAddr::V6(SocketAddrV6::new(
ip,
port,
sin6.sin6_flowinfo,
sin6.sin6_scope_id,
)));
}
Err(io::Error::other(format!(
"unsupported sockaddr family: {family}"
)))
}
pub fn std_to_sockaddr(addr: SocketAddr) -> (libc::sockaddr_storage, libc::socklen_t) {
let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
match addr {
SocketAddr::V4(v4) => {
let sin = libc::sockaddr_in {
sin_family: libc::sa_family_t::try_from(libc::AF_INET)
.expect("AF_INET fits sa_family_t"),
sin_port: v4.port().to_be(),
sin_addr: libc::in_addr {
s_addr: u32::from_ne_bytes(v4.ip().octets()),
},
sin_zero: [0; 8],
};
unsafe {
std::ptr::write_unaligned(
std::ptr::from_mut(&mut storage).cast::<libc::sockaddr_in>(),
sin,
);
}
let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_in>())
.expect("size_of::<sockaddr_in>() fits socklen_t");
(storage, len)
}
SocketAddr::V6(v6) => {
let sin6 = libc::sockaddr_in6 {
sin6_family: libc::sa_family_t::try_from(libc::AF_INET6)
.expect("AF_INET6 fits sa_family_t"),
sin6_port: v6.port().to_be(),
sin6_flowinfo: v6.flowinfo(),
sin6_addr: libc::in6_addr {
s6_addr: v6.ip().octets(),
},
sin6_scope_id: v6.scope_id(),
};
unsafe {
std::ptr::write_unaligned(
std::ptr::from_mut(&mut storage).cast::<libc::sockaddr_in6>(),
sin6,
);
}
let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_in6>())
.expect("size_of::<sockaddr_in6>() fits socklen_t");
(storage, len)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v4_roundtrips() {
let addr: SocketAddr = "203.0.113.7:51820".parse().unwrap();
let (storage, len) = std_to_sockaddr(addr);
let back = sockaddr_to_std(&storage, len).unwrap();
assert_eq!(back, addr);
}
#[test]
fn v4_loopback_roundtrips() {
let addr: SocketAddr = "127.0.0.1:9".parse().unwrap();
let (storage, len) = std_to_sockaddr(addr);
let back = sockaddr_to_std(&storage, len).unwrap();
assert_eq!(back, addr);
}
#[test]
fn v6_roundtrips() {
let addr: SocketAddr = "[2001:db8::1]:443".parse().unwrap();
let (storage, len) = std_to_sockaddr(addr);
let back = sockaddr_to_std(&storage, len).unwrap();
assert_eq!(back, addr);
}
#[test]
fn truncated_length_is_rejected() {
let addr: SocketAddr = "203.0.113.7:51820".parse().unwrap();
let (storage, _) = std_to_sockaddr(addr);
let too_short = libc::socklen_t::try_from(2usize).unwrap();
assert!(sockaddr_to_std(&storage, too_short).is_err());
}
#[test]
fn unknown_family_is_rejected() {
let storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_storage>()).unwrap();
assert!(sockaddr_to_std(&storage, len).is_err());
}
}