#![allow(clippy::expect_used)]
use std::io;
use std::net::SocketAddr;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Domain {
V4,
V6,
}
#[repr(C)]
pub struct SockAddr {
storage: libc::sockaddr_storage,
len: libc::socklen_t,
}
impl std::fmt::Debug for SockAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SockAddr").field("len", &self.len).finish()
}
}
impl SockAddr {
#[must_use]
pub fn from_std(addr: SocketAddr) -> Self {
let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let len = match addr {
SocketAddr::V4(v4) => {
let raw = libc::sockaddr_in {
sin_family: u16::try_from(libc::AF_INET).unwrap_or(libc::AF_INET as u16),
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 {
let dst = (&raw mut storage).cast::<libc::sockaddr_in>();
std::ptr::write(dst, raw);
}
u32::try_from(std::mem::size_of::<libc::sockaddr_in>())
.expect("sockaddr_in size fits in socklen_t")
}
SocketAddr::V6(v6) => {
let raw = libc::sockaddr_in6 {
sin6_family: u16::try_from(libc::AF_INET6).unwrap_or(libc::AF_INET6 as u16),
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 {
let dst = (&raw mut storage).cast::<libc::sockaddr_in6>();
std::ptr::write(dst, raw);
}
u32::try_from(std::mem::size_of::<libc::sockaddr_in6>())
.expect("sockaddr_in6 size fits in socklen_t")
}
};
Self { storage, len }
}
#[must_use]
pub const fn as_ptr(&self) -> *const libc::sockaddr {
(&raw const self.storage).cast::<libc::sockaddr>()
}
#[must_use]
pub const fn len(&self) -> libc::socklen_t {
self.len
}
}
#[derive(Debug)]
pub struct TcpSocket {
fd: OwnedFd,
}
impl TcpSocket {
pub fn new(domain: Domain) -> io::Result<Self> {
let af = match domain {
Domain::V4 => libc::AF_INET,
Domain::V6 => libc::AF_INET6,
};
let raw = unsafe { libc::socket(af, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
if raw < 0 {
return Err(io::Error::last_os_error());
}
let fd = unsafe { OwnedFd::from_raw_fd(raw) };
Ok(Self { fd })
}
pub fn set_nodelay(&self, on: bool) -> io::Result<()> {
let val: libc::c_int = on.into();
let rc = unsafe {
libc::setsockopt(
self.fd.as_raw_fd(),
libc::IPPROTO_TCP,
libc::TCP_NODELAY,
(&raw const val).cast::<libc::c_void>(),
u32::try_from(std::mem::size_of_val(&val)).expect("c_int size fits in socklen_t"),
)
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub fn set_reuseaddr(&self, on: bool) -> io::Result<()> {
let val: libc::c_int = on.into();
let rc = unsafe {
libc::setsockopt(
self.fd.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_REUSEADDR,
(&raw const val).cast::<libc::c_void>(),
u32::try_from(std::mem::size_of_val(&val)).expect("c_int size fits in socklen_t"),
)
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
#[must_use]
pub fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
#[test]
fn sockaddr_v4_layout() {
let std_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080));
let sa = SockAddr::from_std(std_addr);
assert_eq!(sa.len() as usize, std::mem::size_of::<libc::sockaddr_in>());
let in4 = unsafe { &*sa.as_ptr().cast::<libc::sockaddr_in>() };
assert_eq!(u16::from_be(in4.sin_port), 8080);
let expected = u32::from_ne_bytes([127, 0, 0, 1]);
assert_eq!(in4.sin_addr.s_addr, expected);
}
#[test]
fn sockaddr_v6_layout() {
let std_addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 9090, 0, 0));
let sa = SockAddr::from_std(std_addr);
assert_eq!(sa.len() as usize, std::mem::size_of::<libc::sockaddr_in6>());
let in6 = unsafe { &*sa.as_ptr().cast::<libc::sockaddr_in6>() };
assert_eq!(u16::from_be(in6.sin6_port), 9090);
}
#[test]
fn socket_create_and_drop_v4() {
let s = TcpSocket::new(Domain::V4).unwrap();
assert!(s.as_raw_fd() >= 0);
}
#[test]
fn socket_create_and_drop_v6() {
let s = TcpSocket::new(Domain::V6).unwrap();
assert!(s.as_raw_fd() >= 0);
}
#[test]
fn set_nodelay_succeeds() {
let s = TcpSocket::new(Domain::V4).unwrap();
s.set_nodelay(true).unwrap();
}
}