├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "portpicker" 3 | description = "Pick a free unused port" 4 | version = "0.1.0" 5 | authors = ["Hannes Karppila "] 6 | edition = "2018" 7 | 8 | repository = "https://github.com/Dentosal/portpicker-rs" 9 | readme = "README.md" 10 | 11 | keywords = ["port", "network"] 12 | categories = ["network-programming"] 13 | 14 | license = "Unlicense" 15 | 16 | [dependencies] 17 | rand = "0.7" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portpicker-rs - Pick a free unused port 2 | 3 | Picks a free port, that is unused on both TCP and UDP. 4 | 5 | Usage: 6 | 7 | ```rust 8 | portpicker::pick_free_port().expect("No ports free") 9 | ``` -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::net::{ 2 | Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs, UdpSocket, 3 | }; 4 | use rand::prelude::*; 5 | 6 | pub type Port = u16; 7 | 8 | // Try to bind to a socket using UDP 9 | fn test_bind_udp(addr: A) -> Option { 10 | Some(UdpSocket::bind(addr).ok()?.local_addr().ok()?.port()) 11 | } 12 | 13 | // Try to bind to a socket using TCP 14 | fn test_bind_tcp(addr: A) -> Option { 15 | Some(TcpListener::bind(addr).ok()?.local_addr().ok()?.port()) 16 | } 17 | 18 | /// Check if a port is free on UDP 19 | pub fn is_free_udp(port: Port) -> bool { 20 | let ipv4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, port); 21 | let ipv6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, port, 0, 0); 22 | 23 | test_bind_udp(ipv6).is_some() || test_bind_udp(ipv4).is_some() 24 | } 25 | 26 | /// Check if a port is free on TCP 27 | pub fn is_free_tcp(port: Port) -> bool { 28 | let ipv4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, port); 29 | let ipv6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, port, 0, 0); 30 | 31 | test_bind_tcp(ipv6).is_some() || test_bind_tcp(ipv4).is_some() 32 | } 33 | 34 | /// Check if a port is free on both TCP and UDP 35 | pub fn is_free(port: Port) -> bool { 36 | is_free_tcp(port) && is_free_udp(port) 37 | } 38 | 39 | /// Asks the OS for a free port 40 | fn ask_free_tcp_port() -> Option { 41 | let ipv4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0); 42 | let ipv6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); 43 | 44 | test_bind_tcp(ipv6).or_else(|| test_bind_tcp(ipv4)) 45 | } 46 | 47 | /// Picks an available port that is available on both TCP and UDP 48 | /// ```rust 49 | /// use portpicker::pick_unused_port; 50 | /// let port: u16 = pick_unused_port().expect("No ports free"); 51 | /// ``` 52 | pub fn pick_unused_port() -> Option { 53 | let mut rng = rand::thread_rng(); 54 | 55 | // Try random port first 56 | for _ in 0..10 { 57 | let port = rng.gen_range(15000, 25000); 58 | if is_free(port) { 59 | return Some(port); 60 | } 61 | } 62 | 63 | // Ask the OS for a port 64 | for _ in 0..10 { 65 | if let Some(port) = ask_free_tcp_port() { 66 | // Test that the udp port is free as well 67 | if is_free_udp(port) { 68 | return Some(port); 69 | } 70 | } 71 | } 72 | 73 | // Give up 74 | None 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::pick_unused_port; 80 | 81 | #[test] 82 | fn it_works() { 83 | assert!(pick_unused_port().is_some()); 84 | } 85 | } 86 | --------------------------------------------------------------------------------