use std::net::{
Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs, UdpSocket,
};
use getrandom::getrandom;
use crate::port::Port;
fn test_bind_udp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
Some(UdpSocket::bind(addr).ok()?.local_addr().ok()?.port())
}
fn test_bind_tcp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
Some(TcpListener::bind(addr).ok()?.local_addr().ok()?.port())
}
fn is_free_udp(port: Port) -> bool {
let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
let ipv6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0);
test_bind_udp(ipv6).is_some() && test_bind_udp(ipv4).is_some()
}
fn is_free_tcp(port: Port) -> bool {
let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
let ipv6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0);
test_bind_tcp(ipv6).is_some() && test_bind_tcp(ipv4).is_some()
}
fn is_free(port: Port) -> bool {
is_free_tcp(port) && is_free_udp(port)
}
fn ask_free_tcp_port() -> Option<Port> {
let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
let ipv6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0);
test_bind_tcp(ipv6).or_else(|| test_bind_tcp(ipv4))
}
fn generate_port() -> Port {
const MIN: Port = 15000;
const MAX: Port = 25000;
const RANGE: Port = MAX - MIN;
const BUFFER_SIZE: usize = std::mem::size_of::<Port>();
let mut buffer = [0; BUFFER_SIZE];
getrandom(&mut buffer).unwrap();
let rnd = Port::from_le_bytes(buffer);
MIN + rnd % RANGE
}
pub fn pick_unused_port() -> Option<Port> {
for _ in 0..10 {
let port = generate_port();
if is_free(port) {
return Some(port);
}
}
for _ in 0..10 {
if let Some(port) = ask_free_tcp_port() {
if is_free_udp(port) {
return Some(port);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::pick_unused_port;
#[test]
fn it_works() {
assert!(pick_unused_port().is_some());
}
}