TUN设备数据包重组与分片处理

TUN调试 / 4人浏览

凌晨三点,老张的矿场里还亮着灯。不是那种刺眼的白炽灯,而是机架上几百张显卡散热风扇转动时映出的幽蓝光晕,混合着屏幕上跳动的算力曲线。他刚泡了碗方便面,准备盯着ETH的挖矿收益——虽然现在转成了POS,但他还在挖一些小众币,比如KAS和ALPH。这些币对网络延迟特别敏感,尤其是节点间的数据同步,稍微丢几个包,算力就白费了。

他一边吸溜着面条,一边盯着Prometheus的监控面板。突然,一条告警弹了出来:“tun0接口丢包率突增到12%,节点间P2P连接抖动严重。”老张皱了皱眉,放下筷子,手指在键盘上敲了几下,调出了tun设备的统计信息。

“又是分片问题。”他嘟囔了一句。

这不是第一次了。自从他把矿池节点之间的通信从裸TCP换成了基于TUN设备的自定义隧道协议,用来绕过某些ISP对特定端口的QoS限速,他就掉进了一个深坑——TUN设备的数据包重组与分片处理。这个坑,比矿卡掉驱动还让人头疼。

为什么TUN设备在虚拟币网络里这么重要?

老张的矿场里有三十多台机器,分布在三个不同的机房。为了统一管理,也为了把矿池的 Stratum 协议流量伪装成普通VPN流量,他搭了一套基于TUN的隧道。TUN设备是操作系统内核提供的虚拟网络接口,它不像TAP那样处理以太网帧,而是直接处理IP数据包。也就是说,当应用程序往tun0里写数据时,内核会把它当成一个三层设备,发出去的是IP包;反过来,从tun0读到的也是IP包。

在虚拟币的世界里,这种机制特别有用。比如比特币的P2P网络,节点之间要交换区块和交易,这些消息有时候很大——一个完整的区块,在SegWit之后虽然变小了,但带上 witness 数据,动辄1-2MB。而以太坊的区块,虽然gas limit限制了大小,但交易池同步时,一个包含几百笔交易的包也能轻松超过MTU。

MTU,最大传输单元,通常是1500字节。这意味着一个2MB的区块数据,在IP层必须被分片成1400多个小包。如果隧道本身又加了封装——比如老张用的那种自定义协议,在原始IP包外面又套了一层UDP头——那有效载荷就更小了。分片不可避免。

一个深夜的故障:分片重组失败引发的算力暴跌

那天晚上,老张的KAS矿池节点突然算力掉了30%。他检查了矿机,一切正常;检查了网络,延迟从20ms跳到了200ms。他抓包分析,发现大量ICMP分片超时消息。

“tun0上出现了分片重组队列溢出。”他自言自语。

TUN设备在内核里有一个分片重组缓冲区。当IP层收到分片包时,如果开启了重组功能(大多数Linux内核默认开启),它会把这些分片按源IP、目的IP、协议和ID排队,等所有分片到齐后拼成一个完整的IP包,再交给上层。但这个队列是有大小限制的。如果短时间内大量分片涌入,或者某个分片丢失导致重组超时,队列就会满,后续分片直接被丢弃。

老张的隧道里跑的是矿池协议,消息大小不一。有些小包(比如share提交)只有几十字节,有些大包(比如新区块广播)超过MTU。当一个大包被分片后,如果中间某个分片因为网络抖动丢了,接收端的tun设备就会一直等,直到重组超时(通常是30秒)。这30秒里,那个不完整的包占着重组队列的位置,其他分片只能排队或者被丢弃。

更糟糕的是,他的隧道协议在UDP层没有做可靠传输。UDP本身不保证顺序,也不重传。所以一旦分片丢失,整个IP包就废了。而矿池协议里,一个新区块广播的包丢了,意味着节点不知道新区块,继续在旧链上挖,算力全浪费。

内核里的分片重组:TUN设备到底怎么处理?

老张决定深入内核看看。他翻出了Linux内核源码里net/ipv4/ip_fragment.c和net/ipv4/ip_input.c的部分。TUN设备收到数据后,会调用netif_rx把包交给网络栈。如果包是分片(IP头部MF标志置位,或者fragment offset不为0),就会进入ip_defrag流程。

内核里有一个全局的分片重组哈希表,每个条目对应一个“分片组”,由四元组(源IP、目的IP、协议、IP ID)标识。每个分片组里有一个sk_buff链表,按offset排序。当所有分片到齐(从0到最后一个分片的offset+长度等于总长度),内核就把它们合并成一个大的sk_buff,然后交给上层协议处理。

但这里有几个坑:

  1. 重组队列上限:ipfrag_high_thresh和ipfrag_low_thresh控制内存使用。默认高水位是4MB,低水位是3MB。超过高水位,新分片直接被丢。
  2. 超时时间:ipfrag_time默认30秒。如果一个分片组30秒内没凑齐,整个组被丢弃,已到的分片也释放。
  3. 哈希冲突:IP ID只有16位,如果短时间内大量分片,不同数据流的IP ID可能重复,导致重组错误。
  4. TUN设备的特殊性:TUN设备是三层设备,它不处理以太网帧,所以没有硬件校验和卸载。所有分片重组都在软件层完成,CPU开销大。

老张的隧道里,矿池协议的消息大小从几十字节到几兆不等。小的消息一个包就发走了,大的消息被IP层分片。但问题在于,他的隧道协议在UDP封装时,没有考虑分片——他把整个IP包(包括分片)直接塞进UDP载荷。这意味着,如果原始IP包被分片成10个分片,这10个分片每个都会被单独封装进UDP包,然后发送。接收端收到UDP包后,剥掉UDP头,把里面的IP分片交给tun设备。tun设备再重组。

这本来没问题。但UDP是不可靠的。如果其中一个UDP包丢了,对应的IP分片就丢了,重组失败。而且,UDP包本身也可能被网络分片——如果UDP载荷超过路径MTU,路由器会对UDP包分片。这就变成了“分片的分片”,重组逻辑更复杂。

老张的解决方案:应用层分片与重组

痛定思痛,老张决定不在IP层依赖分片重组。他修改了自己的隧道程序,在应用层实现了一套分片与重组机制。

具体做法是:

  • 在发送端,应用程序把要发送的IP包(比如一个2MB的区块)先切成固定大小的块,比如1200字节。每个块加上一个自定义头部,包含:消息ID、块序号、总块数、块长度。
  • 然后,每个块被单独封装进UDP包发送。UDP包的大小控制在路径MTU以下,避免UDP层再分片。
  • 接收端,应用程序从tun设备读到的是完整的IP包——因为发送端已经确保每个UDP包里的数据不超过MTU,所以IP层不会分片。接收端根据自定义头部,把块缓存起来,等所有块到齐后,拼成完整的IP包,再写回tun设备。
  • 为了处理丢包,接收端设置一个定时器,比如200ms。如果某个消息的块没到齐,就发一个NACK给发送端,请求重传丢失的块。发送端收到NACK后,只重传丢失的块。

这套机制把分片和重组的责任从内核转移到了应用层。好处是:

  • 可控性更强:可以针对虚拟币协议的特点优化。比如,对于share提交这种小消息,直接一个UDP包发走,不分片;对于区块广播这种大消息,才启用分片。
  • 避免内核重组队列溢出:因为IP层不再分片,内核重组队列压力大减。
  • 重传效率高:只重传丢失的块,而不是整个大包。

但代价是:应用层要维护更多的状态,CPU开销从内核转移到了用户态。不过老张的矿场机器都是高配CPU,这点开销可以接受。

分片大小与虚拟币网络特性的权衡

老张在调优时发现,分片大小不是随便选的。他试过几种:

  • 500字节:太保守,UDP包数量多,头部开销大,而且矿池协议里很多消息只有几百字节,分片反而增加延迟。
  • 1200字节:比较平衡。路径MTU通常1500,减去UDP头(8字节)和自定义头(16字节),1200字节的载荷不会导致IP分片。
  • 1400字节:接近极限,但在某些网络环境下(比如PPPoE),路径MTU可能只有1492,加上UDP和自定义头就超了,导致IP分片。

最终他选了1200字节。这个值还考虑到了虚拟币P2P网络的特性:比特币的inv消息、getdata消息通常很小,而block消息很大。以太坊的NewBlock消息在合并后也变小了,但NewBlockHashes可能批量发送。所以,他针对不同消息类型设置了不同的分片阈值:小于1200字节的直接发,大于的才分片。

重组超时与虚拟币的实时性要求

虚拟币网络对时间敏感。比特币出块间隔10分钟,但区块传播速度直接影响孤块率。以太坊出块12秒,传播延迟超过几秒就可能被 uncle 掉。KAS的出块更快,1秒一个块。所以,重组超时不能设太长。

老张最初用了内核默认的30秒,结果发现,一旦有分片丢失,接收端要等30秒才放弃,这期间矿机可能已经白挖了好几个块。后来他在应用层把重组超时设成了500毫秒。500毫秒内没到齐,就发NACK请求重传。如果重传两次还没到,就放弃这个包,让上层协议(比如矿池的Stratum)自己处理超时。

这个超时值是基于他的网络RTT(往返时间)设定的。他的机房之间RTT大约20ms,500ms足够覆盖几次重传。如果RTT更大,比如跨国节点,可能需要1-2秒。

内核参数调优:老张的实战笔记

除了应用层改造,老张也调整了一些内核参数,作为兜底:

bash

sysctl -w net.ipv4.ipfraghighthresh=16777216 sysctl -w net.ipv4.ipfraglowthresh=12582912

减少分片超时时间,加快回收

sysctl -w net.ipv4.ipfrag_time=15

开启分片重组统计,方便监控

sysctl -w net.ipv4.ipfragsecretinterval=0

他还用ethtool -K tun0 tx off rx off关掉了tun设备的校验和卸载,因为TUN设备本身不支持硬件卸载,开着反而增加软件开销。

监控方面,他写了脚本定期读取/proc/net/snmp里的ReasmReqds、ReasmOKs、ReasmFails,以及/proc/net/ipfrag里的队列长度。一旦ReasmFails突增,就说明分片重组有问题。

当虚拟币热点遇上TUN:Layer2与Rollup的启示

老张的故事不是孤例。2024年,随着比特币Layer2和以太坊Rollup的爆发,大量项目需要在自己的节点之间传输证明数据、状态根、交易批次。这些数据往往很大,而且对延迟敏感。很多团队选择用TUN设备搭建覆盖网络,因为TUN简单、通用,不需要改内核。

但TUN设备的分片重组是个绕不开的坎。有些团队直接在应用层用TCP,但TCP在丢包时会有队头阻塞,而且延迟抖动大。有些团队用QUIC,QUIC在用户态实现了可靠传输和分片,但QUIC本身也会分片,而且QUIC的分片是在流级别,不是包级别。

老张最后总结出一条经验:在TUN设备上跑虚拟币协议,不要依赖IP层分片。要么在应用层做分片与重组,要么把消息大小控制在MTU以下。 如果非要用IP分片,那就把重组队列调大,超时调短,并且接受一定的丢包率。

他关掉监控面板,把最后一口方便面汤喝完。窗外天已经蒙蒙亮了。矿机还在嗡嗡作响,算力曲线重新回到了正常水平。他打开Telegram,在矿工群里发了一条消息:“TUN分片重组,别用内核的,自己写。血泪教训。”

然后他靠在椅子上,想着下一个要优化的地方——可能是TUN设备的多队列,或者是用eBPF加速分片处理。虚拟币的世界永远不缺新问题,而TUN设备的数据包重组与分片处理,只是其中一个小小的、却足以让人熬夜的坑。

版权声明:

作者: 最新鸿蒙OS VPN免费节点分享

链接: https://harmonyosvpn.com/tun-debug/tun-packet-reassembly-frag.htm

来源: harmonyosvpn.com

文章版权归作者所有,未经允许请勿转载。

最新文章

归档

标签