Skip to content
Merged
80 changes: 62 additions & 18 deletions src/sys/unix/net.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::net::SocketAddr;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};

pub(crate) fn new_ip_socket(
addr: SocketAddr,
Expand Down Expand Up @@ -64,32 +64,76 @@ pub(crate) fn new_socket(
socket
}

pub(crate) fn socket_addr(addr: &SocketAddr) -> (*const libc::sockaddr, libc::socklen_t) {
use std::mem::size_of_val;
/// A type with the same memory layout as `libc::sockaddr`. Used in converting Rust level
/// SocketAddr* types into their system representation. The benefit of this specific
/// type over using `libc::sockaddr_storage` is that this type is exactly as large as it
/// needs to be and not a lot larger. And it can be initialized cleaner from Rust.
#[repr(C)]
pub(crate) union SocketAddrCRepr {
v4: libc::sockaddr_in,
v6: libc::sockaddr_in6,
}

impl SocketAddrCRepr {
pub fn as_ptr(&self) -> *const libc::sockaddr {
self as *const _ as *const libc::sockaddr
}
}

/// Converts a Rust `SocketAddr` into the system representation.
pub(crate) fn socket_addr(addr: &SocketAddr) -> (SocketAddrCRepr, libc::socklen_t) {
use std::mem;

match addr {
SocketAddr::V4(ref addr) => (
addr as *const _ as *const libc::sockaddr,
size_of_val(addr) as libc::socklen_t,
),
SocketAddr::V6(ref addr) => (
addr as *const _ as *const libc::sockaddr,
size_of_val(addr) as libc::socklen_t,
),
SocketAddr::V4(ref a) => {
// `s_addr` is stored as BE on all machine and the array is in BE order.
// So the native endian conversion method is used so that it's never swapped.
let sin_addr = libc::in_addr { s_addr: u32::from_ne_bytes(a.ip().octets()) };

let sockaddr_in = libc::sockaddr_in {
sin_family: libc::AF_INET as libc::sa_family_t,
sin_port: a.port().to_be(),
sin_addr,
..unsafe { mem::zeroed() }
};

let sockaddr = SocketAddrCRepr { v4: sockaddr_in };
(sockaddr, mem::size_of::<libc::sockaddr_in>() as libc::socklen_t)
}
SocketAddr::V6(ref a) => {
let sockaddr_in6 = libc::sockaddr_in6 {
sin6_family: libc::AF_INET6 as libc::sa_family_t,
sin6_port: a.port().to_be(),
sin6_addr: libc::in6_addr { s6_addr: a.ip().octets() },
sin6_flowinfo: a.flowinfo(),
sin6_scope_id: a.scope_id(),
..unsafe { mem::zeroed() }
};

let sockaddr = SocketAddrCRepr { v6: sockaddr_in6 };
(sockaddr, mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t)
}
}
}

/// `storage` must be initialised to `sockaddr_in` or `sockaddr_in6`.
/// Converts a `libc::sockaddr` compatible struct into a native Rust `SocketAddr`.
/// SAFETY: `storage` must be initialised to `sockaddr_in` or `sockaddr_in6`.
pub(crate) unsafe fn to_socket_addr(
storage: *const libc::sockaddr_storage,
) -> std::io::Result<SocketAddr> {
match (*storage).ss_family as libc::c_int {
libc::AF_INET => Ok(SocketAddr::V4(
*(storage as *const libc::sockaddr_in as *const _),
)),
libc::AF_INET6 => Ok(SocketAddr::V6(
*(storage as *const libc::sockaddr_in6 as *const _),
)),
libc::AF_INET => {
let addr: &libc::sockaddr_in = &*(storage as *const libc::sockaddr_in);
let ip = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes());
let port = u16::from_be(addr.sin_port);
Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
},
libc::AF_INET6 => {
let addr: &libc::sockaddr_in6 = &*(storage as *const libc::sockaddr_in6);
let ip = Ipv6Addr::from(addr.sin6_addr.s6_addr);
let port = u16::from_be(addr.sin6_port);
Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, addr.sin6_flowinfo, addr.sin6_scope_id)))
},
_ => Err(std::io::ErrorKind::InvalidInput.into()),
}
}
4 changes: 2 additions & 2 deletions src/sys/unix/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ pub(crate) fn new_v6_socket() -> io::Result<TcpSocket> {

pub(crate) fn bind(socket: TcpSocket, addr: SocketAddr) -> io::Result<()> {
let (raw_addr, raw_addr_length) = socket_addr(&addr);
syscall!(bind(socket, raw_addr, raw_addr_length))?;
syscall!(bind(socket, raw_addr.as_ptr(), raw_addr_length))?;
Ok(())
}

pub(crate) fn connect(socket: TcpSocket, addr: SocketAddr) -> io::Result<net::TcpStream> {
let (raw_addr, raw_addr_length) = socket_addr(&addr);

match syscall!(connect(socket, raw_addr, raw_addr_length)) {
match syscall!(connect(socket, raw_addr.as_ptr(), raw_addr_length)) {
Err(err) if err.raw_os_error() != Some(libc::EINPROGRESS) => {
Err(err)
}
Expand Down
2 changes: 1 addition & 1 deletion src/sys/unix/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn bind(addr: SocketAddr) -> io::Result<net::UdpSocket> {

socket.and_then(|socket| {
let (raw_addr, raw_addr_length) = socket_addr(&addr);
syscall!(bind(socket, raw_addr, raw_addr_length))
syscall!(bind(socket, raw_addr.as_ptr(), raw_addr_length))
.map_err(|err| {
// Close the socket if we hit an error, ignoring the error
// from closing since we can't pass back two errors.
Expand Down