fix(client): stabilize UDP sessions - prevent crashes on transient recv errors in udp_nat and proxy

This commit is contained in:
ospab 2026-05-30 02:12:15 +03:00
parent 3a39f19b45
commit 60b5d565e8
2 changed files with 47 additions and 35 deletions

View File

@ -371,11 +371,19 @@ async fn handle_udp_associate(
loop { loop {
tokio::select! { tokio::select! {
res = client_tcp.read(&mut tcp_buf) => { res = client_tcp.read(&mut tcp_buf) => {
let n = res?; match res {
if n == 0 { break; } Ok(0) | Err(_) => break,
Ok(_) => {}
}
} }
res = sock_rx.recv_from(&mut buf) => { res = sock_rx.recv_from(&mut buf) => {
let (len, addr) = res?; let (len, addr) = match res {
Ok(v) => v,
Err(e) => {
tracing::debug!("udp_associate recv_from error: {}", e);
continue; // transient error, don't kill the session
}
};
{ {
let mut guard = client_udp_addr.lock().unwrap(); let mut guard = client_udp_addr.lock().unwrap();
if guard.is_none() { if guard.is_none() {

View File

@ -96,13 +96,8 @@ async fn start_udp_session(
} }
} }
let udp = match relay_addr { // Local SOCKS5 proxy always returns 127.0.0.1 (IPv4), so always bind IPv4
SocketAddr::V4(_) => UdpSocket::bind("127.0.0.1:0").await?, let udp = UdpSocket::bind("127.0.0.1:0").await?;
SocketAddr::V6(_) => match UdpSocket::bind("[::1]:0").await {
Ok(sock) => sock,
Err(_) => UdpSocket::bind("[::]:0").await?,
},
};
let mut buf = vec![0u8; 65536]; let mut buf = vec![0u8; 65536];
@ -128,36 +123,45 @@ async fn start_udp_session(
} }
} }
res = udp.recv_from(&mut buf) => { res = udp.recv_from(&mut buf) => {
let (len, _peer) = res?; match res {
if len < 10 { continue; } // At least 10 bytes for SOCKS5 header Err(e) => {
let frag = buf[2]; tracing::debug!("udp_nat recv_from error: {}", e);
if frag != 0 { continue; } // fragment not supported continue; // transient error, don't kill the session
let atyp = buf[3];
let (header_len, remote_dst) = match atyp {
1 => {
if len < 10 { continue; }
let ip = std::net::Ipv4Addr::new(buf[4], buf[5], buf[6], buf[7]);
let port = u16::from_be_bytes([buf[8], buf[9]]);
(10, SocketAddr::new(std::net::IpAddr::V4(ip), port))
} }
4 => { Ok((len, _peer)) => {
if len < 22 { continue; } if len < 4 { continue; }
let mut octets = [0u8; 16]; let frag = buf[2];
octets.copy_from_slice(&buf[4..20]); if frag != 0 { continue; } // fragment not supported
let ip = std::net::Ipv6Addr::from(octets); let atyp = buf[3];
let port = u16::from_be_bytes([buf[20], buf[21]]); let (header_len, remote_dst) = match atyp {
(22, SocketAddr::new(std::net::IpAddr::V6(ip), port)) 1 => {
if len < 10 { continue; }
let ip = std::net::Ipv4Addr::new(buf[4], buf[5], buf[6], buf[7]);
let port = u16::from_be_bytes([buf[8], buf[9]]);
(10, SocketAddr::new(std::net::IpAddr::V4(ip), port))
}
4 => {
if len < 22 { continue; }
let mut octets = [0u8; 16];
octets.copy_from_slice(&buf[4..20]);
let ip = std::net::Ipv6Addr::from(octets);
let port = u16::from_be_bytes([buf[20], buf[21]]);
(22, SocketAddr::new(std::net::IpAddr::V6(ip), port))
}
_ => continue,
};
let payload = buf[header_len..len].to_vec();
use futures::SinkExt;
let _ = smoltcp_tx.lock().await.send((payload, remote_dst, client_src)).await;
} }
_ => continue, // Domain name not supported for incoming packets in typical UDP associate }
};
let payload = buf[header_len..len].to_vec();
use futures::SinkExt;
let _ = smoltcp_tx.lock().await.send((payload, remote_dst, client_src)).await;
} }
// If TCP drops, UDP association is over // If TCP drops, UDP association is over
res = tcp.read(&mut tcp_buf) => { res = tcp.read(&mut tcp_buf) => {
let n = res?; match res {
if n == 0 { break; } Ok(0) | Err(_) => break,
Ok(_) => {}
}
} }
} }
} }