TCP Fast Open,简称TFO, 意思是TCP快速打开,是对计算机网络中传输控制协议(TCP)连接的一种简化握手手续的拓展,用于提高两端点间连接的打开速度
二个人只有在认识的基础上才可能深入交流。客户端和服务要传递数据,也需要先“认识”,称为“握手”,握手成功才正式开始传送数据
服务端和客户端传递数据前需要握手三次,这会导致延时,启用 TFO 后,如果验证成功,它可以在三次握手最终的ACK包收到之前就开始发送数据,这样便跳过了一个绕路的行为,于是在传输开始时就降低了延迟
也就是说,TFO 降低了握手阶段的延迟,至于握手成功后数据传递的速度,和 TFO 是没有关系的
Linux kernel 3.7 及以上才支持 TCP Fast Open
在服务端的Ubuntu 检查一下:
uname -r
4.15.0-34-generic
再登录客户端 OpenWrt 路由器确认一下是否可以打开 TCP Fast Open
uname -r
4.9.120
Linux kernel 3.13 及以上默认已经开启了TFO,Linux服务器检查TCP Fast Open是否开启的方法:
cat /proc/sys/net/ipv4/tcp_fastopen
1
如果返回 0 表示没有开启TFO,非0则是默认开启了
tcp_fastopen选项是二进制位掩码,其中第一位启用或禁用客户端支持(默认开启),第二位设置服务端支持(默认关闭),第3位设置是否允许SYN数据包中的数据而不使用TFO cookie选项
-
tcp_fastopen = 1
只能在传出连接上启用(仅限客户端)
-
tcp_fastopen = 2
仅在侦听套接字(服务端)上允许TFO
-
tcp_fastopen = 3
客户端和服务端都启用TFO
请注意,即使启用了这些选项,也必须启用应用程序级支持。我们在服务端和客户端的操作系统上启用了 tcp fast open,如果软件层面不支持,还是起不到TCP通信加速的作用
$ sudo sysctl -w net.ipv4.tcp_fastopen=3
$ cat net.ipv4.tcp_fastopen
3
查看一下 sysctl -w的用法:
$ sysctl --help | grep write
-w, --write enable writing a value to variable
-w 意思是把指定值写入变量
通过 sysctl 我们可以定义内核参数
自定义 TFO 有两种途径:
- /etc/sysctl.conf
- /etc/sysctl.d/*.conf
这两种途径有什么区别?
/etc/sysctl.d/README:
This directory contains settings similar to those found in /etc/sysctl.conf. In general, files in the 10-.conf range come from the procps package and serve as system defaults. Other packages install their files in the 30-.conf range, to override system defaults. End-users can use 60-*.conf and above, or use /etc/sysctl.conf directly, which overrides anything in this directory.
/etc/sysctl.d/目录包含与/etc/sysctl.conf中类似的设置。通常,10-.conf范围内的文件来自procps包和 作为系统默认值。 其他包安装他们的文件30-.conf范围,覆盖系统默认值。 最终用户可以使用60 -*.conf 以上,或直接使用/etc/sysctl.conf,它会覆盖任何内容 这个目录
也就是开头数字小的.conf先加载,后面的覆盖前面,最后加载/etc/sysctl.conf
网上的教程一般是把 TFO 设置写在 /etc/sysctl.conf,那么哪一种方式最佳呢
有少数的软件包建议直接编辑/etc/sysctl.conf,但这可能是为了与旧的GNU / Linux发行版的兼容,最近软件包一般建议使用.d配置目录,这样更加灵活
用的.d目录(如/etc/sysctl.d/)的重点是允许应用程序/管理员在那里添加文件,这比添加或删除单个文件中的条目更容易,更安全。 例如,可以将TFO的参数放在文件/etc/sysctl.d/98-tcp_fastopen.conf中,一看就知道这些参数是针对tcp_fastopen的。 任何自动化工具(包括安装程序)也更容易将文件放在目录中而不是附加到现有文件
明白了原理以后,自然尽量不去编辑 /etc/sysctl.conf 了
$ su
# echo 'net.ipv4.tcp_fastopen=3' > /etc/sysctl.d/98-tcp_fastopen.conf
# reboot
$ cat /proc/sys/net/ipv4/tcp_fastopen
3
上面命令,我们切换到 root 用户,把设置写入文件,然后重启Ubuntu验证,返回3说明设置正确
Linux 内核3.13开始,在应用程序首次设置相关的setsockopt系统调用选项时生成密钥。 在设置密钥之前,proc值全为零
默认情况下,由于密钥在系统重新启动之后不会持续存在,因此正规地使用TFO,应该包括通过sysctl安全地保存密钥,比如生成随机密钥,设置限制性文件权限。 这将确保客户端可以使用现有cookie而无需生成新密钥
要生成新密钥并通过sysctl进行持久化,可以执行以下命令:
$ su
# RAND=$(openssl rand -hex 16)
# NEWKEY=${RAND:0:8}-${RAND:8:8}-${RAND:16:8}-${RAND:24:8}
# echo "net.ipv4.tcp_fastopen_key=$NEWKEY" > /etc/sysctl.d/98-tcp_fastopen_key.conf
# chmod 600 /etc/sysctl.d/98-tcp_fastopen_key.conf; chown root /etc/sysctl.d/98-tcp_fastopen_key.conf
# sysctl -p /etc/sysctl.d/98-tcp_fastopen_key.conf
# unset RAND NEWKEY
在Linux环境下,有二种方法可以显示当TFO密钥:
- $ sudo cat/proc/sys/net/ipv4/tcp_fastopen_key
- $ sudo sysctl net.ipv4.tcp_fastopen_key
同理,以可以用之改变密钥
TFO密钥是16个字节,表示为32个字符的十六进制字符串,分为4个8个字符的块,用短划线分隔,类似这样:
32100e0a-9876daaf-7654b836-21096051
可以使用前述方法实现密钥的转换
在多服务器服务器环境中,您需要随机生成一次密钥,并在所有服务器上设置相同的密钥
如果有多个翻墙VPS,需要随机切换使用,建议所有VPS使用相同密钥
vi /etc/sysctl.conf
# add line
net.ipv4.tcp_fastopen = 3
执行如下命令使之生效:
sysctl -p
在服务端和 OpenWrt 路由器的 shadowsocks-libev 配置文件 config.json / shadowsocks.json 中加上:
"fast_open": true
重启服务端和客户端 shadowsocks-libev
登录shadowsocks服务端 Ubnutu 后,运行命令测试:
# 清空 IP 记录
sudo ip tcp_metrics flush all
ip tcp_metrics
# 应该是空
这时你打开 https://www.youtube.com
然后运行命令:
ip tcp_metrics
如果正常,会看到数条类似下面的记录
216.58.200.277 age 5.356sec fo_mss 1380 fo_cookie 87aaa1f0c8761336 source 210.98.76
发现了 fo_mss
和 fo_cookie
字段没有,这是 TCP Fast Open 特有的字段,其中 fo_cookie 确认我们与行开头的IP地址216.58.200.277 的通信使用了TFO
如果系统还提供除了翻墙的其他网络服务,那么ip记录可能很多,其中一些记录并没有 fo_cookie 字段,可以运行命令把含有 fo_cookie的条目找出来:
ip tcp_metrics | grep fo_cookie
如果一条 fo_cookie 记录也找不到,说明开启 TFO 没有成功
root用户用这条命令查看TCP fast open的详细状态:
root@ubuntu:~# grep '^TcpExt:' /proc/net/netstat | cut -d ' ' -f 87-92 | column -t
我得到的值是:
TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenActiveFail TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPFastOpenBlackhole TCPSpuriousRtxHostQueues BusyPollRxPackets TCPAutoCorking TCPFromZeroWindowAdv TCPToZeroWindowAdv TCPWantZeroWindowAdv
1 12 5 79 23 621 1 70 2 1 0 0 0 48 48 84
如果非 root 用户执行,结果是:
TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPFastOpenBlackhole TCPSpuriousRtxHostQueues BusyPollRxPackets
1 70 2 1 0 0
一定要注意,root用户才能查看 TCPFastOpenPassive 的值
在shadowsocks-libev客户端 ss-rrdir | ss-tunnel 的启动参数中加上 -v
会在控制台显示 TFO 是否启用
kige@openwrt:~# /etc/init.d/shadowsocks stop
/usr/bin/ss-redir -v -b 0.0.0.0 -c /etc/shadowsocks-libev/config.json -f /var/run/shadowsocks.pid
- TCPFastOpenActive - 成功的出站TFO连接数
- TCPFastOpenActiveFail - 收到的SYN-ACK数据包的数量,这些数据包未确认SYN数据包中发送的数据并导致无SYN数据的重传。 请注意原始SYN数据包包含cookie + 数据,这不是连接到不支持TFO的服务器的数量
- TCPFastOpenPassive - 成功的入站TFO连接数
- TCPFastOpenPassiveFail - TFO cookie无效的入站SYN数据包数
- TCPFastOpenCookieReqd - 请求TFO设置但没有cookie的入站SYN数据包数
- TCPFastOpenListenOverflow - 由于套接字已超过最大队列长度而将禁用TFO的入站SYN数据包的数量
上面检测 TCP fast open 状态,得到:
TCPFastOpenPassive
621
成功的入站TFO连接数621,这是各项值中的最大值,说明 TFO 工作正常
-
当带有TFO的包经过路由器 可能会被丢包 不同运营商的策略也不同 如果配置了NAT地址池 在第二次连接时可能会成功 取决于NAT表老化时间 客户端IP改变和NAT之后的公网IP改变都会影响TFO的正常使用 服务端收到不正确的包后依旧可以发送syn-ack 且退回为3WHS 可以选择在服务端单方面将模式改为1或者客户端改为0 不影响服务端对外连接的性能
相关资源:
- https://github.com/softwaredownload/openwrt-fanqiang/blob/master/ubuntu/etc/sysctl.d/98-tcp_fastopen.conf
- https://bradleyf.id.au/nix/shaving-your-rtt-wth-tfo/
- 维基 TCP快速打开
- https://wikitech.wikimedia.org/wiki/TCP_Fast_Open
- https://www.keycdn.com/support/tcp-fast-open/
- 8.6 TCP Fast Open(TFO)
- 各种加密代理协议的简单对比
- https://fanqiang.software-download.name/