灵感来自于2025年8月27日,真是个好日子,我的生日。
在仔细对比了TCP协议后,进行了代码实现。于2025年8月29日,也是个好日子,七夕,大概下午吧,iperf3测试从15-20Mbps上升到了家庭带宽上限。
但我认为这100%属于一种hack方法,并不主流,也不符合网络协议标准,这破坏了TCP标准通信流程。所以,如果你能使用UDP隧道,那使用UDP是最好的办法。如果你有不得不使用TCP隧道的理由,又遇到了跟本文的一样的问题,那不妨一试。
接下来我会尽可能详细、完整的描述这一技术原理。
1、条件背景
- A、B连个远程设备之间使用tun虚拟网卡构建异地局域网,A 虚拟ip 10.18.18.A、B虚拟ip10.18.18.B
- A、B之间使用TCP隧道连接,服务器中转,服务器带宽500Mbps,A、B两边最小上传带宽50Mbps
- A、B使用TCP通信,比如访问网页,或其它基于TCP协议的服务
- 完整通信过程:tun(10.18.18.A)<—->socketA<—->服务器(socket1<–>socket2)<—->socketB<—->tun(10.18.18.B),从A网卡读取数据包通过socketA发往服务器socket1,服务器socket1收到数据通过socket2发往socketB,socketB收到数据后写入网卡B,完成一次数据发送,反之亦然
2、实际问题
在实际通信中,无论使用打洞隧道还是服务器中转隧道
- HTTP下载文件大多数时候都是远远达不到上传带宽峰值,仅有时候可以(应该是“捎带确认”的优化机制的原因)
- iperf3则无论如何都无法达到上传峰值,大概速率为10-20Mbps之间波动,同样的隧道,直接发送数据或使用端口转发方式通信时速率正常
3、表现猜测
经过Wireshark抓包,物理网卡上,隧道没有丢包,没有双重确认等问题。虚拟网卡上,窗口非常小。在仔细分析整个通信过程后,觉得伪造和丢弃ACK数据包非常的可行。
完整链路:(tun(10.18.18.A)<—->socketA<—->服务器(socket1<–>socket2)<—->socketB<—->tun(10.18.18.B))
- 默认情况下,A到B发送一个PSH+ACK数据包,将有以下过程
- socketA–PSH+ACK–>socket1,socket1–ACK–>socketA
- socket2--PSH+ACK–>socketB,socketB–ACK–>socket2
- PSH+ACK到达B网卡后,B网卡会回复一条ACK,当然在隧道里被包装为PSH+ACK
- socketB–PSH+ACK(ACK)–>socket2,socket2–ACK–>socketB
- socket1–PSH+ACK(ACK)–>socketA,socketA–ACK–>socket1
- 如果丢弃回复的ACK,那A到B发送一个PSH+ACK数据包,将有以下过程
- socketA–PSH+ACK–>socket1,socket1–ACK–>socketA
- socket2--PSH+ACK–>socketB,socketB–ACK–>socket2
4、优化原理
有几个前提
- ACK包的序列号可以使用SYN+ACK的序列号+1得到
- ACK包的确认号为要确认的包的 序列号+荷载长度
- ACK包需要计算校验和,没有荷载数据
最大的挑战是,如何在不影响正常的TCP握手和挥手以及无需复杂的状态管理的情况下丢弃ACK包,以一个完整TCP连接流程为例
- A–>SYN–>B
- B–>SYN+ACK–>A,在B、A分别取出SYN+ACK包的序列号+1,得到新序列号,用四元组记录状态
- B、(源IP,源端口,目标IP,目标端口):(新序列号,丢包=true)
- A、(源IP,源端口,目标IP,目标端口):(新序列号,丢包=false),A还要给B回复一个ACK,先不丢包
- A–>ACK–>B,检查状态,发现丢包==false,原样发送,并设置丢包=true,丢弃以后的ACK
- A–>PSH+ACK–>B,数据包原样发送到B,从状态获取新序列号,伪造B到A的ACK包写入A网卡
- B–>ACK–>A,检查状态,发现丢包==true,直接丢包
- ……
- A–>FIN/RST–>B,发送方和接收方都删除状态,发送和收到RST时也删除
- B–>ACK–>A,没有状态,原样发送
- B–>FIN+ACK,原样发送
- A–>ACK–>B,没有状态,原样发送
大概流程图
