#![allow(dead_code)]
pub struct RefusedPort {
held: sys::Held,
port: u16,
}
impl RefusedPort {
pub fn hold() -> Self {
let (held, port) = sys::refused_port().expect("a loopback port that refuses connections");
assert_ne!(port, 0);
Self { held, port }
}
pub fn port(&self) -> u16 {
self.port
}
pub fn url(&self, path: &str) -> String {
format!("http://127.0.0.1:{}{path}", self.port)
}
}
impl Drop for RefusedPort {
fn drop(&mut self) {
sys::release(&mut self.held);
}
}
#[cfg(windows)]
mod sys {
use std::io;
pub type Held = usize;
const INVALID_SOCKET: usize = usize::MAX;
const AF_INET: i32 = 2;
const SOCK_STREAM: i32 = 1;
const IPPROTO_TCP: i32 = 6;
const SOL_SOCKET: i32 = 0xffff;
const SO_EXCLUSIVEADDRUSE: i32 = !4;
#[repr(C)]
struct SockaddrIn {
family: u16,
port: u16,
addr: [u8; 4],
zero: [u8; 8],
}
#[link(name = "ws2_32")]
unsafe extern "system" {
fn socket(family: i32, kind: i32, protocol: i32) -> usize;
fn setsockopt(socket: usize, level: i32, name: i32, value: *const u8, len: i32) -> i32;
fn bind(socket: usize, address: *const SockaddrIn, len: i32) -> i32;
fn getsockname(socket: usize, address: *mut SockaddrIn, len: *mut i32) -> i32;
fn closesocket(socket: usize) -> i32;
}
pub fn refused_port() -> io::Result<(Held, u16)> {
drop(std::net::UdpSocket::bind("127.0.0.1:0")?);
unsafe {
let socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if socket == INVALID_SOCKET {
return Err(io::Error::last_os_error());
}
let fail = |socket: usize| {
let error = io::Error::last_os_error();
closesocket(socket);
Err(error)
};
let on = 1i32.to_ne_bytes();
if setsockopt(socket, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, on.as_ptr(), 4) != 0 {
return fail(socket);
}
let mut address = SockaddrIn {
family: AF_INET as u16,
port: 0,
addr: [127, 0, 0, 1],
zero: [0; 8],
};
let mut len = std::mem::size_of::<SockaddrIn>() as i32;
if bind(socket, &address, len) != 0 {
return fail(socket);
}
if getsockname(socket, &mut address, &mut len) != 0 {
return fail(socket);
}
Ok((socket, u16::from_be(address.port)))
}
}
pub fn release(socket: &mut Held) {
unsafe { closesocket(*socket) };
}
}
#[cfg(unix)]
mod sys {
use std::io::{self, ErrorKind};
use std::net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
pub type Held = ();
const FIRST: u32 = 20_000;
const SPAN: u32 = 10_000;
const CONNECT_WAIT: Duration = Duration::from_secs(2);
pub fn refused_port() -> io::Result<(Held, u16)> {
static CALLS: AtomicU32 = AtomicU32::new(0);
let start = std::process::id()
.wrapping_mul(2_654_435_761)
.wrapping_add(CALLS.fetch_add(1, Ordering::Relaxed).wrapping_mul(7_919));
for step in 0..SPAN {
let port = (FIRST + start.wrapping_add(step) % SPAN) as u16;
let address = SocketAddr::from((Ipv4Addr::LOCALHOST, port));
if TcpListener::bind(address).is_err() {
continue;
}
if let Err(error) = TcpStream::connect_timeout(&address, CONNECT_WAIT) {
if error.kind() == ErrorKind::ConnectionRefused {
return Ok(((), port));
}
}
}
Err(io::Error::other("no refused loopback port in 20000..30000"))
}
pub fn release(_held: &mut Held) {}
}