From 0a73bc3356ea43beffa744bc54f58df08c790baa Mon Sep 17 00:00:00 2001 From: lbl <1791778603@qq.com> Date: Thu, 20 Aug 2026 23:05:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(nat):=20TCP/ICMP=20NAT=20=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=9B=A0=E5=8D=95=E7=82=B9=E7=9E=AC=E6=97=B6=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=B0=B8=E4=B9=85=E7=98=AB=E7=97=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - tcp_nat listen_task:accept() 或 local/peer_addr 查询失败、 no_tun 下 network.ip() 未就绪(None),任一错误经 ? 传播直接 杀掉整个监听任务,NAT 永久失效且无重启。 - icmp_nat task:任一侧 socket 收发或单包处理出错同样经 ? 传播 终止整个任务。 修复: - 循环内错误改为 log::warn + continue,并加 100ms 休眠避免持续性 错误造成空转。 - no_tun 下 network.ip() 为 None 时跳过重写/丢弃该包,而非报错退出。 注:该修复为异步 I/O 循环的容错行为变更,依赖真实 socket, 未补充端到端测试;已有 nat 相关单测全部通过。 --- vnt-core/src/nat/internal_nat/icmp_nat.rs | 37 +++++++++++++++++++---- vnt-core/src/nat/internal_nat/tcp_nat.rs | 25 ++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/vnt-core/src/nat/internal_nat/icmp_nat.rs b/vnt-core/src/nat/internal_nat/icmp_nat.rs index 49dc76f..1d0fc41 100644 --- a/vnt-core/src/nat/internal_nat/icmp_nat.rs +++ b/vnt-core/src/nat/internal_nat/icmp_nat.rs @@ -60,14 +60,34 @@ async fn task( let mut map: IcmpNatMap = HashMap::new(); let mut gc_interval = tokio::time::interval(ICMP_NAT_GC_INTERVAL); loop { + // 单次收发/处理失败不能拖垮整个任务:记录日志后继续, + // 短暂休眠避免持续性错误造成空转 tokio::select! { rs = tokio_icmp_socket.recv(&mut buf1) => { - let len = rs?; - tokio_icmp_socket_recv(&buf1[..len],&inner_icmp_socket,&mut map,no_tun,&network).await?; + match rs { + Ok(len) => { + if let Err(e) = tokio_icmp_socket_recv(&buf1[..len],&inner_icmp_socket,&mut map,no_tun,&network).await { + log::warn!("icmp nat outbound error: {e:?}"); + } + } + Err(e) => { + log::warn!("icmp nat recv error: {e:?}"); + tokio::time::sleep(Duration::from_millis(100)).await; + } + } } rs = inner_icmp_socket.recv_from_to(&mut buf2) => { - let (len,src,dst) = rs?; - inner_icmp_socket_recv(&buf2[..len],src,dst,&tokio_icmp_socket,&mut map,no_tun,&network).await?; + match rs { + Ok((len,src,dst)) => { + if let Err(e) = inner_icmp_socket_recv(&buf2[..len],src,dst,&tokio_icmp_socket,&mut map,no_tun,&network).await { + log::warn!("icmp nat inbound error: {e:?}"); + } + } + Err(e) => { + log::warn!("icmp nat inner recv error: {e:?}"); + tokio::time::sleep(Duration::from_millis(100)).await; + } + } } _ = gc_interval.tick() => { evict_expired(&mut map, Instant::now(), ICMP_NAT_TIMEOUT); @@ -115,7 +135,11 @@ async fn tokio_icmp_socket_recv( return Ok(()); }; if no_tun && src == Ipv4Addr::LOCALHOST { - src = network.ip().context("not ip")?; + // 虚拟地址未就绪时丢弃该应答包,而不是让错误传播杀掉整个任务 + let Some(ip) = network.ip() else { + return Ok(()); + }; + src = ip; } inner_icmp_socket @@ -148,7 +172,8 @@ async fn inner_icmp_socket_recv( if payload.len() < 4 { return Ok(()); } - if no_tun && dst == network.ip().context("not ip")? { + // 虚拟地址未就绪(None)时跳过重写,而不是让错误传播杀掉整个任务 + if no_tun && Some(dst) == network.ip() { dst = Ipv4Addr::LOCALHOST; } diff --git a/vnt-core/src/nat/internal_nat/tcp_nat.rs b/vnt-core/src/nat/internal_nat/tcp_nat.rs index 3382985..d7c5329 100644 --- a/vnt-core/src/nat/internal_nat/tcp_nat.rs +++ b/vnt-core/src/nat/internal_nat/tcp_nat.rs @@ -31,14 +31,31 @@ async fn listen_task( network: SharedNetworkAddr, ) -> anyhow::Result<()> { loop { - let (stream, _addr) = tcp_listener.accept().await?; - let mut local_addr = stream.local_addr()?; - let peer_addr = stream.peer_addr()?; + // 单次 accept/地址查询失败不能拖垮整个监听任务: + // 记录日志后继续,短暂休眠避免持续性错误造成空转 + let (stream, _addr) = match tcp_listener.accept().await { + Ok(v) => v, + Err(e) => { + log::warn!("tcp nat accept error: {e:?}"); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + continue; + } + }; + let (mut local_addr, peer_addr) = match (stream.local_addr(), stream.peer_addr()) { + (Ok(local_addr), Ok(peer_addr)) => (local_addr, peer_addr), + (Err(e), _) | (_, Err(e)) => { + log::warn!("tcp nat get addr error: {e:?}"); + continue; + } + }; if no_tun { let IpAddr::V4(ip) = local_addr.ip() else { continue; }; - if ip == network.ip().context("not ip")? { + // 虚拟地址未就绪(None)时跳过重写,而不是终止任务 + if let Some(net_ip) = network.ip() + && ip == net_ip + { // 无tun的情况下写入本机的则写到localhost local_addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)); }