portpicker_ipv4/
lib.rs

1use rand::prelude::*;
2use std::net::{
3    Ipv4Addr, SocketAddrV4, TcpListener, ToSocketAddrs, UdpSocket,
4};
5
6pub type Port = u16;
7
8// Try to bind to a socket using UDP
9fn test_bind_udp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
10    Some(UdpSocket::bind(addr).ok()?.local_addr().ok()?.port())
11}
12
13// Try to bind to a socket using TCP
14fn test_bind_tcp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
15    Some(TcpListener::bind(addr).ok()?.local_addr().ok()?.port())
16}
17
18/// Check if a port is free on UDP
19pub fn is_free_udp(port: Port) -> bool {
20    let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
21    test_bind_udp(ipv4).is_some()
22}
23
24/// Check if a port is free on TCP
25pub fn is_free_tcp(port: Port) -> bool {
26    let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);
27    test_bind_tcp(ipv4).is_some()
28}
29
30/// Check if a port is free on both TCP and UDP
31pub fn is_free(port: Port) -> bool {
32    is_free_tcp(port) && is_free_udp(port)
33}
34
35/// Asks the OS for a free port
36fn ask_free_tcp_port() -> Option<Port> {
37    let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
38    test_bind_tcp(ipv4)
39}
40
41/// Picks an available port that is available on both TCP and UDP
42/// ```rust
43/// use portpicker::pick_unused_port;
44/// let port: u16 = pick_unused_port().expect("No ports free");
45/// ```
46pub fn pick_unused_port() -> Option<Port> {
47    let mut rng = rand::thread_rng();
48
49    // Try random port first
50    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    // Ask the OS for a port
58    for _ in 0..10 {
59        if let Some(port) = ask_free_tcp_port() {
60            // Test that the udp port is free as well
61            if is_free_udp(port) {
62                return Some(port);
63            }
64        }
65    }
66
67    // Give up
68    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}