portpicker/lib.rs
1// Modified by `Vector Contributors <vector@datadoghq.com>`.
2// Based on `https://github.com/Dentosal/portpicker-rs` by `Hannes Karppila <hannes.karppila@gmail.com>`.
3// `portpicker-rs` LICENSE:
4// This is free and unencumbered software released into the public domain.
5
6// Anyone is free to copy, modify, publish, use, compile, sell, or
7// distribute this software, either in source code form or as a compiled
8// binary, for any purpose, commercial or non-commercial, and by any
9// means.
10
11// In jurisdictions that recognize copyright laws, the author or authors
12// of this software dedicate any and all copyright interest in the
13// software to the public domain. We make this dedication for the benefit
14// of the public at large and to the detriment of our heirs and
15// successors. We intend this dedication to be an overt act of
16// relinquishment in perpetuity of all present and future rights to this
17// software under copyright law.
18
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
24// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25// OTHER DEALINGS IN THE SOFTWARE.
26
27// For more information, please refer to <http://unlicense.org>
28
29#![deny(warnings)]
30
31use std::net::{IpAddr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket};
32
33use rand::{rng, Rng};
34
35pub type Port = u16;
36
37// Try to bind to a socket using UDP
38fn test_bind_udp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
39 Some(UdpSocket::bind(addr).ok()?.local_addr().ok()?.port())
40}
41
42// Try to bind to a socket using TCP
43fn test_bind_tcp<A: ToSocketAddrs>(addr: A) -> Option<Port> {
44 Some(TcpListener::bind(addr).ok()?.local_addr().ok()?.port())
45}
46
47/// Check if a port is free on UDP
48pub fn is_free_udp(ip: IpAddr, port: Port) -> bool {
49 test_bind_udp(SocketAddr::new(ip, port)).is_some()
50}
51
52/// Check if a port is free on TCP
53pub fn is_free_tcp(ip: IpAddr, port: Port) -> bool {
54 test_bind_tcp(SocketAddr::new(ip, port)).is_some()
55}
56
57/// Check if a port is free on both TCP and UDP
58pub fn is_free(ip: IpAddr, port: Port) -> bool {
59 is_free_tcp(ip, port) && is_free_udp(ip, port)
60}
61
62/// Asks the OS for a free port
63fn ask_free_tcp_port(ip: IpAddr) -> Option<Port> {
64 test_bind_tcp(SocketAddr::new(ip, 0))
65}
66
67/// Picks an available port that is available on both TCP and UDP
68/// ```rust
69/// use portpicker::pick_unused_port;
70/// use std::net::{IpAddr, Ipv4Addr};
71/// let port: u16 = pick_unused_port(IpAddr::V4(Ipv4Addr::LOCALHOST));
72/// ```
73pub fn pick_unused_port(ip: IpAddr) -> Port {
74 let mut rng = rng();
75
76 loop {
77 // Try random port first
78 for _ in 0..10 {
79 let port = rng.random_range(15000..25000);
80 if is_free(ip, port) {
81 return port;
82 }
83 }
84
85 // Ask the OS for a port
86 for _ in 0..10 {
87 if let Some(port) = ask_free_tcp_port(ip) {
88 // Test that the udp port is free as well
89 if is_free_udp(ip, port) {
90 return port;
91 }
92 }
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
100
101 use super::pick_unused_port;
102
103 #[test]
104 fn ipv4_localhost() {
105 pick_unused_port(IpAddr::V4(Ipv4Addr::LOCALHOST));
106 }
107
108 #[test]
109 fn ipv6_localhost() {
110 pick_unused_port(IpAddr::V6(Ipv6Addr::LOCALHOST));
111 }
112}