src/sockaddr.rs
654 lines · rust · 2 line annotations
use std::hash::Hash;use std::mem::{self, size_of};use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};#[cfg(not(target_os = "wasi"))]use std::path::Path;use std::{fmt, io, ptr};#[cfg(windows)]use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6_0;#[cfg(not(target_os = "wasi"))]use crate::sys::AF_UNIX;use crate::sys::{c_int, sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6};use crate::Domain;/// The integer type used with `getsockname` on this platform.#[allow(non_camel_case_types)]pub type socklen_t = crate::sys::socklen_t;/// The integer type for the `ss_family` field on this platform.#[allow(non_camel_case_types)]pub type sa_family_t = crate::sys::sa_family_t;/// Rust version of the [`sockaddr_storage`] type.////// This type is intended to be used with with direct calls to the `getsockname` syscall. See the/// documentation of [`SockAddr::new`] for examples.////// This crate defines its own `sockaddr_storage` type to avoid semver concerns with upgrading/// `windows-sys`.#[repr(transparent)]pub struct SockAddrStorage { storage: sockaddr_storage,}impl SockAddrStorage { /// Construct a new storage containing all zeros. #[inline] pub fn zeroed() -> Self { // SAFETY: All zeros is valid for this type. unsafe { mem::zeroed() } } /// Returns the size of this storage. #[inline] pub fn size_of(&self) -> socklen_t { size_of::<Self>() as socklen_t } /// View this type as another type. /// /// # Safety /// /// The type `T` must be one of the `sockaddr_*` types defined by this platform. /// /// # Examples /// ``` /// # #[allow(dead_code)] /// # #[cfg(unix)] mod unix_example { /// # use core::mem::size_of; /// use libc::sockaddr_storage; /// use socket2::{SockAddr, SockAddrStorage, socklen_t}; /// /// fn from_sockaddr_storage(recv_address: &sockaddr_storage) -> SockAddr { /// let mut storage = SockAddrStorage::zeroed(); /// let libc_address = unsafe { storage.view_as::<sockaddr_storage>() }; /// *libc_address = *recv_address; /// unsafe { SockAddr::new(storage, size_of::<sockaddr_storage>() as socklen_t) } /// } /// # } /// ``` #[inline] pub unsafe fn view_as<T>(&mut self) -> &mut T { assert!(size_of::<T>() <= size_of::<Self>()); // SAFETY: This type is repr(transparent) over `sockaddr_storage` and `T` is one of the // `sockaddr_*` types defined by this platform. &mut *(self as *mut Self as *mut T) }}impl std::fmt::Debug for SockAddrStorage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("sockaddr_storage") .field("ss_family", &self.storage.ss_family) .finish_non_exhaustive() }}/// The address of a socket.////// `SockAddr`s may be constructed directly to and from the standard library/// [`SocketAddr`], [`SocketAddrV4`], and [`SocketAddrV6`] types.#[derive(Clone)]pub struct SockAddr { storage: sockaddr_storage, len: socklen_t,}#[allow(clippy::len_without_is_empty)]impl SockAddr { /// Create a `SockAddr` from the underlying storage and its length. /// /// # Safety /// /// Caller must ensure that the address family and length match the type of /// storage address. For example if `storage.ss_family` is set to `AF_INET` /// the `storage` must be initialised as `sockaddr_in`, setting the content /// and length appropriately. /// /// # Examples /// /// ``` /// # fn main() -> std::io::Result<()> { /// # #[cfg(unix)] { /// use std::io; /// use std::os::fd::AsRawFd; /// /// use socket2::{SockAddr, SockAddrStorage, Socket, Domain, Type}; /// /// let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; /// /// // Initialise a `SocketAddr` by calling `getsockname(2)`. /// let mut addr_storage = SockAddrStorage::zeroed(); /// let mut len = addr_storage.size_of(); /// /// // The `getsockname(2)` system call will initialize `storage` for /// // us, setting `len` to the correct length. /// let res = unsafe { /// libc::getsockname( /// socket.as_raw_fd(), /// addr_storage.view_as(), /// &mut len, /// ) /// }; /// if res == -1 { /// return Err(io::Error::last_os_error()); /// } /// /// let address = unsafe { SockAddr::new(addr_storage, len) }; /// # drop(address); /// # } /// # Ok(()) /// # } /// ``` pub const unsafe fn new(storage: SockAddrStorage, len: socklen_t) -> SockAddr { SockAddr { storage: storage.storage, len: len as socklen_t, } } /// Initialise a `SockAddr` by calling the function `init`. /// /// The type of the address storage and length passed to the function `init` /// is OS/architecture specific. /// /// The address is zeroed before `init` is called and is thus valid to /// dereference and read from. The length initialised to the maximum length /// of the storage. /// /// # Safety /// /// Caller must ensure that the address family and length match the type of /// storage address. For example if `storage.ss_family` is set to `AF_INET` /// the `storage` must be initialised as `sockaddr_in`, setting the content /// and length appropriately. /// /// # Examples /// /// ``` /// # fn main() -> std::io::Result<()> { /// # #[cfg(unix)] { /// use std::io; /// use std::os::fd::AsRawFd; /// /// use socket2::{SockAddr, Socket, Domain, Type}; /// /// let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; /// /// // Initialise a `SocketAddr` by calling `getsockname(2)`. /// let (_, address) = unsafe { /// SockAddr::try_init(|addr_storage, len| { /// // The `getsockname(2)` system call will initialize `storage` for /// // us, setting `len` to the correct length. /// if libc::getsockname(socket.as_raw_fd(), addr_storage.cast(), len) == -1 { /// Err(io::Error::last_os_error()) /// } else { /// Ok(()) /// } /// }) /// }?; /// # drop(address); /// # } /// # Ok(()) /// # } /// ``` pub unsafe fn try_init<F, T>(init: F) -> io::Result<(T, SockAddr)> where F: FnOnce(*mut SockAddrStorage, *mut socklen_t) -> io::Result<T>, { const STORAGE_SIZE: socklen_t = size_of::<sockaddr_storage>() as socklen_t; // NOTE: `SockAddr::unix` depends on the storage being zeroed before // calling `init`. // NOTE: calling `recvfrom` with an empty buffer also depends on the // storage being zeroed before calling `init` as the OS might not // initialise it. let mut storage = SockAddrStorage::zeroed(); let mut len = STORAGE_SIZE; init(&mut storage, &mut len).map(|res| { debug_assert!(len <= STORAGE_SIZE, "overflown address storage"); (res, SockAddr::new(storage, len)) }) }SockAddr::new and SockAddr::try_init are unsafe fn requiring the caller to ensure ss_family and length match the actual contents. try_init zeroes the storage first (important for recvfrom with zero-length buffers and for Unix sockets), then delegates to a caller-supplied closure. Both functions have clear, accurate safety contracts in their doc comments. as_socket checks ss_family before casting to sockaddr_in / sockaddr_in6; uses ptr::addr_of! to avoid creating references to potentially unaligned/uninit memory. All address-family checks precede the casts. SockAddrStorage::view_as asserts size_of::<T>() <= size_of::<Self>() before the pointer cast.
Supports unsafe-safe, unsafe-documented.
/// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. /// /// Returns an error if the path is longer than `SUN_LEN`. #[cfg(not(target_os = "wasi"))] pub fn unix<P>(path: P) -> io::Result<SockAddr> where P: AsRef<Path>, { crate::sys::unix_sockaddr(path.as_ref()) } /// Set the length of the address. /// /// # Safety /// /// Caller must ensure that the address up to `length` bytes are properly /// initialised. pub unsafe fn set_length(&mut self, length: socklen_t) { self.len = length; } /// Returns this address's family. pub const fn family(&self) -> sa_family_t { self.storage.ss_family } /// Returns this address's `Domain`. pub const fn domain(&self) -> Domain { Domain(self.storage.ss_family as c_int) } /// Returns the size of this address in bytes. pub const fn len(&self) -> socklen_t { self.len } /// Returns a raw pointer to the address. pub const fn as_ptr(&self) -> *const SockAddrStorage { &self.storage as *const sockaddr_storage as *const SockAddrStorage } /// Returns the address as the storage. pub const fn as_storage(self) -> SockAddrStorage { SockAddrStorage { storage: self.storage, } } /// Returns true if this address is in the `AF_INET` (IPv4) family, false otherwise. pub const fn is_ipv4(&self) -> bool { self.storage.ss_family == AF_INET as sa_family_t } /// Returns true if this address is in the `AF_INET6` (IPv6) family, false /// otherwise. pub const fn is_ipv6(&self) -> bool { self.storage.ss_family == AF_INET6 as sa_family_t } /// Returns true if this address is of a unix socket (for local interprocess communication), /// i.e. it is from the `AF_UNIX` family, false otherwise. #[cfg(not(target_os = "wasi"))] pub fn is_unix(&self) -> bool { self.storage.ss_family == AF_UNIX as sa_family_t } /// Returns this address as a `SocketAddr` if it is in the `AF_INET` (IPv4) /// or `AF_INET6` (IPv6) family, otherwise returns `None`. pub fn as_socket(&self) -> Option<SocketAddr> { if self.storage.ss_family == AF_INET as sa_family_t { // SAFETY: if the `ss_family` field is `AF_INET` then storage must // be a `sockaddr_in`. let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::<sockaddr_in>()) }; let ip = crate::sys::from_in_addr(addr.sin_addr); let port = u16::from_be(addr.sin_port); Some(SocketAddr::V4(SocketAddrV4::new(ip, port))) } else if self.storage.ss_family == AF_INET6 as sa_family_t { // SAFETY: if the `ss_family` field is `AF_INET6` then storage must // be a `sockaddr_in6`. let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::<sockaddr_in6>()) }; let ip = crate::sys::from_in6_addr(addr.sin6_addr); let port = u16::from_be(addr.sin6_port); Some(SocketAddr::V6(SocketAddrV6::new( ip, port, addr.sin6_flowinfo, #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] addr.sin6_scope_id, #[cfg(windows)] unsafe { addr.Anonymous.sin6_scope_id }, ))) } else { None } } /// Returns this address as a [`SocketAddrV4`] if it is in the `AF_INET` /// family. pub fn as_socket_ipv4(&self) -> Option<SocketAddrV4> { match self.as_socket() { Some(SocketAddr::V4(addr)) => Some(addr), _ => None, } } /// Returns this address as a [`SocketAddrV6`] if it is in the `AF_INET6` /// family. pub fn as_socket_ipv6(&self) -> Option<SocketAddrV6> { match self.as_socket() { Some(SocketAddr::V6(addr)) => Some(addr), _ => None, } } /// Returns the initialised storage bytes. fn as_bytes(&self) -> &[u8] { // SAFETY: `self.storage` is a C struct which can always be treated a // slice of bytes. Furthermore, we ensure we don't read any uninitialised // bytes by using `self.len`. unsafe { std::slice::from_raw_parts(self.as_ptr().cast(), self.len as usize) } }}impl From<SocketAddr> for SockAddr { fn from(addr: SocketAddr) -> SockAddr { match addr { SocketAddr::V4(addr) => addr.into(), SocketAddr::V6(addr) => addr.into(), } }}impl From<SocketAddrV4> for SockAddr { fn from(addr: SocketAddrV4) -> SockAddr { // SAFETY: a `sockaddr_storage` of all zeros is valid. let mut storage = unsafe { mem::zeroed::<sockaddr_storage>() }; let len = { let storage = unsafe { &mut *ptr::addr_of_mut!(storage).cast::<sockaddr_in>() }; storage.sin_family = AF_INET as sa_family_t; storage.sin_port = addr.port().to_be(); storage.sin_addr = crate::sys::to_in_addr(addr.ip()); #[cfg(not(target_os = "wasi"))] { storage.sin_zero = Default::default(); } mem::size_of::<sockaddr_in>() as socklen_t }; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] { storage.ss_len = len as u8; } SockAddr { storage, len } }}impl From<SocketAddrV6> for SockAddr { fn from(addr: SocketAddrV6) -> SockAddr { // SAFETY: a `sockaddr_storage` of all zeros is valid. let mut storage = unsafe { mem::zeroed::<sockaddr_storage>() }; let len = { let storage = unsafe { &mut *ptr::addr_of_mut!(storage).cast::<sockaddr_in6>() }; storage.sin6_family = AF_INET6 as sa_family_t; storage.sin6_port = addr.port().to_be(); storage.sin6_addr = crate::sys::to_in6_addr(addr.ip()); storage.sin6_flowinfo = addr.flowinfo(); #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] { storage.sin6_scope_id = addr.scope_id(); } #[cfg(windows)] { storage.Anonymous = SOCKADDR_IN6_0 { sin6_scope_id: addr.scope_id(), }; } mem::size_of::<sockaddr_in6>() as socklen_t }; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] { storage.ss_len = len as u8; } SockAddr { storage, len } }}impl fmt::Debug for SockAddr { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = fmt.debug_struct("SockAddr"); #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] f.field("ss_len", &self.storage.ss_len); f.field("ss_family", &self.storage.ss_family) .field("len", &self.len) .finish() }}impl PartialEq for SockAddr { fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() }}impl Eq for SockAddr {}impl Hash for SockAddr { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.as_bytes().hash(state); }}#[cfg(test)]mod tests { use super::*; #[test] fn ipv4() { use std::net::Ipv4Addr; let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let addr = SockAddr::from(std); assert!(addr.is_ipv4()); assert!(!addr.is_ipv6()); #[cfg(not(target_os = "wasi"))] assert!(!addr.is_unix()); assert_eq!(addr.family(), AF_INET as sa_family_t); assert_eq!(addr.domain(), Domain::IPV4); assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std))); assert_eq!(addr.as_socket_ipv4(), Some(std)); assert!(addr.as_socket_ipv6().is_none()); let addr = SockAddr::from(SocketAddr::from(std)); assert_eq!(addr.family(), AF_INET as sa_family_t); assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std))); assert_eq!(addr.as_socket_ipv4(), Some(std)); assert!(addr.as_socket_ipv6().is_none()); #[cfg(all(unix, not(target_os = "wasi")))] { assert!(addr.as_pathname().is_none()); assert!(addr.as_abstract_namespace().is_none()); } } #[test] fn ipv6() { use std::net::Ipv6Addr; let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let addr = SockAddr::from(std); assert!(addr.is_ipv6()); assert!(!addr.is_ipv4()); #[cfg(not(target_os = "wasi"))] assert!(!addr.is_unix()); assert_eq!(addr.family(), AF_INET6 as sa_family_t); assert_eq!(addr.domain(), Domain::IPV6); assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std))); assert!(addr.as_socket_ipv4().is_none()); assert_eq!(addr.as_socket_ipv6(), Some(std)); let addr = SockAddr::from(SocketAddr::from(std)); assert_eq!(addr.family(), AF_INET6 as sa_family_t); assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std))); assert!(addr.as_socket_ipv4().is_none()); assert_eq!(addr.as_socket_ipv6(), Some(std)); #[cfg(all(unix, not(target_os = "wasi")))] { assert!(addr.as_pathname().is_none()); assert!(addr.as_abstract_namespace().is_none()); } } #[test] fn ipv4_eq() { use std::net::Ipv4Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv4_hash() { use std::net::Ipv4Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv6_eq() { use std::net::Ipv6Addr; let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv6_hash() { use std::net::Ipv6Addr; let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv4_ipv6_eq() { use std::net::Ipv4Addr; use std::net::Ipv6Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); test_eq( SockAddr::from(std2), SockAddr::from(std2), SockAddr::from(std1), ); } #[test] fn ipv4_ipv6_hash() { use std::net::Ipv4Addr; use std::net::Ipv6Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); test_hash( SockAddr::from(std2), SockAddr::from(std2), SockAddr::from(std1), ); } #[allow(clippy::eq_op)] // allow a0 == a0 check fn test_eq(a0: SockAddr, a1: SockAddr, b: SockAddr) { assert!(a0 == a0); assert!(a0 == a1); assert!(a1 == a0); assert!(a0 != b); assert!(b != a0); } fn test_hash(a0: SockAddr, a1: SockAddr, b: SockAddr) { assert!(calculate_hash(&a0) == calculate_hash(&a0)); assert!(calculate_hash(&a0) == calculate_hash(&a1)); // technically unequal values can have the same hash, in this case x != z and both have different hashes assert!(calculate_hash(&a0) != calculate_hash(&b)); } fn calculate_hash(x: &SockAddr) -> u64 { use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; let mut hasher = DefaultHasher::new(); x.hash(&mut hasher); hasher.finish() }}The package ships 12 #[test] unit tests in src/sockaddr.rs and src/sys/unix.rs covering IPv4/IPv6 address round-trips, equality, and hashing. The VCS repository contains an additional 60-test integration test suite in tests/socket.rs covering socket creation, binding, options, and platform-specific calls. The integration tests are not included in the published crate. The CI configuration (.github/) runs tests on Linux, macOS, Windows, and multiple targets.
Supports has-unit-tests, has-integration-tests, unsafe-tested.