blob: 8dd00e60200a5e58db09721e66a31532dcab7eea [file]
//@ignore-target: windows # No libc socket on Windows
//@compile-flags: -Zmiri-disable-isolation
#![feature(io_error_inprogress)]
#[path = "../../utils/libc.rs"]
mod libc_utils;
use std::io::{self, ErrorKind};
use std::time::Duration;
#[allow(unused)]
use std::{mem::MaybeUninit, thread};
use libc_utils::*;
fn main() {
test_socket_close();
test_bind_ipv4();
test_bind_ipv4_reuseaddr();
test_set_reuseaddr_invalid_len();
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
{
test_bind_ipv4_nosigpipe();
test_set_nosigpipe_invalid_len();
}
test_bind_ipv4_invalid_addr_len();
test_bind_ipv6();
test_listen();
test_accept_connect();
test_getsockname_ipv4();
test_getsockname_ipv4_random_port();
test_getsockname_ipv4_unbound();
test_getsockname_ipv6();
test_getpeername_ipv4();
test_getpeername_ipv6();
}
fn test_socket_close() {
unsafe {
let sockfd = errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap();
errno_check(libc::close(sockfd));
}
}
fn test_bind_ipv4() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
}
/// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket.
fn test_bind_ipv4_reuseaddr() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int).unwrap();
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
}
/// Tests setting the `SO_REUSEADDR` socket option but with an invalid length.
fn test_set_reuseaddr_invalid_len() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Value should be of type `libc::c_int` which has size 4 bytes.
// By providing a u64 of size 8 bytes we trigger an invalid length error.
let err = setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// check that it is the right kind of `InvalidInput`
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
}
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
/// Tests binding after the `SO_NOSIGPIPE` socket option has been set on the newly created socket.
/// That flag only exists on BSD-like OSes.
fn test_bind_ipv4_nosigpipe() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1 as libc::c_int).unwrap();
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
}
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
/// Tests setting the `SO_NOSIGPIPE` socket option but with an invalid length.
fn test_set_nosigpipe_invalid_len() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Value should be of type `libc::c_int` which has size 4 bytes.
// By providing a u64 of size 8 bytes we trigger an invalid length error.
let err = setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u64).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// Check that it is the right kind of `InvalidInput`.
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
}
/// Tests binding an IPv4 socket with an IPv4 address but the addrlen argument
/// has the wrong size.
fn test_bind_ipv4_invalid_addr_len() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
let err = unsafe {
errno_result(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
// Add 1 to the address to make the size invalid.
(size_of::<libc::sockaddr_in>() + 1) as libc::socklen_t,
))
.unwrap_err()
};
assert_eq!(err.kind(), ErrorKind::InvalidInput);
// Check that it is the right kind of `InvalidInput`.
assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
}
fn test_bind_ipv6() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv6_sock_addr(net::IPV6_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
}
fn test_listen() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(sockfd, 16));
}
}
/// Test accepting connections by running a server in a separate thread and connecting clients
/// from the main thread.
/// This function tests both
/// - Connecting when the server is already accepting
/// - Accepting when there is already an incoming connection
fn test_accept_connect() {
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
server_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(server_sockfd, 16));
}
// Retrieve actual listener address because we used a randomized port.
let (_, server_addr) =
sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(addr) = server_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
// Yield back to the client thread to test whether calling `connect` first also
// works.
thread::sleep(Duration::from_millis(10));
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
});
// Yield to server thread to ensure `accept` is called before we try
// to connect.
thread::sleep(Duration::from_millis(10));
// Test connecting to an already accepting server.
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
// Server thread should now be in its `sleep`.
// Test connecting when there is no actively ongoing `accept`.
// We need a new client socket since we cannot connect a socket multiple times.
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
server_thread.join().unwrap();
}
/// Test the `getsockname` syscall on an IPv4 socket which is bound.
/// The `getsockname` syscall should return the same address as to
/// which the socket was bound to.
fn test_getsockname_ipv4() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 6789);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(sockfd, 16));
}
let (_, sock_addr) =
sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(sock_addr) = sock_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
assert_eq!(addr.sin_family, sock_addr.sin_family);
assert_eq!(addr.sin_port, sock_addr.sin_port);
assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr);
}
/// Test the `getsockname` syscall on an IPv4 socket which is bound
/// but the port was zero.
/// The `getsockname` syscall should return the same address as to
/// which the socket was bound to but the port should be non-zero.
fn test_getsockname_ipv4_random_port() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Use zero-port to let the OS choose a free port to bind to.
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(sockfd, 16));
}
let (_, sock_addr) =
sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(sock_addr) = sock_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
assert_eq!(addr.sin_family, sock_addr.sin_family);
// The bound port must not be the zero port.
assert!(sock_addr.sin_port > 0);
assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr);
}
/// Test the `getsockname` syscall on an IPv4 socket which is not bound.
/// The `getsockname` syscall should return 0.0.0.0:0
fn test_getsockname_ipv4_unbound() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let (_, sock_addr) =
sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap();
// Libc representation of an unspecified IPv4 address with zero port.
let addr = net::ipv4_sock_addr([0, 0, 0, 0], 0);
let LibcSocketAddr::V4(sock_addr) = sock_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
assert_eq!(addr.sin_family, sock_addr.sin_family);
assert_eq!(addr.sin_port, sock_addr.sin_port);
assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr);
}
/// Test the `getsockname` syscall on an IPv6 socket which is bound.
/// The `getsockname` syscall should return the same address as to
/// which the socket was bound to.
fn test_getsockname_ipv6() {
let sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv6_sock_addr(net::IPV6_LOCALHOST, 1234);
unsafe {
errno_check(libc::bind(
sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(sockfd, 16));
}
let (_, sock_addr) =
sockname(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V6(sock_addr) = sock_addr else {
// We bound an IPv6 address so we also expect
// an IPv6 address to be returned.
panic!()
};
assert_eq!(addr.sin6_family, sock_addr.sin6_family);
assert_eq!(addr.sin6_port, sock_addr.sin6_port);
assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo);
assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id);
assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr);
}
/// Test the `getpeername` syscall on an IPv4 socket.
/// For a connected socket, the `getpeername` syscall should
/// return the same address as the socket was connected to.
fn test_getpeername_ipv4() {
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
server_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(server_sockfd, 16));
}
// Retrieve actual listener address because we used a randomized port.
let (_, server_addr) =
sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(addr) = server_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
});
// Test connecting to an already accepting server.
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
let (_, peer_addr) =
sockname(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(peer_addr) = peer_addr else {
// We connected to an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
assert_eq!(addr.sin_family, peer_addr.sin_family);
assert_eq!(addr.sin_port, peer_addr.sin_port);
assert_eq!(addr.sin_addr.s_addr, peer_addr.sin_addr.s_addr);
server_thread.join().unwrap();
}
/// Test the `getpeername` syscall on an IPv6 socket.
/// For a connected socket, the `getpeername` syscall should
/// return the same address as the socket was connected to.
fn test_getpeername_ipv6() {
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv6_sock_addr(net::IPV6_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
server_sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(server_sockfd, 16));
}
// Retrieve actual listener address because we used a randomized port.
let (_, server_addr) =
sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V6(addr) = server_addr else {
// We bound an IPv6 address so we also expect
// an IPv6 address to be returned.
panic!()
};
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
});
// Test connecting to an already accepting server.
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
let (_, peer_addr) =
sockname(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V6(peer_addr) = peer_addr else {
// We connected to an IPv6 address so we also expect
// an IPv6 address to be returned.
panic!()
};
assert_eq!(addr.sin6_family, peer_addr.sin6_family);
assert_eq!(addr.sin6_port, peer_addr.sin6_port);
assert_eq!(addr.sin6_flowinfo, peer_addr.sin6_flowinfo);
assert_eq!(addr.sin6_scope_id, peer_addr.sin6_scope_id);
assert_eq!(addr.sin6_addr.s6_addr, peer_addr.sin6_addr.s6_addr);
server_thread.join().unwrap();
}
/// Set a socket option. It's the caller's responsibility to ensure that `T` is
/// associated with the given socket option.
///
/// This function is directly copied from the standard library implementation
/// for sockets on UNIX targets.
fn setsockopt<T>(
sockfd: i32,
level: libc::c_int,
option_name: libc::c_int,
option_value: T,
) -> io::Result<()> {
let option_len = size_of::<T>() as libc::socklen_t;
errno_result(unsafe {
libc::setsockopt(
sockfd,
level,
option_name,
(&raw const option_value) as *const _,
option_len,
)
})?;
Ok(())
}
enum LibcSocketAddr {
V4(libc::sockaddr_in),
V6(libc::sockaddr_in6),
}
/// Wraps a call to a platform function that returns a socket address.
/// This is very much the same as the function with the same name in the
/// standard library implementation.
/// Returns a tuple containing the actual return value of the performed
/// syscall and the written address of it.
fn sockname<F>(f: F) -> io::Result<(libc::c_int, LibcSocketAddr)>
where
F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int,
{
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let value = errno_result(f(storage.as_mut_ptr().cast(), &mut len))?;
// SAFETY:
// The caller guarantees that the storage has been successfully initialized
// and its size written to `len` if `f` returns a success.
let address = unsafe {
match (*storage.as_ptr()).ss_family as libc::c_int {
libc::AF_INET => {
assert!(len as usize >= size_of::<libc::sockaddr_in>());
LibcSocketAddr::V4(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in))
}
libc::AF_INET6 => {
assert!(len as usize >= size_of::<libc::sockaddr_in6>());
LibcSocketAddr::V6(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in6))
}
_ => return Err(io::Error::new(ErrorKind::InvalidInput, "invalid argument")),
}
};
Ok((value, address))
}