Skip to main content

teaql_tool_std/
net.rs

1use std::net::{IpAddr, Ipv4Addr, TcpListener, UdpSocket};
2use teaql_tool_core::{Result, TeaQLToolError};
3
4/// Network utility wrapper
5pub struct NetTool;
6
7impl NetTool {
8    pub fn new() -> Self {
9        Self
10    }
11
12    /// Quickly get the local IPv4 address by attempting a dummy UDP connection
13    pub fn get_local_ipv4(&self) -> Result<String> {
14        let socket = UdpSocket::bind("0.0.0.0:0")
15            .map_err(|e| TeaQLToolError::ExecutionError(format!("Failed to bind socket: {}", e)))?;
16        socket.connect("8.8.8.8:80")
17            .map_err(|e| TeaQLToolError::ExecutionError(format!("Failed to connect socket: {}", e)))?;
18        let addr = socket.local_addr()
19            .map_err(|e| TeaQLToolError::ExecutionError(format!("Failed to get local address: {}", e)))?;
20        Ok(addr.ip().to_string())
21    }
22
23    /// Check if a local port is usable (not occupied)
24    pub fn is_usable_local_port(&self, port: u16) -> bool {
25        match TcpListener::bind(("127.0.0.1", port)) {
26            Ok(_) => true,
27            Err(_) => false,
28        }
29    }
30
31    /// Check if an IP address string is an internal/private IP
32    pub fn is_inner_ip(&self, ip_str: impl AsRef<str>) -> bool {
33        let ip: IpAddr = match ip_str.as_ref().parse() {
34            Ok(ip) => ip,
35            Err(_) => return false,
36        };
37
38        match ip {
39            IpAddr::V4(ipv4) => {
40                let octets = ipv4.octets();
41                // 10.0.0.0/8
42                if octets[0] == 10 { return true; }
43                // 172.16.0.0/12
44                if octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31 { return true; }
45                // 192.168.0.0/16
46                if octets[0] == 192 && octets[1] == 168 { return true; }
47                // 127.0.0.0/8 (Loopback is generally considered private/internal)
48                if octets[0] == 127 { return true; }
49                false
50            }
51            IpAddr::V6(ipv6) => {
52                let segments = ipv6.segments();
53                // Unique Local Addresses (fc00::/7)
54                if (segments[0] & 0xfe00) == 0xfc00 { return true; }
55                // Loopback
56                if ipv6 == std::net::Ipv6Addr::LOCALHOST { return true; }
57                false
58            }
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_net_tool() {
69        let tool = NetTool::new();
70        // port 0 is always assignable
71        assert!(tool.is_usable_local_port(0));
72        assert!(tool.is_inner_ip("192.168.1.10"));
73        assert!(tool.is_inner_ip("10.0.0.5"));
74        assert!(tool.is_inner_ip("127.0.0.1"));
75        assert!(!tool.is_inner_ip("8.8.8.8"));
76    }
77}