标签:而在 def 初始 内核版本 scale 图片 font 问题: device
由于packetdrill是在运行时才打开一个tun(由tun.ko驱动的一个虚拟网卡设备)网卡设备,然后对其配置,为了仅仅论述TCP本身,我需要消除任何offload机制的影响,并且我不知道怎么用参数去掉offload(好像也没有!),因此我修改了packetdrill的netdev.c的代码:
/* Set the offload flags to be like a typical ethernet device */
static void set_device_offload_flags(struct local_netdev *netdev)
{
#ifdef linux
// const u32 offload =
// TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 | TUN_F_TSO_ECN | TUN_F_UFO;
// if (ioctl(netdev->tun_fd, TUNSETOFFLOAD, offload) != 0)
// die_perror("TUNSETOFFLOAD");
#endif
}
很傻比的一个修改,特此声明。
首先我给出我的第一个packetdrill脚本,注意看其中的注释。// 建立连接
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
// 完成三次握手
+0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0 > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0 accept(3, ..., ...) = 4
// 发送一个段,注意,它不会诱发拥塞窗口增加,因为初始拥塞窗口已经遵循google的建议变为10个段,此不满10个段会被限制。
// 请用tcpprobe来确认,限制窗口增加的代码请参加本脚本后面的代码。注意,在拥塞窗口从1,2或者3起步的年代,这种限制是不存在的。
+0 write(4, ..., 1000) = 1000
// 发包序列为:+0 > P. 1:1001(1000) ack 1
+.1 < . 1:1(0) ack 1001 win 32792
// 写入4个段,此目的为了触发快速重传
+0 write(4, ..., 4000) = 4000
// 我们得到3个SACK,但是请注意,FACK和标准的SACK的区别要体现了:
// 标准SACK:只有等到4001-5001这一个段以及2001-4001这两个段都收被SACK了,才能触发重传
// FACK:注意到先收到的那个4001-5001 SACK,其与UNA的距离已经超过了3,不用收到2001-5001这个SACK即可触发重传
+.1 < . 1:1(0) ack 1001 win 257 <sack 4001:5001,nop,nop>
+0 < . 1:1(0) ack 1001 win 257 <sack 2001:4001,nop,nop>
// 一个小问题,如果SACK的序列成了以下的:+0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001,nop,nop> 重传还会被触发吗?
// 此时抓包的话,就会发现,重传已经发生
// 全部确认
+.1 < . 1:1(0) ack 5001 win 257
然后抓包来看重传触发的时机,如果你还不是很理解其不同,请一定要做上述的抓包确认,保证可以瞬间搞明白。关于注释里说的那个限制窗口增加的代码如下:
int tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)
{
const struct tcp_sock *tp = tcp_sk(sk);
u32 left;
if (in_flight >= tp->snd_cwnd)
return 1;
left = tp->snd_cwnd - in_flight;
if (sk_can_gso(sk) &&
left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd &&
left * tp->mss_cache < sk->sk_gso_max_size)
return 1;
return left <= tcp_max_burst(tp);
}
这个函数会在增窗前被调用。
下面我用一个综合的复杂点的例子来作为本文接下来的内容,这个例子中不仅仅包含了快速重传,还有关于拥塞窗口的部分细节,请一定仔细分析。这个比较复杂些的例子如下:
// 建立连接
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
// 完成握手
+0 < S 0:0(0) win 65535 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0 > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 65535
+0 accept(3, ..., ...) = 4
// 发送1个段,不会诱发拥塞窗口增加
+0 write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 1001 win 65535
// 再发送1个段,拥塞窗口还是初始值10!
+0 write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 2001 win 65535
// .....
+0 write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 3001 win 65535
// 不管怎么发,只要是每次发送不超过init_cwnd-reordering,拥塞窗口就不会增加,详见上述的tcp_is_cwnd_limited函数
+0 write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 4001 win 65535
// 多发一点,结果呢?自己用tcpprobe确认吧
+0 write(4, ..., 6000) = 6000
+.1 < . 1:1(0) ack 10001 win 65535
// 好吧,我们发送10个段,可以用tcpprobe确认,在收到ACK后拥塞窗口会增加1,这正是慢启动的效果!
+0 write(4, ..., 10000) = 10000
+.1 < . 1:1(0) ack 20001 win 65535
// 该步入正题了。为了触发快速重传,我们发送足够多的数据,一下子发送8个段吧,注意,此时的拥塞窗口为11!
+0 write(4, ..., 8000) = 8000
// 以下为收到的SACK序列。由于我假设你已经通过上面那个简单的packetdrill脚本理解了SACK和FACK的区别,因此这里我们默认开启FACK!
// sack 1的效果:确认了27001-28001,此处距离ACK字段20001为8个段,超过了reordering 3,会立即触发重传。
+.1 < . 1:1(0) ack 20001 win 257 <sack 27001:28001,nop,nop> // ----(sack 1)
+0 < . 1:1(0) ack 20001 win 257 <sack 22001:23001 27001:28001,nop,nop> // ----(sack 2)
+0 < . 1:1(0) ack 20001 win 257 <sack 23001:24001 22001:23001 27001:28001,nop,nop> // ----(sack 3)
+0 < . 1:1(0) ack 20001 win 257 <sack 24001:25001 23001:24001 22001:23001 27001:28001,nop,nop> // ----(sack 4)
// 收到了28001的ACK,注意,此时的reordering已经被更新为6了,另外,这个ACK也会尝试触发reordering的更新,但是并不成功,为什么呢?详情见下面的分析。
+.1 < . 1:1(0) ack 28001 win 65535
// 由于经历了上述的快速重传/快速恢复,拥塞窗口已经下降到了5,为了确认reordering已经更新,我们需要将拥塞窗口增加到10或者11
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 33001 win 65535
// 由于此时拥塞窗口的值为5,我们连续写入几个等于拥塞窗口大小的数据,诱发拥塞窗口增加到10.
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 38001 win 65535
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 43001 win 65535
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 48001 win 65535
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 53001 win 65535
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 58001 win 65535
+0 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 63001 win 65535
// 好吧!此时重复上面发生SACK的序列,写入8个段,我们来看看同样的SACK序列还会不会诱发快速重传!
+0 write(4, ..., 8000) = 8000
// 我们构造同上面sack 1/2/3/4一样的SACK序列,然而等待我们的不是重传被触发,而是...
// 什么?没有触发重传?这不可能吧!你看,70001-71001这个段距离63001为8个段,而此时reordering被更新为6,8>6,依然符合触发条件啊,为什么没有触发呢?
// 答案在于,在于8>6触发快速重传有个前提,那就是开启FACK,然而在reordering被更新的时候,已经禁用了FACK,此后就是要数SACK的段数而不是数最高被SACK的段值了,以下4个SACK只是选择确认了4个段,而4<6,不会触发快速重传。
+.1 < . 1:1(0) ack 63001 win 257 <sack 70001:71001,nop,nop>
+0 < . 1:1(0) ack 63001 win 257 <sack 65001:66001 70001:71001,nop,nop>
+0 < . 1:1(0) ack 63001 win 257 <sack 67001:68001 65001:66001 70001:71001,nop,nop>
+0 < . 1:1(0) ack 63001 win 257 <sack 68001:69001 67001:68001 65001:66001 70001:71001,nop,nop>
// 这里,这里到底会不会触发超时重传呢?取决于packetdrill注入下面这个ACK的时机
// 如果没有发生超时重传,下面这个ACK将会再次把reordering从6更新到8
+.1 < . 1:1(0) ack 71001 win 65535
//从这里往后,属于神的世界...
然后在我们来看抓包结果之前,允许我再次重申,如果不是为了分析协议的细节,请直接使用tcpdump的屏幕输出即可,没必要用wireshark/tshark来展现自己的高大上,我们直接看tcpdump的输出来确认细节:
很明了,如果你去仔细比Linux内核协议栈的实现,会更加清晰,那么在同一TCP连接的后面呈现相同的SACK序列时,情况就不同了,没有触发快速重传,下图分析之:
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
通过packetdrill构造的包序列理解TCP快速重传机制
标签:而在 def 初始 内核版本 scale 图片 font 问题: device
原文地址:https://www.cnblogs.com/ksiwnhiwhs/p/10390197.html