Skip to main content

yip_io/
addr.rs

1//! `sockaddr` ⇄ [`SocketAddr`] conversions for the addressed socket seam.
2//!
3//! `recvfrom`/`recvmsg`/`sendto`/`sendmsg` all speak `libc::sockaddr_storage`
4//! (IPv4 *or* IPv6, selected by `ss_family`); the rest of yip works with
5//! `std::net::SocketAddr`. These two helpers are the only place that bridges
6//! the two representations, and the only new `unsafe` this task introduces
7//! (confined to `yip-io`, per the crate's `unsafe`-only-here contract).
8
9use std::io;
10use std::mem;
11use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
12
13/// Convert a `sockaddr_storage` (as filled in by `recvfrom`/`recvmsg`) into a
14/// [`SocketAddr`], given the address length the kernel reported.
15///
16/// # Errors
17///
18/// Returns an error if `len` is too short for the address family the kernel
19/// reported, or if the family is neither `AF_INET` nor `AF_INET6`.
20pub fn sockaddr_to_std(
21    storage: &libc::sockaddr_storage,
22    len: libc::socklen_t,
23) -> io::Result<SocketAddr> {
24    let len = usize::try_from(len).unwrap_or(0);
25    let family = i32::from(storage.ss_family);
26
27    if family == libc::AF_INET {
28        if len < mem::size_of::<libc::sockaddr_in>() {
29            return Err(io::Error::other("sockaddr_in shorter than expected"));
30        }
31        // SAFETY: `storage.ss_family == AF_INET` and `len` covers a full
32        // `sockaddr_in`, so reinterpreting the start of `storage` (a
33        // `sockaddr_storage`, which is defined to be large enough and
34        // suitably aligned for any address family) as `sockaddr_in` reads
35        // only initialized bytes of a compatible layout.
36        let sin = unsafe {
37            std::ptr::read_unaligned(std::ptr::from_ref(storage).cast::<libc::sockaddr_in>())
38        };
39        let ip = Ipv4Addr::from(sin.sin_addr.s_addr.to_ne_bytes());
40        let port = u16::from_be(sin.sin_port);
41        return Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)));
42    }
43
44    if family == libc::AF_INET6 {
45        if len < mem::size_of::<libc::sockaddr_in6>() {
46            return Err(io::Error::other("sockaddr_in6 shorter than expected"));
47        }
48        // SAFETY: same rationale as the `AF_INET` arm above, for `sockaddr_in6`.
49        let sin6 = unsafe {
50            std::ptr::read_unaligned(std::ptr::from_ref(storage).cast::<libc::sockaddr_in6>())
51        };
52        let ip = Ipv6Addr::from(sin6.sin6_addr.s6_addr);
53        let port = u16::from_be(sin6.sin6_port);
54        return Ok(SocketAddr::V6(SocketAddrV6::new(
55            ip,
56            port,
57            sin6.sin6_flowinfo,
58            sin6.sin6_scope_id,
59        )));
60    }
61
62    Err(io::Error::other(format!(
63        "unsupported sockaddr family: {family}"
64    )))
65}
66
67/// Convert a [`SocketAddr`] into a `sockaddr_storage` + length suitable for
68/// `sendto`/`sendmsg`'s `msg_name`/`msg_namelen`.
69pub fn std_to_sockaddr(addr: SocketAddr) -> (libc::sockaddr_storage, libc::socklen_t) {
70    // SAFETY: `sockaddr_storage` is a plain-old-data struct of integers and
71    // byte arrays; the all-zero bit pattern is a valid value for all of them.
72    let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
73
74    match addr {
75        SocketAddr::V4(v4) => {
76            let sin = libc::sockaddr_in {
77                sin_family: libc::sa_family_t::try_from(libc::AF_INET)
78                    .expect("AF_INET fits sa_family_t"),
79                sin_port: v4.port().to_be(),
80                sin_addr: libc::in_addr {
81                    s_addr: u32::from_ne_bytes(v4.ip().octets()),
82                },
83                sin_zero: [0; 8],
84            };
85            // SAFETY: `sockaddr_storage` is defined to be large enough and
86            // suitably aligned to hold any address family's sockaddr, so
87            // writing a fully-initialized `sockaddr_in` at its start is valid.
88            unsafe {
89                std::ptr::write_unaligned(
90                    std::ptr::from_mut(&mut storage).cast::<libc::sockaddr_in>(),
91                    sin,
92                );
93            }
94            let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_in>())
95                .expect("size_of::<sockaddr_in>() fits socklen_t");
96            (storage, len)
97        }
98        SocketAddr::V6(v6) => {
99            let sin6 = libc::sockaddr_in6 {
100                sin6_family: libc::sa_family_t::try_from(libc::AF_INET6)
101                    .expect("AF_INET6 fits sa_family_t"),
102                sin6_port: v6.port().to_be(),
103                sin6_flowinfo: v6.flowinfo(),
104                sin6_addr: libc::in6_addr {
105                    s6_addr: v6.ip().octets(),
106                },
107                sin6_scope_id: v6.scope_id(),
108            };
109            // SAFETY: same rationale as the `V4` arm above, for `sockaddr_in6`.
110            unsafe {
111                std::ptr::write_unaligned(
112                    std::ptr::from_mut(&mut storage).cast::<libc::sockaddr_in6>(),
113                    sin6,
114                );
115            }
116            let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_in6>())
117                .expect("size_of::<sockaddr_in6>() fits socklen_t");
118            (storage, len)
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn v4_roundtrips() {
129        let addr: SocketAddr = "203.0.113.7:51820".parse().unwrap();
130        let (storage, len) = std_to_sockaddr(addr);
131        let back = sockaddr_to_std(&storage, len).unwrap();
132        assert_eq!(back, addr);
133    }
134
135    #[test]
136    fn v4_loopback_roundtrips() {
137        let addr: SocketAddr = "127.0.0.1:9".parse().unwrap();
138        let (storage, len) = std_to_sockaddr(addr);
139        let back = sockaddr_to_std(&storage, len).unwrap();
140        assert_eq!(back, addr);
141    }
142
143    #[test]
144    fn v6_roundtrips() {
145        let addr: SocketAddr = "[2001:db8::1]:443".parse().unwrap();
146        let (storage, len) = std_to_sockaddr(addr);
147        let back = sockaddr_to_std(&storage, len).unwrap();
148        assert_eq!(back, addr);
149    }
150
151    #[test]
152    fn truncated_length_is_rejected() {
153        let addr: SocketAddr = "203.0.113.7:51820".parse().unwrap();
154        let (storage, _) = std_to_sockaddr(addr);
155        let too_short = libc::socklen_t::try_from(2usize).unwrap();
156        assert!(sockaddr_to_std(&storage, too_short).is_err());
157    }
158
159    #[test]
160    fn unknown_family_is_rejected() {
161        // SAFETY: all-zero sockaddr_storage is a valid bit pattern; ss_family
162        // == 0 (AF_UNSPEC) is neither AF_INET nor AF_INET6.
163        let storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
164        let len = libc::socklen_t::try_from(mem::size_of::<libc::sockaddr_storage>()).unwrap();
165        assert!(sockaddr_to_std(&storage, len).is_err());
166    }
167}