use crate::{Result, QsshError};
use tokio::net::UdpSocket;
use std::net::SocketAddr;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NatType {
None,
FullCone,
RestrictedCone,
PortRestrictedCone,
Symmetric,
}
pub const STUN_SERVERS: &[&str] = &[
"stun.l.google.com:19302",
"stun.services.mozilla.com:3478",
];
pub struct NatTraversal {
local_addr: SocketAddr,
public_addr: Option<SocketAddr>,
nat_type: Option<NatType>,
}
impl NatTraversal {
pub async fn new(local_port: u16) -> Result<Self> {
let local_addr = format!("0.0.0.0:{}", local_port).parse()
.map_err(|e| QsshError::Connection(format!("Invalid address: {}", e)))?;
Ok(Self {
local_addr,
public_addr: None,
nat_type: None,
})
}
pub async fn detect(&mut self) -> Result<(SocketAddr, NatType)> {
let socket = UdpSocket::bind(self.local_addr).await?;
let request = self.create_stun_request();
for server in STUN_SERVERS {
if let Ok(server_addr) = server.parse::<SocketAddr>() {
socket.send_to(&request, server_addr).await?;
let mut buf = vec![0u8; 1024];
match tokio::time::timeout(
std::time::Duration::from_secs(3),
socket.recv_from(&mut buf)
).await {
Ok(Ok((len, _))) => {
if let Some(public_addr) = self.parse_stun_response(&buf[..len]) {
self.public_addr = Some(public_addr);
self.nat_type = Some(self.detect_nat_type(&socket).await?);
return Ok((public_addr, self.nat_type.unwrap()));
}
}
_ => continue,
}
}
}
Err(QsshError::Connection("Failed to detect NAT".into()))
}
fn create_stun_request(&self) -> Vec<u8> {
let mut request = vec![
0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xA4, 0x42, ];
use rand::RngCore;
let mut rng = rand::thread_rng();
let mut tx_id = vec![0u8; 12];
rng.fill_bytes(&mut tx_id);
request.extend_from_slice(&tx_id);
request
}
fn parse_stun_response(&self, data: &[u8]) -> Option<SocketAddr> {
if data.len() < 20 {
return None;
}
if data[0] != 0x01 || data[1] != 0x01 {
return None;
}
let mut i = 20;
while i + 4 <= data.len() {
let attr_type = u16::from_be_bytes([data[i], data[i + 1]]);
let attr_len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
if i + 4 + attr_len > data.len() {
break;
}
if attr_type == 0x0020 && attr_len >= 8 {
let family = data[i + 5];
if family == 0x01 { let port = u16::from_be_bytes([data[i + 6], data[i + 7]]) ^ 0x2112;
let ip = u32::from_be_bytes([
data[i + 8] ^ 0x21,
data[i + 9] ^ 0x12,
data[i + 10] ^ 0xA4,
data[i + 11] ^ 0x42,
]);
use std::net::{IpAddr, Ipv4Addr};
let ip_addr = IpAddr::V4(Ipv4Addr::from(ip));
return Some(SocketAddr::new(ip_addr, port));
}
}
i += 4 + attr_len;
i = (i + 3) & !3;
}
None
}
async fn detect_nat_type(&self, _socket: &UdpSocket) -> Result<NatType> {
if let Some(public_addr) = self.public_addr {
if public_addr.ip() == self.local_addr.ip() {
Ok(NatType::None)
} else {
Ok(NatType::RestrictedCone)
}
} else {
Ok(NatType::Symmetric)
}
}
pub async fn hole_punch(
&self,
peer_public: SocketAddr,
peer_private: SocketAddr,
) -> Result<UdpSocket> {
let socket = UdpSocket::bind(self.local_addr).await?;
let punch_msg = b"QSSH-PUNCH";
for _ in 0..10 {
socket.send_to(punch_msg, peer_public).await?;
socket.send_to(punch_msg, peer_private).await?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
Ok(socket)
}
}