TCP 三次握手

  1. client 发送 SYN
  2. server 收到 SYN 后,回复 SYN + ACK
  3. client 收到 SYN + ACK 后,回复 ACK

其中第2步,server 回复 SYN + ACK 是合并在一起发送的,piggybacking,背着走,搭顺风车。

server 不想接收这次握手,2种情况:

  1. 静默丢包:虽然收到了 SYN,但直接丢弃,会导致 client 不知真相
    1. SYN 在网络上丢失了
    2. 对端收到了,但没回复消息,即静默丢包
    3. 对端收到了也回复了,但回包在网络中丢失了
  2. 直接拒绝
# 列出所有 chain=INPUT 的规则,并且显示行号(便于后续删除使用)
iptables -L INPUT --line-numbers
# 删除某一规则
iptables -D INPUT 1

root@node2:~# iptables -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       tcp  --  anywhere             anywhere             tcp dpt:http
root@node2:~# iptables -D INPUT 1
root@node2:~# iptables -L --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
# 实验环境
# client node1 192.168.0.21
# server node2 192.168.0.22
# server 安装并启动默认的 nginx
# 实验1 - 正常 TCP 三次握手

# server 查看80端口
root@node2:~# lsof -i:80
COMMAND PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   719     root    6u  IPv4  23596      0t0  TCP *:http (LISTEN)
nginx   719     root    7u  IPv6  23597      0t0  TCP *:http (LISTEN)
nginx   724 www-data    6u  IPv4  23596      0t0  TCP *:http (LISTEN)
nginx   724 www-data    7u  IPv6  23597      0t0  TCP *:http (LISTEN)

# client-1 1个窗口使用 tcpdump 抓包
sudo tcpdump -i any host 192.168.0.22 and port 80

# client-2 另一个窗口使用 telnet 连接 server
telnet 192.168.0.22 80

# 此时可以在 client-1 看到三次握手的包
00:49:51.387721 IP 192.168.0.21.54384 > 192.168.0.22.http: Flags [S], seq 2868519268, win 64240, options [mss 1460,sackOK,TS val 2126477609 ecr 0,nop,wscale 6], length 0
00:49:51.388426 IP 192.168.0.22.http > 192.168.0.21.54384: Flags [S.], seq 3802430513, ack 2868519269, win 65160, options [mss 1460,sackOK,TS val 4198512138 ecr 2126477609,nop,wscale 6], length 0
00:49:51.388586 IP 192.168.0.21.54384 > 192.168.0.22.http: Flags [.], ack 1, win 1004, options [nop,nop,TS val 2126477609 ecr 4198512138], length 0

# 实验2 - 静默丢包

# server 使用 iptables 把 80 端口收到的包扔掉
iptables -I INPUT -p tcp --dport 80 -j DROP

# client-1 1个窗口使用 tcpdump 抓包
sudo tcpdump -i any host 192.168.0.22 and port 80

# client-2 另一个窗口使用 telnet 连接 server
telnet 192.168.0.22 80

# client-2 会挂起
# client-1 会看到client 一直发送 SYN 包
00:59:29.556304 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127055777 ecr 0,nop,wscale 6], length 0
00:59:30.580021 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127056801 ecr 0,nop,wscale 6], length 0
00:59:32.595259 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127058816 ecr 0,nop,wscale 6], length 0
00:59:36.723466 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127062944 ecr 0,nop,wscale 6], length 0
00:59:44.915446 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127071136 ecr 0,nop,wscale 6], length 0
01:00:01.043084 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127087264 ecr 0,nop,wscale 6], length 0
01:00:33.300038 IP 192.168.0.21.54430 > 192.168.0.22.http: Flags [S], seq 1232420566, win 64240, options [mss 1460,sackOK,TS val 2127119521 ecr 0,nop,wscale 6], length 0

# 仔细观察,第一次是正常发送 SYN 包
# 指数退避
# 后面6次是重试,并且时间间隔分别是 1s 2s 4s 8s 16s 32s
# 经过6次重试后,又等待了 64s,client-2 telnet 才退出
root@node1:~# telnet 192.168.0.22 80
Trying 192.168.0.22...
telnet: Unable to connect to remote host: Connection timed out

# 实验3 - 直接拒绝

# server 拒绝80端口请求
iptables -I INPUT -p tcp --dport 80 -j REJECT

# client-1 1个窗口使用 tcpdump 抓包
sudo tcpdump -i any host 192.168.0.22 and port 80

# client-2 另一个窗口使用 telnet 连接 server
# 直接显示被拒绝
root@node1:~# telnet 192.168.0.22 80
Trying 192.168.0.22...
telnet: Unable to connect to remote host: Connection refused

# client-1 抓包情况
# 只有1个 SYN 包,没有看到回包
01:11:40.080738 IP 192.168.0.21.54492 > 192.168.0.22.http: Flags [S], seq 599140845, win 64240, options [mss 1460,sackOK,TS val 2127786302 ecr 0,nop,wscale 6], length 0

# 改变抓包条件,把端口去掉,重新抓包
sudo tcpdump -i any host 192.168.0.22
# 可以看到 ICMP 的回包
01:14:08.840140 IP 192.168.0.21.54506 > 192.168.0.22.http: Flags [S], seq 514234273, win 64240, options [mss 1460,sackOK,TS val 2127935061 ecr 0,nop,wscale 6], length 0
01:14:08.840707 IP 192.168.0.22 > 192.168.0.21: ICMP 192.168.0.22 tcp port http unreachable, length 68

# 在 server 上查看 iptables 规则
root@node2:~# iptables-save
# Generated by iptables-save v1.8.4 on Sat Mar 19 01:16:49 2022
*filter
:INPUT ACCEPT [526:44429]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [502:35372]
-A INPUT -p tcp -m tcp --dport 80 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -m tcp --dport 80 -j DROP
COMMIT
# Completed on Sat Mar 19 01:16:49 2022

# 可以看到 iptables 在 REJECT 后面跟上了默认的拒绝行为 --reject-with icmp-port-unreachable
# 可以指定
iptables -I INPUT -p tcp --dport 80 -j REJECT --reject-with tcp-reset

# 在 client-2 中 telnet,同样会被拒绝
root@node1:~# telnet 192.168.0.22 80
Trying 192.168.0.22...
telnet: Unable to connect to remote host: Connection refused

# 可以在 client-1 中看到抓包情况
01:19:19.054012 IP 192.168.0.21.54528 > 192.168.0.22.http: Flags [S], seq 1408630290, win 64240, options [mss 1460,sackOK,TS val 2128245275 ecr 0,nop,wscale 6], length 0
01:19:19.054588 IP 192.168.0.22.http > 192.168.0.21.54528: Flags [R.], seq 0, ack 1408630291, win 0, length 0