TUN模式下的TCP首包延迟增加
操作系统
Windows
系统版本
10 LTSC
安装类型
宣传使用 sing-box 的第三方图形客户端程序 (Windows)
如果您使用图形客户端程序,请提供该程序版本。
V2RayN V6.55
版本
Environment: go1.22.6 windows/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api
Revision: 3066dfe3b31c0d436766047ab6c363be5c60ff53
CGO: disabled
描述
在启用v2RayN的TUN模式后,在建立TCP连接后,发送第一个数据包增加了约200ms-300ms的延迟。测试机器的CPU是1225 v5,理论上不存在CPU瓶颈。
v2RayN自带的绕过大陆和黑名单模式均会遇到问题。
测试服务器在境内,localhost测试没有延迟问题,貌似是在公网上才会遇到
未启用TUN模式:
TCP handshake time: 24.30 ms
Packet 1 round-trip time: 23.83 ms
Received from server: Message received
Packet 2 round-trip time: 23.50 ms
Received from server: Message received
Packet 3 round-trip time: 23.80 ms
Received from server: Message received
Average round-trip time for 3 packets: 23.71 ms
在启用TUN模式后:
TCP handshake time: 0.64 ms
Packet 1 round-trip time: 322.09 ms
Received from server: Message received
Packet 2 round-trip time: 19.89 ms
Received from server: Message received
Packet 3 round-trip time: 19.85 ms
Received from server: Message received
Average round-trip time for 3 packets: 120.61 ms
类似的,建立ssh连接也遇到了类似的延迟增加问题。
SSH handshake time: 217.55 ms
SSH handshake time: 233.25 ms
SSH handshake time: 230.96 ms
SSH handshake time: 246.99 ms
SSH handshake time: 216.03 ms
SSH handshake time: 224.81 ms
SSH handshake time: 234.40 ms
SSH handshake time: 236.22 ms
# 开启TUN后
SSH handshake time: 463.93 ms
SSH handshake time: 469.41 ms
SSH handshake time: 451.46 ms
SSH handshake time: 479.30 ms
SSH handshake time: 458.75 ms
SSH handshake time: 484.66 ms
通过后续时间戳的比较,可以确认是客户端发向服务端的第一个tcp包遇到了延迟,服务器返回的数据包是正常的。在TCP握手后time.sleep(1),发送的第一个数据包没有遇到延迟增加的问题。
使用相同的脚本,把客户端发送的数据从
MESSAGE = b"hello"
改成
MESSAGE = b"GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n"
延迟问题就解决了
在macos(Sequoia,MacBook air M1)上的Shadowrocket,即便开启“直连”,也遇到一模一样的的延迟增加问题,非常诡异,同样也是把MESSAGE改成HTTP头后问题解决。
在macOS和windows的Mihomo core的TUN模式均没有遇到延迟问题,延迟一切正常。
重现方式
GPT写的几个脚本 tcp_test_client.py
import socket
import time
# Server configuration
HOST = 'x.x.x.x'
PORT = 65432
# Dummy data to send
MESSAGE = b"hello"
def main():
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# Measure TCP handshake time
start_handshake_time = time.perf_counter()
s.connect((HOST, PORT))
end_handshake_time = time.perf_counter()
tcp_handshake_time = (end_handshake_time - start_handshake_time) * 1000 # Convert to milliseconds
print(f"TCP handshake time: {tcp_handshake_time:.2f} ms")
# Send multiple packets to measure round-trip time stability
total_round_trip_time = 0
num_packets = 3
# time.sleep(1)
for i in range(num_packets):
try:
# Measure time for sending the packet and receiving the response
start_send_time = time.perf_counter()
s.sendall(MESSAGE)
# Wait for the server response (receive only the first packet)
data = s.recv(1024) # Receiving first response packet
end_receive_time = time.perf_counter()
packet_time = (end_receive_time - start_send_time) * 1000 # Convert to milliseconds
total_round_trip_time += packet_time
print(f"Packet {i+1} round-trip time: {packet_time:.2f} ms")
# Print the received data
if data:
print(f"Received from server: {data.decode('utf-8')}")
else:
print("No data received from server.")
except socket.error as e:
print(f"Error during communication: {e}")
break
# Calculate average round-trip time
average_round_trip_time = total_round_trip_time / num_packets
print(f"Average round-trip time for {num_packets} packets: {average_round_trip_time:.2f} ms")
break
except Exception as e:
print(f"Failed to connect to {HOST}:{PORT}: {e}")
time.sleep(1) # Wait before retrying
while True:
main()
time.sleep(5)
tcp_test_server.py
import socket
# Server configuration
HOST = '0.0.0.0' # Listen on all interfaces
PORT = 65432 # Port to listen on
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print(f"Server listening on {HOST}:{PORT}")
while True:
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
while True:
# Receive data from the client
data = conn.recv(1024)
if not data:
print("Connection closed by client.")
break # Exit loop if the client has closed the connection
print(f"Received: {data.decode('utf-8')}")
# Send acknowledgment back to the client
conn.sendall(b"Message received")
print("Acknowledgment sent to client.")
if __name__ == "__main__":
main()
ssh_test.py
import paramiko
import time
def measure_ssh_handshake_time(host, port=22, username=None, password=None, pkey=None):
# Create a new SSH client
client = paramiko.SSHClient()
# Automatically add untrusted hosts (make sure okay for security policy in real usage)
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Measure the time it takes to perform the SSH handshake
try:
start_time = time.time()
# Connect to the SSH server (without actually opening a session or executing commands)
client.connect(hostname=host, port=port, username=username, password=password, pkey=pkey, timeout=10)
# Time when the server's certificate is received and validated
end_time = time.time()
handshake_time = (end_time - start_time) * 1000 # Convert to milliseconds
print(f"SSH handshake time: {handshake_time:.2f} ms")
except Exception as e:
print(f"Failed to connect to {host}:{port} - {e}")
finally:
# Close the connection
client.close()
if __name__ == "__main__":
while True:
# Specify the host and port you want to connect to
HOST = 'x.x.x.x' # Replace with the actual server address
PORT = 22 # Default SSH port, change if necessary
# Optionally, you can provide a username and password or a private key
USERNAME = 'xxx'
PASSWORD = 'xxxx' # Alternatively, set to None if using key-based authentication
PKEY = None # Replace with paramiko.RSAKey object if using key-based authentication
measure_ssh_handshake_time(HOST, PORT, USERNAME, PASSWORD, PKEY)
time.sleep(1)
日志
No response
支持我们
- [ ] 我已经 赞助
完整性要求
- [X] 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
- [X] 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
- [X] 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
- [x] 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
我们不是广告软件的客服,请反馈 sing-box 的问题并排除其他程序造成的影响。
我们不是广告软件的客服,请反馈 sing-box 的问题并排除其他程序造成的影响。
谢谢回复,但是 v2rayN的TUN模式使用了sing-box,我单独测试sing-box仍能复现这个问题,于是才来的这里提出的issue。 测试的远端服务器在腾讯云北京。
TCP handshake time: 0.45 ms
Packet 1 round-trip time: 356.18 ms
Received from server: Message received
Packet 2 round-trip time: 24.86 ms
Received from server: Message received
Packet 3 round-trip time: 24.97 ms
Received from server: Message received
Average round-trip time for 3 packets: 135.34 ms
以下是我使用的sing-box配置文件,我使用sing-box的经验不多,如果有配置错误烦请指出🙏。
{
"log": {
"level": "warn",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "local",
"address": "223.5.5.5",
"strategy": "ipv4_only",
"detour": "direct"
}
],
"final": "local"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "singbox_tun",
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000,
"auto_route": true,
"strict_route": true,
"stack": "gvisor",
"sniff": true
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns_out"
}
],
"route": {
"auto_detect_interface": true,
"rules": [
{
"outbound": "direct",
"port_range": [
"0:65535"
]
}
]
}
}
sing-box version 1.9.4
Environment: go1.22.6 windows/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api
Revision: 3066dfe3b31c0d436766047ab6c363be5c60ff53
CGO: disabled
sing-box version 1.10.0-beta.4
Environment: go1.23.0 windows/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api
Revision: 68ec00aaa5a20f1adbf9dfb56804ee2e0c4d0671
CGO: disabled
两个版本均可复现。
日志如下,管理员身份运行。
PS C:\Users\_\Desktop\sing_box> .\sing-box.exe -c .\config.json run
+0800 2024-08-27 16:51:03 [36mINFO[0m router: updated default interface 以太网, index 7
+0800 2024-08-27 16:51:03 [36mINFO[0m inbound/tun[tun-in]: started at singbox_tun
+0800 2024-08-27 16:51:03 [36mINFO[0m sing-box started (0.588s)
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;208m81714368[0m 0ms] inbound/tun[tun-in]: inbound packet connection from 172.19.0.1:52692
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;208m81714368[0m 3ms] inbound/tun[tun-in]: inbound packet connection to 172.19.0.2:53
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;208m81714368[0m 25ms] outbound/direct[direct]: outbound packet connection
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;131m3542776947[0m 0ms] inbound/tun[tun-in]: inbound packet connection from 172.19.0.1:56624
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;131m3542776947[0m 1ms] inbound/tun[tun-in]: inbound packet connection to 172.19.0.2:53
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;131m3542776947[0m 3ms] outbound/direct[direct]: outbound packet connection
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;104m3538114904[0m 0ms] inbound/tun[tun-in]: inbound packet connection from 172.19.0.1:57973
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;104m3538114904[0m 0ms] inbound/tun[tun-in]: inbound packet connection to 172.19.0.2:53
+0800 2024-08-27 16:51:04 [36mINFO[0m [[38;5;104m3538114904[0m 2ms] outbound/direct[direct]: outbound packet connection
+0800 2024-08-27 16:51:07 [36mINFO[0m [[38;5;69m4175692085[0m 0ms] inbound/tun[tun-in]: inbound connection from 172.19.0.1:57348
+0800 2024-08-27 16:51:07 [36mINFO[0m [[38;5;69m4175692085[0m 0ms] inbound/tun[tun-in]: inbound connection to x.x.x.x:65432
+0800 2024-08-27 16:51:07 [36mINFO[0m [[38;5;69m4175692085[0m 304ms] outbound/direct[direct]: outbound connection to x.x.x.x:65432
从日志最后一行来看,我个人猜测可能是分流时耗费了时间,发送的
MESSAGE从hello
改成
MESSAGE = b"GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n"
之类的类似的http头,延迟会大幅降低
+0800 2024-08-27 16:57:27 [36mINFO[0m [[38;5;50m4049345017[0m 23ms] outbound/direct[direct]: outbound connection to x.x.x.x7:65432
ssh之类的连接延迟均在200-300ms左右,貌似是只有http头能在30ms内
但个人不会写golang,还在艰难地研究中,抱歉
对于服务器先发协议,是预期行为;对于其他类型的连接,应已修复部分问题。