find_port 0.1.4

Find an available TCP/UDP port — library and CLI tool
Documentation
use std::{
    net::{Ipv4Addr, TcpListener, TcpStream, UdpSocket},
    str::FromStr,
};

/// Check if a UDP port is available for binding on the given host.
///
/// # Examples
///
/// ```
/// use find_port::udp_is_available;
/// assert!(udp_is_available("127.0.0.1", 0));
/// ```
pub fn udp_is_available(host: &str, port: u16) -> bool {
    Ipv4Addr::from_str(host)
        .map(|addr| UdpSocket::bind((addr, port)).is_ok())
        .unwrap_or(false)
}

/// Check if a TCP port is available for binding on the given host.
///
/// # Examples
///
/// ```
/// use find_port::tcp_is_available;
/// assert!(tcp_is_available("127.0.0.1", 0));
/// ```
pub fn tcp_is_available(host: &str, port: u16) -> bool {
    Ipv4Addr::from_str(host)
        .map(|addr| TcpListener::bind((addr, port)).is_ok())
        .unwrap_or(false)
}

/// Check if a TCP connection can be established to the given host and port.
///
/// Returns `true` if the port is already in use and accepting connections.
///
/// # Examples
///
/// ```
/// use find_port::tcp_can_connect;
/// // An unused port should not be connectable
/// assert!(!tcp_can_connect("127.0.0.1", 1));
/// ```
pub fn tcp_can_connect(host: &str, port: u16) -> bool {
    TcpStream::connect((host, port)).is_ok()
}

/// Check if a port is fully available — both TCP and UDP can bind,
/// and no existing TCP connection is accepted on that port.
///
/// # Examples
///
/// ```
/// use find_port::port_is_available;
/// assert!(port_is_available("127.0.0.1", 0));
/// ```
pub fn port_is_available(host: &str, port: u16) -> bool {
    // Fast-path: if TCP bind fails the port is definitely taken
    if !tcp_is_available(host, port) {
        return false;
    }
    udp_is_available(host, port) && !tcp_can_connect(host, port)
}

/// Find the first available port starting from `port`, scanning upward.
///
/// Returns `None` if no port is available in the range `port..=65535`.
///
/// # Examples
///
/// ```
/// use find_port::find_port;
/// // Port 0 typically resolves to an available port, but we search from 1024 for a realistic test
/// let port = find_port("127.0.0.1", 1024);
/// assert!(port.is_some());
/// ```
pub fn find_port(host: &str, port: u16) -> Option<u16> {
    (port..=u16::MAX).find(|&p| port_is_available(host, p))
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_find_port() {
        let port = find_port("127.0.0.1", 8080).unwrap();
        assert_eq!(port, 8080);
    }

    #[test]
    fn test_port_is_available() {
        assert!(port_is_available("127.0.0.1", 0));
    }

    #[test]
    fn test_invalid_host() {
        assert!(!udp_is_available("invalid", 8080));
        assert!(!tcp_is_available("invalid", 8080));
    }
}