TCP RTT 测不准饱受诟病:
第三点还成了 QUIC 的反面教材。
果真如此?别人怎么教你,你就怎么记笔记,但凡跟笔记对不上的就是错的?
今晚,我来演示一种精确测量 RTT 的方法,你的笔记上肯定没有,但可以加上去。
这种方法的有趣之处恰哈因为它是在 Recovery 状态下测量。按照 TCP 规范,接收端在收到非连续报文时必须立刻 ACK,这避开了 Delayed ACK 的不确定性。
做法很简单:
下面是一个简单的 POC 脚本:
#!/usr/local/bin/stap -g
%{
#include <linux/tcp.h>
#include <net/tcp.h>
__u32 anch = 0;
__u32 skip = 0;
%}
function set_ts(tpp:long, opt:long)
%{
struct tcp_sock *tp = (struct tcp_sock *)STAP_ARG_tpp;
struct sock *sk = (struct sock *)tp;
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_out_options *opts = (struct tcp_out_options *)STAP_ARG_opt;
if (tp != NULL &&
ntohs(inet_sk(sk)->inet_dport) == 5001 &&
icsk->icsk_ca_state == TCP_CA_Recovery) {
if (opts->tsval == skip) { // 避开 anch
opts->tsval ++; // 不要避开太多,当心对端 PAWS 检测
} else if (skip == 0){ // 设置 anch 作为识别符
anch = opts->tsval;
opts->tsval = anch;
skip = anch;
STAP_PRINTF("set ts: %d\n", anch);
}
}
if (tp != NULL && icsk->icsk_ca_state == TCP_CA_Open) {
skip = 0;
anch = 0;
}
%}
function get_ts(skk:long)
%{
struct sock *sk = (struct sock *)STAP_ARG_skk;
struct tcp_sock *tp = (struct tcp_sock *)sk;
struct inet_connection_sock *icsk = inet_csk(sk);
if (ntohs(inet_sk(sk)->inet_dport) == 5001 &&
icsk->icsk_ca_state == TCP_CA_Recovery) {
__u32 ecr = tp->rx_opt.rcv_tsecr + tp->tsoffset;
if (skip == ecr) { // 找到确认打上 anch 的重传包的 ACK。
u32 delta = tcp_time_stamp(tp) - ecr + tp->tsoffset;
delta = delta * (USEC_PER_SEC / TCP_TS_HZ);
STAP_PRINTF("echo ts: %d anch: %d rtt_us: %d srtt_us: %d\n", ecr, anch, delta, tp->srtt_us>>3);
skip = 0;
anch = 0;
}
}
%}
probe kernel.function("tcp_options_write")
{
set_ts($tp, $opts);
}
probe kernel.function("tcp_ack_update_rtt")
{
get_ts($sk);
}
有趣的是,timestamps 在测量 RTT 过程中没有参与任何与时间相关的运算,仅作为识别符。实际中, TCP timestamps 精度太低,不能识别 us,我曾一直以为 TCP 完全依赖它计算 RTT,但并没有:
// 实在是不堪大用
static inline u32 tcp_skb_timestamp(const struct sk_buff *skb)
{
return div_u64(skb->skb_mstamp_ns, NSEC_PER_SEC / TCP_TS_HZ);
}
上述脚本并未涉及扫描 rtx queue 的逻辑,因为需要修改 Linux TCP 源码,这不是晚上一两个小时能搞定的。
TCP 传输优化中,可以这么利用这个精确的 RTT:
玩法还是挺多的…
但如果不支持 timestamps 怎么办?
目标不是用 timestamps,目标是需要一个字段能 echo 回来用来将 Data 和 ACK 对应起来就行。
显然,seq 也可 echo 回来,把它 echo 回来的就是 ACK。为捕捉标识以精确计算 RTT 从而判断是否拥塞,最终决策是否 undo,需花式重传:
花式重传可以区分原始包和重传包:
后面的玩法就和 timestamps 开启时一样的。
花式传输还可以更花,后面单独介绍,本文仅和 RTT 测量相关。
至此,我们已经有了三个信息:
其中后两个都是通过额外的想法挖掘的。我还是老观念,充分利用信息,把现有信息运用到极致后再抱怨信息不够。
这个思路很常用,给定一个数学函数,你能看到它的图像,但这就够了吗?对它求导,对它的导数再求导,再再求导…你就可以看到它的单调性,凸凹,极值等别的性质了。
天气闷热,高温黄色预警,总也不下雨,白天用派森操作意克塞尔表格折腾了一天,晚上换点事情做。随笔记录。
浙江温州皮鞋湿,下雨进水不会胖。