1use rand::prelude::*;
2use std::net::{
3 Ipv4Addr, SocketAddrV4, TcpListener, ToSocketAddrs, UdpSocket,
4};
5
6pub type Port = u16;
7
8fn test_bind_udp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
10 Some(UdpSocket::bind(addr).ok()?.local_addr().ok()?.port())
11}
12
13fn test_bind_tcp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
15 Some(TcpListener::bind(addr).ok()?.local_addr().ok()?.port())
16}
17
18pub fn is_free_udp(port: Port) -> bool {
20 let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
21 test_bind_udp(ipv4).is_some()
22}
23
24pub fn is_free_tcp(port: Port) -> bool {
26 let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
27 test_bind_tcp(ipv4).is_some()
28}
29
30pub fn is_free(port: Port) -> bool {
32 is_free_tcp(port) && is_free_udp(port)
33}
34
35fn ask_free_tcp_port() -> Option<Port> {
37 let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
38 test_bind_tcp(ipv4)
39}
40
41pub fn pick_unused_port() -> Option<Port> {
47 let mut rng = rand::thread_rng();
48
49 for _ in 0..10 {
51 let port = rng.gen_range(30000..60000);
52 if is_free(port) {
53 return Some(port);
54 }
55 }
56
57 for _ in 0..10 {
59 if let Some(port) = ask_free_tcp_port() {
60 if is_free_udp(port) {
62 return Some(port);
63 }
64 }
65 }
66
67 None
69}
70
71#[cfg(test)]
72mod tests {
73 use super::pick_unused_port;
74
75 #[test]
76 fn it_works() {
77 assert!(pick_unused_port().is_some());
78 }
79}