选择的方向能够让自己乐在其中
实验目的
基于 packetdrill TCP 三次握手脚本,测试 TCP options 中 SACK 字段的由来,此次构造模拟的是客户端场景。
对于 TCP options,各类介绍资料已经数不胜数,本篇就不再赘述。
基础脚本
# cat tcp_3hs_007.pkt
// TCP 基础之三次握手
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
+0 > S 0:0(0) <...>
+0.01 < S. 0:0(0) ack 1 win 10000 <mss 1000>
+0 > . 1:1(0) ack 1
实验测试一
因为 > 表示预期协议栈会发送的数据包,所以内核协议栈自动构建发送 SYN 数据包。
+0 > S 0:0(0) <...>
// +0 本行代码执行时间相对于上一行代码的偏移时间。
// > ,表示预期协议栈会发送的数据包。
// 0:0(0) ,表示开始序号:结束序号(数据包长度)。
// <> 表示 TCP options,... 表示默认值。
模拟的是客户端场景,SYN 数据包自动构建的情况下,各字段因此无需自定义。
1.执行脚本
# packetdrill tcp_3hs_007.pkt
#
执行完成后退出。
2.捕获数据包
# tcpdump -i any port 8080 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
15:49:36.529121 tun0 Out IP 192.168.174.54.50762 > 192.0.2.1.8080: Flags [S], seq 1691568983, win 64240, options [mss 1460,sackOK,TS val 329810675 ecr 0,nop,wscale 7], length 0
15:49:36.629222 ? In IP 192.0.2.1.8080 > 192.168.174.54.50762: Flags [S.], seq 0, ack 1691568984, win 10000, options [mss 1000], length 0
15:49:36.629256 ? Out IP 192.168.174.54.50762 > 192.0.2.1.8080: Flags [.], ack 1, win 64240, length 0
15:49:36.629427 ? Out IP 192.168.174.54.50762 > 192.0.2.1.8080: Flags [F.], seq 1, ack 1, win 64240, length 0
15:49:36.629444 ? In IP 192.0.2.1.8080 > 192.168.174.54.50762: Flags [R.], seq 1, ack 1, win 10000, length 0
#
观察 tcpdump 抓包结果可以看到客户端 SYN TCP Options 中包含 SACK 字段 ,因为此时在客户端开启了 SACK 支持,所以如上结果。
# sysctl -a | grep tcp_sack
net.ipv4.tcp_sack = 1
#
而服务器端响应的 SYN/ACK 不包含 SACK 字段,因为此时服务器端属于 < 自行构造的数据包,在 <> TCP options 中只有 mss 1000,并没注入 SACK,所以此时 TCP 三次握手的结果,表明之后的 TCP 通讯将不支持 SACK,因为 SYN/ACK 并不支持。
实验测试二
此时修改 SYN/ACK 的 TCP options ,同样增加 SACK 支持,如下:
# cat tcp_3hs_options_sack_001.pkt
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
+0 > S 0:0(0) <...>
+.1 < S. 0:0(0) ack 1 win 10000 <mss 1000, nop, nop, sackOK>
+0 > . 1:1(0) ack 1
#
# packetdrill tcp_3hs_options_sack_001.pkt
#
观察 tcpdump 抓包结果可以看到确实 SYN TCP Options 中包含 SACK 字段 ,且服务器端响应的 SYN/ACK 同样也包含 SACK 字段,所以此时 TCP 三次握手的结果,表明之后的 TCP 通讯将支持 SACK,因为双方均支持。
# tcpdump -i any port 8080 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
16:36:34.089125 tun0 Out IP 192.168.59.18.36452 > 192.0.2.1.8080: Flags [S], seq 3396072839, win 64240, options [mss 1460,sackOK,TS val 3180330847 ecr 0,nop,wscale 7], length 0
16:36:34.189239 tun0 In IP 192.0.2.1.8080 > 192.168.59.18.36452: Flags [S.], seq 0, ack 3396072840, win 10000, options [mss 1000,nop,nop,sackOK], length 0
16:36:34.189277 tun0 Out IP 192.168.59.18.36452 > 192.0.2.1.8080: Flags [.], ack 1, win 64240, length 0
16:36:34.189485 tun0 Out IP 192.168.59.18.36452 > 192.0.2.1.8080: Flags [F.], seq 1, ack 1, win 64240, length 0
16:36:34.189522 tun0 In IP 192.0.2.1.8080 > 192.168.59.18.36452: Flags [R.], seq 1, ack 1, win 10000, length 0
#
实验测试三
此时如果将客户端 SACK 支持关闭,则:
# sysctl -q net.ipv4.tcp_sack=0
#
# sysctl -a | grep tcp_sack
net.ipv4.tcp_sack = 0
#
仍然执行如下脚本,那么可以看到 tcpdump 抓包结果中客户端所发送的 SYN 不再包含 SACK 字段,那么不管服务器端本地的 net.ipv4.tcp_sack 是否启用(对于真实环境),在响应的 SYN/ACK 中都不会包含 SACK 字段。此时 TCP 三次握手的结果,也表明之后的 TCP 通讯将不支持 SACK。
# packetdrill tcp_3hs_007.pkt
#
# tcpdump -i any port 8080 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
16:45:28.581127 tun0 Out IP 192.168.146.240.52106 > 192.0.2.1.8080: Flags [S], seq 92066967, win 64240, options [mss 1460,nop,nop,TS val 2690206826 ecr 0,nop,wscale 7], length 0
16:45:28.681231 tun0 In IP 192.0.2.1.8080 > 192.168.146.240.52106: Flags [S.], seq 0, ack 92066968, win 10000, options [mss 1000], length 0
16:45:28.681260 tun0 Out IP 192.168.146.240.52106 > 192.0.2.1.8080: Flags [.], ack 1, win 64240, length 0
16:45:28.681482 tun0 Out IP 192.168.146.240.52106 > 192.0.2.1.8080: Flags [F.], seq 1, ack 1, win 64240, length 0
16:45:28.681508 tun0 In IP 192.0.2.1.8080 > 192.168.146.240.52106: Flags [R.], seq 1, ack 1, win 10000, length 0
#
测试脚本的服务器端 < 属于手工模拟,所以即便是注入了 SACK 字段,也非真实场景。
SYN SACK
SYN 中 TCP options 的 SACK,代码中是通过 tcp_syn_options 函数确定的,相关的代码流程简要说明如下:
sys_connect
|--inet_stream_connect
| |--tcp_v4_connect
| |--tcp_connect
| |--tcp_transmit_skb
| |--tcp_syn_options
tcp_connect -> tcp_transmit_skb -> __tcp_transmit_skb -> tcp_syn_options 构造 SYN 的 Options 各字段,其中涉及 SACK 。
/* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{
...
/* Send off SYN; include data in Fast Open. */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
if (err == -ECONNREFUSED)
return err;
...
}
EXPORT_SYMBOL(tcp_connect);
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
gfp_t gfp_mask)
{
return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask,
tcp_sk(sk)->rcv_nxt);
}
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
...
if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
}
...
}
如果启用了 TCP SACK(ipv4.sysctl_tcp_sack),则设置 SACK 允许选项标志。
static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts,
struct tcp_md5sig_key **md5)
{
...
if (likely(sock_net(sk)->ipv4.sysctl_tcp_sack)) {
opts->options |= OPTION_SACK_ADVERTISE;
if (unlikely(!(OPTION_TS & opts->options)))
remaining -= TCPOLEN_SACKPERM_ALIGNED;
}
...
}
原文始发于微信公众号(Echo Reply):Wireshark & Packetdrill | TCP 三次握手之 TCP Options 字段 SACK
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论