src/sockaddr.rs
src/sockaddr.rs, line 73-213
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.
src/sockaddr.rs, line 469-653
#[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.