use hightower_stun::client::StunClient;
use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::time::Duration;
use tracing::{debug, warn};
const DEFAULT_STUN_SERVER: &str = "gateway.shotgun.dev:3478";
pub fn discover_with_bound_address(
local_addr: SocketAddr,
stun_server: Option<&str>,
) -> Result<crate::NetworkInfo, Box<dyn std::error::Error>> {
let stun_server = stun_server.unwrap_or(DEFAULT_STUN_SERVER);
debug!("Discovering network info for bound address: {}", local_addr);
let client = StunClient::new()?.with_timeout(Duration::from_secs(5));
let public_addr = client.get_public_address(stun_server)?;
debug!(
public_ip = %public_addr.ip(),
public_port = public_addr.port(),
"Discovered public address via STUN"
);
let local_ip = discover_local_ip()?;
if local_addr.ip() != local_ip && !local_addr.ip().is_unspecified() {
warn!(
bound_ip = %local_addr.ip(),
discovered_ip = %local_ip,
"Bound IP differs from discovered local IP"
);
}
Ok(crate::NetworkInfo {
public_ip: public_addr.ip().to_string(),
public_port: public_addr.port(),
local_ip: local_ip.to_string(),
local_port: local_addr.port(),
})
}
fn discover_local_ip() -> Result<IpAddr, Box<dyn std::error::Error>> {
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.connect("8.8.8.8:53")?;
let local_addr = socket.local_addr()?;
Ok(local_addr.ip())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_discover_local_ip() {
let ip = discover_local_ip().expect("should discover local IP");
println!("Discovered local IP: {}", ip);
assert!(!ip.is_unspecified());
}
}