fix(nat): TCP/ICMP NAT 不再因单点瞬时错误永久瘫痪

问题:
- 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 相关单测全部通过。
This commit is contained in:
lbl
2026-08-20 23:05:08 +08:00
parent 40e2396741
commit 0a73bc3356
2 changed files with 52 additions and 10 deletions
+31 -6
View File
@@ -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;
}
+21 -4
View File
@@ -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));
}