当前位置:   article > 正文

【计算机网络】 0、各网络命令 + tcpdump + Wireshark、抓包实战、TCP 握手挥手、防火墙、保活、MTU_wireshark tcpdump动态实时抓包

wireshark tcpdump动态实时抓包

文章目录

一、各层网络工具

  • TCP是有前后顺序的,对应的socket类型是SOCK_STREAM
  • UDP是无序的数据单元,对应的socket类型是SOCK_DGRAM

一个TCP流对应的是五元组:传输协议类型、源IP、源端口、目标IP、目标端口, 例如(TCP, myip, myport, www.baidu.com, 443)

各分层和数据的术语如下:

  • 广义的packet,是报文,是一种相对宽泛的说法,基本上每一层都可使用,例如应用层叫HTTP报文,网络层叫IP报文。
  • frame是帧,是第二层数据链路层的概念,包含帧头、载荷、帧尾。
  • 狭义的packet,是分组,是IP报文。
  • TCP segment 是段, 应用层将message交给传输层,若该message大小超出传输层数据单元的限制(如超出TCP的MSS),则会被划分为多个segments。此过程叫做分段(segmentation),也是TCP层重要的职责之一。

在这里插入图片描述

下面介绍各层排查工具:
在这里插入图片描述

应用层

Chrome 的 F12 打开开发者工具,其可以做以下工作:

找到服务器的 IP

  • 如下图中的 Remote Address:

在这里插入图片描述

查接口、对象的耗时

在这里插入图片描述

删除指定网站的Cookie

在 Storage -> Cookie 中清除对应的条目,下次再访问此网站即可被视为全新的访问了

在这里插入图片描述

表示层、会话层

  • 可以查看网站的 TLS 证书,示例如下:
    在这里插入图片描述

tcpdump

可以用 tcpdump 或 wireshard 查看 TLS 握手、秘钥交换、密文传输的细节。例如下图可以看到 TLS 握手阶段,双方协商过程中各自展示 CIpher Suite,而在 Chrome 的开发者工具中只能看到协商完毕后的结果,如下图:

tcpdump 用法如下:

  • tcpdump -i eth0:指定网卡
  • tcpdump src host hostname:指定来源

案例如下:

  • tcpdump 'tcp and port 80 and src host 192.168.1.25' :抓 tcp 包,且 port 是 80,来自 192.168.1.25 地址。
  • tcpdump 'tcp and port 80 and tcp[13:1]&2 != 0': tcp[13:1] 表示的是 TCP 头部开始处偏移为 13 的字节,如果这个值为 2,说明设置了 SYN 分节。当然我们也可以设置成其他值来获取希望类型的分节。
  • tcpdump host 10.1.11.133 and udp port 5060 -v -w gw.cap:写成 wireshark 可读取的 cap 格式

tcpdump 基于libpcap在开启抓包的时候,会自动创建一个类型为 AF_PACKET 的网络套接口,并向系统内核注册。当网卡接收到一个网络报文之后,它会遍历系统中所有已经被注册的网络协议,包括其中已经注册了的 AF_PACKET 网络协议。系统内核接下来就会将网卡收到的报文发送给该协议的回调函数进行一次处理,回调函数可以把接收到的报文完完整整地复制一份,假装是自己接收到的报文,然后交给 tcpdump 程序,进行各种条件的过滤和判断,再对报文进行解析输出。

RFC 的内容,对初学者来说可能并不友好。如果你对网络协议还不太了解,推荐你去学《TCP/IP 详解》,特别是第一卷的 TCP/IP 协议族。这是每个程序员都要掌握的核心基础知识。

再回到 tcpdump 工具本身,它的基本使用方法,还是比较简单的,也就是 tcpdump [选项] [过滤表达式]。当然,选项和表达式的外面都加了中括号,表明它们都是可选的。查看 tcpdump 的手册 ,以及 pcap-filter 的手册,你会发现,tcpdump 提供了大量的选项以及各式各样的过滤表达式。不过不要担心,只需要掌握一些常用选项和过滤表达式,就可以满足大部分场景的需要了。


下面这张图显示的是 tcpdump 的输出格式:


首先我们看到的是时间戳,之后类似 192.168.33.11.41388 > 192.168.33.11.6443 这样的,显示的是源地址(192.168.33.11.41388)到目的地址(192.168.33.11.6443);然后 Flags [ ] 是包的标志,[P] 表示是数据推送,比较常见的包格式如下:

  • [S]:SYN,表示开始连接
  • [.]:没有标记,一般是确认
  • [P]:PSH,表示数据推送
  • [F]:FIN,表示结束连接
  • [R] :RST,表示重启连接

我们可以看到最后有几个数据,它们代表的含义如下:

  • seq:包序号,就是 TCP 的确认分组
  • cksum:校验码
  • win:滑动窗口大小
  • length:承载的数据(payload)长度 length,如果没有数据则为 0

wireshark

Wireshark 也是最流行的一个网络分析工具,它最大的好处就是提供了跨平台的图形界面。跟 tcpdump 类似,Wireshark 也提供了强大的过滤规则表达式,同时,还内置了一系列的汇总分析工具。

比如,拿刚刚的 ping 案例来说,你可以执行下面的命令,把抓取的网络包保存到 ping.pcap 文件中:
$ tcpdump -nn udp port 53 or host 35.190.27.188 -w ping.pcap

接着,把它拷贝到你安装有 Wireshark 的机器中,比如你可以用 scp 把它拷贝到本地来:
$ scp host-ip/path/ping.pcap .

然后,再用 Wireshark 打开它。打开后,你就可以看到下面这个界面:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从 Wireshark 的界面里,你可以发现,它不仅以更规整的格式,展示了各个网络包的头部信息;还用了不同颜色,展示 DNS 和 ICMP 这两种不同的协议。你也可以一眼看出,中间的两条 PTR 查询并没有响应包。

接着,在网络包列表中选择某一个网络包后,在其下方的网络包详情中,你还可以看到,这个包在协议栈各层的详细信息。比如,以编号为 5 的 PTR 包为例:

你可以看到,IP 层(Internet Protocol)的源地址和目的地址、传输层的 UDP 协议(Uder Datagram Protocol)、应用层的 DNS 协议(Domain Name System)的概要信息。

继续点击每层左边的箭头,就可以看到该层协议头的所有信息。比如点击 DNS 后,就可以看到 Transaction ID、Flags、Queries 等 DNS 协议各个字段的数值以及含义。

当然,Wireshark 的功能远不止如此。接下来我再带你一起,看一个 HTTP 的例子,并理解 TCP 三次握手和四次挥手的工作原理。

这个案例我们将要访问的是 http://example.com/ 。进入终端一,执行下面的命令,首先查出 example.com 的 IP。然后,执行 tcpdump 命令,过滤得到的 IP 地址,并将结果保存到 web.pcap 中。
$ dig +short example.com
93.184.216.34
$ tcpdump -nn host 93.184.216.34 -w web.pcap # 实际上,你可以在 host 表达式中,直接使用域名,即 tcpdump -nn host example.com -w web.pcap。


接下来,切换到终端二,执行下面的 curl 命令,访问 http://example.com:
$ curl http://example.com


最后,再回到终端一,按下 Ctrl+C 停止 tcpdump,并把得到的 web.pcap 拷贝出来。
使用 Wireshark 打开 web.pcap 后,你就可以在 Wireshark 中,看到如下的界面:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12


由于 HTTP 基于 TCP ,所以你最先看到的三个包,分别是 TCP 三次握手的包。接下来,中间的才是 HTTP 请求和响应包,而最后的三个包,则是 TCP 连接断开时的三次挥手包。

从菜单栏中,点击 Statistics -> Flow Graph,然后,在弹出的界面中的 Flow type 选择 TCP Flows,你可以更清晰的看到,整个过程中 TCP 流的执行过程:

这其实跟各种教程上讲到的,TCP 三次握手和四次挥手很类似,作为对比, 你通常看到的 TCP 三次握手和四次挥手的流程,基本是这样的:

不过,对比这两张图,你会发现,这里抓到的包跟上面的四次挥手,并不完全一样,实际挥手过程只有三个包,而不是四个。

其实,之所以有三个包,是因为服务器端收到客户端的 FIN 后,服务器端同时也要关闭连接,这样就可以把 ACK 和 FIN 合并到一起发送,节省了一个包,变成了“三次挥手”。

而通常情况下,服务器端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端一个 ACK 包。稍等一会儿,完成所有数据包的发送后,才会发送 FIN 包。这也就是四次挥手了。

抓包后, Wireshark 中就会显示下面这个界面(原始网络包来自 Wireshark TCP 4-times close 示例,你可以点击 这里 下载):

当然,Wireshark 的使用方法绝不只有这些,更多的使用方法,同样可以参考 官方文档 以及 WIKI

传输层

telnet: 路径可达性测试

NAME
     telnet — user interface to the TELNET protocol

SYNOPSIS
     telnet [-468ELadr] [-S tos] [-b address] [-e escapechar] [-l user] [-n tracefile] [host [port]]

DESCRIPTION
     The telnet command is used for interactive communication with another host using the TELNET protocol. It begins in command mode, where it prints a telnet prompt ("telnet> "). If telnet is invoked with a host argument, it per‐
     forms an open command implicitly; see the description below.

telnet www.baidu.com 443
Trying 110.242.68.4...
Connected to www.a.shifen.com.
Escape character is '^]'.
^CConnection closed by foreign host.


telnet www.github.com 443
Trying 20.205.243.166...
Connected to github.com.
Escape character is '^]'.
^CConnection closed by foreign host.

telnet 192.168.2.133 22
Trying 192.168.2.133...
Connected to 192.168.2.133.
Escape character is '^]'.
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.10
^CConnection closed by foreign host.

telnet 192.168.2.133 443
Trying 192.168.2.133...
telnet: connect to address 192.168.2.133: Connection refused
telnet: Unable to connect to remote host
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

nc: 路径可达性测试

NAME
     nc — arbitrary TCP and UDP connections and listens

SYNOPSIS
     nc [-46bCDdhklnrStUuvZz] [-I length] [-i interval] [-O length] [-P proxy_username] [-p source_port] [-q seconds] [-s source] [-T toskeyword] [-V rtable] [-w timeout] [-X proxy_protocol] [-x proxy_address[:port]] [destination]
        [port]

DESCRIPTION
     The nc (or netcat) utility is used for just about anything under the sun involving TCP, UDP, or UNIX-domain sockets.  It can open TCP connections, send UDP packets, listen on arbitrary TCP and UDP ports, do port scanning, and
     deal with both IPv4 and IPv6.  Unlike telnet(1), nc scripts nicely, and separates error messages onto standard error instead of sending them to standard output, as telnet(1) does with some.
     
	Common uses include:
	
	      ·   simple TCP proxies
	      ·   shell-script based HTTP clients and servers
	      ·   network daemon testing
	      ·   a SOCKS or HTTP ProxyCommand for ssh(1)
	      ·   and much, much more

nc -w 2 -zv 192.168.2.99 8000-8009
Connection to 192.168.2.99 8000 port [tcp/*] succeeded!
nc: connect to 192.168.2.99 port 8001 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8002 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8003 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8004 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8005 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8006 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8007 (tcp) failed: Connection refused
nc: connect to 192.168.2.99 port 8008 (tcp) failed: Connection refused
Connection to 192.168.2.99 8009 port [tcp/*] succeeded!

nv -w 2 -zv www.baidu.com 443
Connection to www.baidu.com 443 port [tcp/https] succeeded!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

netstat:查看当前连接状态

NAME
       netstat - Print network connections, routing tables, interface statistics, masquerade connections, and multicast memberships

netstat -ant | less
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:1947            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN
tcp        0      0 192.168.2.99:47356   	192.168.103.243:8080    ESTABLISHED
tcp        0      1 192.168.2.99:60912   	142.251.42.234:443      SYN_SENT
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

iftop:查看当前连接的传输速率

NAME
       iftop - display bandwidth usage on an interface by host

SYNOPSIS
       iftop -h | [-nNpblBP] [-i interface] [-f filter code] [-F net/mask] [-G net6/mask6]

DESCRIPTION
       iftop  listens  to  network  traffic  on a named interface, or on the first interface it can find which looks like an external interface if none is specified, and displays a table of current bandwidth usage by pairs of hosts.
       iftop must be run with sufficient permissions to monitor all network traffic on the interface; see pcap(3) for more information, but on most systems this means that it must be run as root.

       By default, iftop will look up the hostnames associated with addresses it finds in packets. This can cause substantial traffic of itself, and may result in a confusing display. You may wish to suppress display of DNS  traffic
       by using filter code such as not port domain, or switch it off entirely, by using the -n option or by pressing r when the program is running.

       By  default, iftop counts all IP packets that pass through the filter, and the direction of the packet is determined according to the direction the packet is moving across the interface.  Using the -F option it is possible to
       get iftop to show packets entering and leaving a given network.  For example, iftop -F 10.0.0.0/255.0.0.0 will analyse packets flowing in and out of the 10.* network.

       Some other filter ideas:

       not ether host ff:ff:ff:ff:ff:ff
              Ignore ethernet broadcast packets.

       port http and not host webcache.example.com
              Count web traffic only, unless it is being directed through a local web cache.

       icmp   How much bandwidth are users wasting trying to figure out why the network is slow?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在这里插入图片描述

netstat -s: 查看丢包和乱序的统计

netstat 和 lsof 侧重点不同,可配合使用:

  • netstat 可以查 IP、port 的使用情况,各 TCP 连接的状态
  • lsof 可以查指定的 IP 或 port 上打开的 socket 进程

netstat 处理可获取实时连接状态,还可获取历史统计信息。例如你怀疑一台机器网络很不稳定,除了用 PING,还可用 netstat -s 获取更详细信息。例如其中的 TCP 丢包和乱序的计数值,可以帮你判断传输层的状况。

netstat -s
...
Tcp:
    796288992 active connections openings
    434510098 passive connection openings
    19310764 failed connection attempts
    12878149 connection resets received
    511 connections established
    29366081857 segments received
    33470764155 segments send out
    2921003 segments retransmited
    1066 bad segments received.
    835349688 resets sent
TcpExt:
    69 resets received for embryonic SYN_RECV sockets
    28 packets pruned from receive queue because of socket buffer overrun
    26 ICMP packets dropped because socket was locked
    7049182 TCP sockets finished time wait in fast timer
    1858219 time wait sockets recycled by time stamp
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

还可用 watch --diff netstat -s 查看动态变化,如下图所示:
在这里插入图片描述
当然你可以把 netstat -s 的输出值写入 TSDB,并用 Grafana 展示历史曲线,可得到历史任意时刻的值,和抖动速率,这是更专业的运维操作。

netstat -alepn 查看所有连接详情:

在这里插入图片描述

netstat Socket -x alepn 查看本地 socket:

ss:新一代 netstat

netstat 的功能被拆分到 ss 和 ip 两个命令中,并分别得到加强。

SS(8)                                        System Manager's Manual                                       SS(8)

NAME
       ss - another utility to investigate sockets

SYNOPSIS
       ss [options] [ FILTER ]

DESCRIPTION
       ss  is  used to dump socket statistics. It allows showing information similar to netstat.  It can display
       more TCP and state information than other tools.


ss -s
Total: 78
TCP:   0 (estab 0, closed 0, orphaned 0, timewait 0)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       2         1         1
TCP       0         0         0
INET      2         1         1
FRAG      0         0         0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

lsof:查看谁打开了此文件

lsof /var/run/docker.sock 查看谁打开了此文件,下图显示 dockerd 打开了此本地 socket:

在这里插入图片描述

lsof -i :8080 查出谁在占用 8080 端口

网络层

traceroute: 查看网络路径状况

TRACEROUTE6(8)                                       iputils                                      TRACEROUTE6(8)

NAME
       traceroute6 - traces path to a network host

SYNOPSIS
       traceroute6 [-dnrvV] [-i interface] [-m max_ttl] [-p port] [-q max_probes] [-s source] [-w wait time]
                   {destination} [size]

DESCRIPTION
       Description can be found in traceroute(8), all the references to IP replaced to IPv6. It is needless to
       copy the description from there.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

mtr: 连续多次路径探测

mtr 是 traceroute 的超集,有丰富的探测报告,它的 每一跳丢包的百分比 是定位路径中节点问题的重要指标。当遇到连接状况时好时坏时,单纯的一次 traceroute 难以看清楚,则可用 mtr 获取更全面的链路状态。

MTR(8)                                        System Administration                                       MTR(8)

NAME
       mtr - a network diagnostic tool

SYNOPSIS
       mtr  [-4|-6]  [-F FILENAME]  [--report]  [--report-wide]  [--xml] [--gtk] [--curses] [--displaymode MODE]
       [--raw] [--csv] [--json] [--split] [--no-dns] [--show-ips] [-o FIELDS] [-y IPINFO]  [--aslookup]  [-i IN‐
       TERVAL]  [-c COUNT]  [-s PACKETSIZE] [-B BITPATTERN] [-G GRACEPERIOD] [-Q TOS] [--mpls] [-I NAME] [-a AD‐
       DRESS] [-f FIRST-TTL] [-m MAX-TTL] [-U MAX-UNKNOWN] [--udp]  [--tcp]  [--sctp]  [-P PORT]  [-L LOCALPORT]
       [-Z TIMEOUT] [-M MARK] HOSTNAME

DESCRIPTION
       mtr combines the functionality of the traceroute and ping programs in a single network diagnostic tool.

       As  mtr starts, it investigates the network connection between the host mtr runs on and HOSTNAME by send‐
       ing packets with purposely low TTLs.  It continues to send packets with low TTL, noting the response time
       of  the  intervening routers.  This allows mtr to print the response percentage and response times of the
       internet route to HOSTNAME.  A sudden increase in packet loss or response time is often an indication  of
       a bad (or simply overloaded) link.

       The results are usually reported as round-trip-response times in milliseconds and the percentage of pack‐
       etloss.

root@node# mtr www.baidu.com -r -c 10
Start: 2022-11-14T23:15:07+0800
HOST: abc                         Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|-- y.mshome.net               0.0%    10    0.9   1.1   0.4   1.4   0.3
  2.|-- bogon                      0.0%    10    5.9   5.3   4.8   6.3   0.4
  3.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
  4.|-- bogon                      0.0%    10   24.1  30.0  19.8  50.3  11.1
  5.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
  6.|-- 36.112.243.137             0.0%    10   17.3  31.5  16.5  65.0  14.0
  7.|-- 36.112.243.153             0.0%    10   42.6  32.6  19.3  44.0   8.9
  8.|-- 36.110.248.126            80.0%    10   43.0  31.2  19.4  43.0  16.7
  9.|-- 36.110.249.58             70.0%    10   36.1  34.5  27.7  39.6   6.1
 10.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
 11.|-- 220.181.17.94              0.0%    10   42.8  39.4  24.8  54.3   9.9
 12.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
 13.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
 14.|-- ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
 15.|-- 220.181.38.149             0.0%    10   46.8  34.7  19.3  49.6  11.7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

route、netstat、ip: 查看路由

ROUTE(8)                               Linux System Administrator's Manual                              ROUTE(8)

NAME
       route - show / manipulate the IP routing table

SYNOPSIS
       route [-CFvnNee] [-A family |-4|-6]

       route  [-v] [-A family |-4|-6] add [-net|-host] target [netmask Nm] [gw Gw] [metric N] [mss M] [window W]
              [irtt I] [reject] [mod] [dyn] [reinstate] [[dev] If]

       route  [-v] [-A family |-4|-6] del [-net|-host] target [gw Gw] [netmask Nm] [metric M] [[dev] If]

       route  [-V] [--version] [-h] [--help]

DESCRIPTION
       Route manipulates the kernel's IP routing tables.  Its primary use is to set up static routes to specific
       hosts or networks via an interface after it has been configured with the ifconfig(8) program.

       When  the  add  or del options are used, route modifies the routing tables.  Without these options, route
       displays the current contents of the routing tables.


y# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         y.mshome.net    0.0.0.0         UG    0      0        0 eth0
192.168.128.0   0.0.0.0         255.255.240.0   U     0      0        0 eth0

y# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.128.1   0.0.0.0         UG    0      0        0 eth0
192.168.128.0   0.0.0.0         255.255.240.0   U     0      0        0 eth0

y# netstat -r
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
default         y.mshome.net    0.0.0.0         UG        0 0          0 eth0
192.168.128.0   0.0.0.0         255.255.240.0   U         0 0          0 eth0

y# ip route
default via 192.168.128.1 dev eth0 proto kernel
192.168.128.0/20 dev eth0 proto kernel scope link src 192.168.129.90
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

数据链路层、物理层

ethtool: 查看网卡驱动

ETHTOOL(8)                                   System Manager's Manual                                  ETHTOOL(8)

NAME
       ethtool - query or control network driver and hardware settings

y# ethtool -S eth0
NIC statistics:
     tx_scattered: 0
     tx_no_memory: 0
     tx_no_space: 0
     tx_too_big: 0
     tx_busy: 0
     tx_send_full: 0
     rx_comp_busy: 0
     rx_no_memory: 0
     stop_queue: 0
     wake_queue: 0
     vlan_error: 0
     vf_rx_packets: 0
     vf_rx_bytes: 0
     vf_tx_packets: 0
     vf_tx_bytes: 0
     vf_tx_dropped: 0
     tx_queue_0_packets: 547
     tx_queue_0_bytes: 170662
     ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

待分类

设置机器 IP

  • /etc/network/interfaces 如下:
    • 静态 ip
    source /etc/network/interfaces.d/*
    auto eno1
    address 192.168.100.176
    netmask 255.255.254.0
    gateway 192.168.101.254
    dns-nameservers 114.114.114.114
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 通过 dhcp 获取动态 ip
    source /etc/network/interfaces.d/*
    auto eno1
    iface eno1 inet dhcp
    dns-nameservers 114.114.114.114
    
    • 1
    • 2
    • 3
    • 4
    • ifdown eno1; ifup eno1 重启网卡使配置生效(若静态ip不生效,可先设置为 dhcp,再改回静态ip 再试试)
  • 跨网段是怎么连通的:
    • 如果机器a是100网段的,其能和100网段的机器连通
    • 如果机器b是2网段的,其能和2网段的机器连通
    • 而当网管设置「100网段的网关」和「2网段的网关」连通时,则「100网段的a机器」即可和「2网段的b机器」连通
  • 判断网线是否插好:下面三种方法可以搭配使用
    • 从上面的命令找到网卡名称,然后执行 ethtool- p网卡名称 如果服务器有多个网卡,对应网卡的灯会闪烁,将网线插入对应网卡即可
    • 配置好网线和IP后,执行 mii-tool 网卡名称 可以检查网卡配置是否正常
    root@ubuntu:~# mii-tool eno1
    eno1: negotiated 1000baseT-FD flow-control, link ok
    root@ubuntu:~# mii-tool eno2
    eno2: no link
    
    • 1
    • 2
    • 3
    • 4
    • 配置好网线和IP后,执行 ethtool 网卡名称 也可以检查网卡配置是否正常(显示出速度即为正常)
      在这里插入图片描述

双网卡绑定

为了保证网络传输速率和稳定性,如果现场可以给服务器插多个网线,而且服务器带的有多网口的话,可以做绑定网卡的操作。

  1. 查服务器里所有网卡的名称:ifconfig -a
  2. 配置双网卡绑定
vim/etc/network/interface

source/etc/network/interfaces.d/*
auto lo
iface lo inet loopback

auto enp2s0
iface enp2s0 inet manua
bond-master bond0

auto enp3s0
iface enp3s0 inet manua
bond-master bond0

auto bond0

iface bond0 inet static
	address 192.168.100.118
	netmask 255.255.254.0
	network 192.168.100.0
	broadcast 192.168.101.255
	gateway 192.168.101.254
	dns-namrservers 192.168.2.1
	bond-mode 6
	bond-miimon 100
	bond-slaves enp2s0 enp3s0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  1. 重启服务器检查:reboot; ifconfig; 检查服务器是否有 bond 网卡

ping

y% ping www.sina.com.cn
PING spool.grid.sinaedge.com (49.7.37.60) 56(84) bytes of data.
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=1 ttl=52 time=19.0 ms
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=2 ttl=52 time=30.7 ms
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=3 ttl=52 time=28.6 ms
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=4 ttl=52 time=27.0 ms
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=5 ttl=52 time=25.5 ms
64 bytes from 49.7.37.60 (49.7.37.60): icmp_seq=6 ttl=52 time=34.8 ms
^C
--- spool.grid.sinaedge.com ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5041ms
rtt min/avg/max/mdev = 19.021/27.613/34.831/4.856 ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

ping 基于 ICMP 协议,ICMP 是基于 IP 的控制协议,格式如下:

  • 类型:即 ICMP 的类型, 其中 ping 的请求类型为 0,应答为 8。
  • 代码:进一步划分 ICMP 的类型, 用来查找产生错误的原因。
  • 校验和:用于检查错误的数据。
  • 标识符:通过标识符来确认是谁发送的控制协议,可以是进程 ID。
  • 序列号:唯一确定的一个报文,前面 ping 名字执行后显示的 icmp_seq 就是这个值。

在这里插入图片描述

当我们发起 ping 命令时,ping 程序实际上会组装成如图的一个 IP 报文。报文的目的地址为 ping 的目标地址,源地址就是发送 ping 命令时的主机地址,同时按照 ICMP 报文格式填上数据,在可选数据上可以填上发送时的时间戳。

IP 报文通过 ARP 协议,源地址和目的地址被翻译成 MAC 地址,经过数据链路层后,报文被传输出去。当报文到达目的地址之后,目的地址所在的主机也按照 ICMP 协议进行应答。

应答数据到达源地址之后,ping 命令可以通过再次解析 ICMP 报文,对比序列号,计算时间戳等来完成每个发送 - 应答的显示,最终显示的格式就像前面的例子中展示的一样。

pinginfoview 可在 windows 可视化 ping 效果。
常见ping响应和原因

ifconfig

显示所有网络设备,即网卡列表。

vagrant@ubuntu-xenial-01:~$ ifconfig
cni0      Link encap:Ethernet  HWaddr 0a:58:0a:f4:00:01 # 是一个以太网设备,MAC 地址为 02:54:ad:ea:60:2e
          inet addr:10.244.0.1  Bcast:0.0.0.0  Mask:255.255.255.0 # 此网卡的IPv4地址、子网掩码、广播地址
          inet6 addr: fe80::401:b4ff:fe51:bcf9/64 Scope:Link # 此网卡的IPv6地址
          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1 # 广播通常是用udp实现的,网卡状态是RUNNING,MTU是最大传输单元(链路层包为1450byte),metric表示网卡优先级(例如某机器同时有无线网卡和有线网卡,数值越小优先级越高,优先用哪个网卡,1为最高优先级)
          RX packets:2133 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2216 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:139381 (139.3 KB)  TX bytes:853302 (853.3 KB)
 
 
docker0   Link encap:Ethernet  HWaddr 02:42:93:0f:f7:11
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:93ff:fe0f:f711/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:653 errors:0 dropped:0 overruns:0 frame:0
          TX packets:685 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:49542 (49.5 KB)  TX bytes:430826 (430.8 KB)
 
 
enp0s3    Link encap:Ethernet  HWaddr 02:54:ad:ea:60:2e
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::54:adff:feea:602e/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:7951 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4123 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:5081047 (5.0 MB)  TX bytes:385600 (385.6 KB)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • vim /etc/network/interfaces 编辑网卡配置文件
  • ifconfig -a 查看网卡名称

iftop

查网络 io 大户

netplan

netplan.io

Netplan 从 /etc/netplan/*.yaml读取网络配置,这些配置由管理员、安装程序、云映像实例化或其他操作系统部署编写。在早期引导期间,Netplan在 /run 中生成特定于后端的配置文件,以将设备控制权移交给特定的网络守护进程。

在这里插入图片描述

netplan apply 修改网卡配置后让配置直接生效

telnet 和 ssh

telnet和ssh都是连接远程计算机的连接协议,可以完成对完成计算机的控制,方便维护。他们都是基于TCP/IP协议下的,所以连接时都需要知道目标机的网址或者域名,他们都是与远程主机连接的通道,完成的目的是一样的,只不过手段不一样而已。

Telnet连接计算机需要如下几个过程: 客户端建立与远程主机的TCP连接;远程机通知客户机收到连接,等候输入;客户机收到通知后收集用户输入,将输入的字符串变成标准格式并传送给远程机;远程机接受输入的命令,并执行,将得到的结果输出给客户机;客户机在收到回显后显示在界面上。值得注意的是,telnet连接的时候直接建立TCP连接,所有传输的数据都是明文传输,所以是一种不安全的方式。

SSH 为Secrue Shell的缩写,SSH 为建立在应用层基础上的安全协议,是比较可靠安全的协议。版本号协商阶段,SSH目前包括 SSH1和SSH2两个版本,双方通过版本协商确定使用的版本密钥和算法协商阶段,SSH支持多种加密算法,双方根据本端和对端支持的算法,协商出最终使用的算法认证阶段,SSH客户端向服务器端发起认证请求,服务器端对客户端进行认证会话请求阶段,认证通过后,客户端向服务器端发送会话请求交互会话阶段,会话请求通过后,服务器端和客户端进行信息的交互值得注意的是,由于ssh经过加密算法加密,收报文需要解密,发报文需要加密,导致其传输速度、效率较telnet低很多,然而,它却有telnet不具有的安全性。

二、实战场景

查看某个进程的连接

  • 那个11517是进程号即 docker inspect里面的 Pid
sudo lsof -p 11517 | grep 'CLOSE_WAIT' | wc -l
  • 1

查看最大连接

ulimit -n
ulimit -a
  • 1
  • 2

/proc/114906/net 可以看到某个进程的最大连接数上限

supervisor 默认会限制其下属的服务为1024,如果希望达到机器的ulimit -n是65535,需在supervisor 配置文件添加如下:

minfds=65535                 ; min. avail startup file descriptors; default 1024
minprocs=65535               ; min. avail process descriptors;default 200
  • 1
  • 2

查看网络

nload
  • 1

根据端口找进程

lsof -i:3000
netstat -tunlpa | grep 9092
// n: 禁用域名解析, 加速命令查询的速度
// l: listen: 只列出后台监听的端口
// t: tcp
// p: 列出 pid/program name 
// a: all: 所有的连接, 和 listen 是互斥项
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

改DNS

vim /etc/resolve.conf增加一行nameserver 192.168.2.1
  • 1
nameserver 114.114.114.114
  • 1
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update && sudo apt upgrade -y
  • 1
  • 2
  • 3

改hostname

  • 例如改为masterNode
sudo vim /etc/hostname 改127.0.0.1那一行, 如127.0.0.1 masterNode
sudo hostnamectl set-hostname masterNode
  • 1
  • 2

固定 IP

参考

iptables

部分现场某些组件如果不想被其它服务器访问,可以通过iptables限制只允许集群内的服务器访问,其它的ip无法访问。可以解决漏洞扫描或者限制访问

例如现场有3台postgres集群,ip分别为192.168.2.27,192.168.2.28,192.168.2.29,需要访问数据库的ip有192.168.2.50,192.168.2.147

一、数据库服务器设置所有机器无法访问5432端口
每台数据库机器都需要设置

iptables -A INPUT -p tcp --dport 5432 -j DROP
设置完后,可以用telnet命令测试一下,从其他服务器看5432端口能否访问,正常情况下已经无法访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kShrxSE-1666859831861)(https://note.youdao.com/yws/res/123315/WEBRESOURCEca6db5ff8e9c9f202ba8b87b2ddd19f4)]

二、设置需要访问数据库的ip可以访问集群内的数据库

设置数据库集群ip可以访问

iptables -I INPUT -s 127.0.0.1 -p tcp --dport 5432 -j ACCEPT
iptables -I INPUT -s 192.168.2.27 -p tcp --dport 5432 -j ACCEPT
iptables -I INPUT -s 192.168.2.28 -p tcp --dport 5432 -j ACCEPT
iptables -I INPUT -s 192.168.2.29 -p tcp --dport 5432 -j ACCEPT
  • 1
  • 2
  • 3
  • 4

设置其他需要访问数据的ip

iptables -I INPUT -s 192.168.2.50  -p tcp --dport 5432 -j ACCEPT
iptables -I INPUT -s 192.168.2.147 -p tcp --dport 5432 -j ACCEPT
  • 1
  • 2

设置完后,可以用telnet命令测试一下,从需要访问数据库的服务器,telnet 5432端口能否访问,正常情况下可以访问

在这里插入图片描述

k8s端口

因k8s端口一般都是10.244.xxx.xxx, 可通过kubectl get po -n ullr -o wide
在这里插入图片描述

故下述命令即可使k8s均可连此自己
iptables -I INPUT -s 10.244.0.0/16 -p tcp --dport 1:65535 -j ACCEPT

三、检查业务是否受影响
检查应用服务器,需要访问数据库的组件,是否有报连接数据库的错误,如果没有,表示正常,如果有,可以检查一下是否将业务服务器ip,在上面的iptables规则里放行

查看规则

iptables -L INPUT -n --line-numbers
  • 1

在这里插入图片描述

too many open files

排查命令:

netstat -an|grep 2013
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
cat /etc/supervisor/supervisord.conf
ls /proc/142111/fd | wc -l
cat /proc/142111/limits
ulimit -a
ss命令
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

ping 案例

虽然 ping 比较简单,但有时候你会发现,ping 工具本身也可能出现异常,比如运行缓慢,但实际网络延迟却并不大的情况。

接下来,我们打开一个终端,SSH 登录到案例机器中,执行下面的命令,来测试案例机器与极客邦科技官网的连通性和延迟。如果一切正常,你会看到下面这个输出:

# ping 3 次(默认每次发送间隔 1 秒)
# 假设 DNS 服务器还是上一期配置的 114.114.114.114
$ ping -c3 geektime.org
PING geektime.org (35.190.27.188) 56(84) bytes of data.
64 bytes from 35.190.27.188 (35.190.27.188): icmp_seq=1 ttl=43 time=36.8 ms
64 bytes from 35.190.27.188 (35.190.27.188): icmp_seq=2 ttl=43 time=31.1 ms
64 bytes from 35.190.27.188 (35.190.27.188): icmp_seq=3 ttl=43 time=31.2 ms
 
--- geektime.org ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 11049ms
rtt min/avg/max/mdev = 31.146/33.074/36.809/2.649 ms


假如你运行时发现 ping 很快就结束了,那就执行下面的命令,再重试一下。至于这条命令的含义,稍后我们再做解释。
```bash
# 禁止接收从 DNS 服务器发送过来并包含 googleusercontent 的包
$ iptables -I INPUT -p udp --sport 53 -m string --string googleusercontent --algo bm -j DROP


根据 ping 的输出,你可以发现,geektime.org 解析后的 IP 地址是 35.190.27.188,而后三次 ping 请求都得到了响应,延迟(RTT)都是 30ms 多一点。
但汇总的地方,就有点儿意思了。3 次发送,收到 3 次响应,没有丢包,但三次发送和接受的总时间居然超过了 11s(11049ms),这就有些不可思议了吧。
回想起上一节的 DNS 解析问题,你可能会怀疑,这可能是 DNS 解析缓慢的问题。但到底是不是呢?再回去看 ping 的输出,三次 ping 请求中,用的都是 IP 地址,说明 ping 只需要在最开始运行时,解析一次得到 IP,后面就可以只用 IP 了。


我们再用 nslookup 试试。在终端中执行下面的 nslookup 命令,注意,这次我们同样加了 time 命令,输出 nslookup 的执行时间:
$ time nslookup geektime.org
Server:		114.114.114.114
Address:	114.114.114.114#53
 
Non-authoritative answer:
Name:	geektime.org
Address: 35.190.27.188

real	0m0.044s
user	0m0.006s
sys		0m0.003s


可以看到,域名解析还是很快的,只需要 44ms,显然比 11s 短了很多。
到这里,再往后该怎么分析呢?其实,这时候就可以用 tcpdump 抓包,查看 ping 在收发哪些网络包。
我们再打开另一个终端(终端二),SSH 登录案例机器后,执行下面的命令:
# -nn ,表示不解析抓包中的域名(即不反向解析)、协议以及端口号。
# udp port 53 ,表示只显示 UDP 协议的端口号(包括源端口和目的端口)为 53 的包。
# host 35.190.27.188 ,表示只显示 IP 地址(包括源地址和目的地址)为 35.190.27.188 的包。
# 这两个过滤条件中间的“ or ”,表示或的关系,也就是说,只要满足上面两个条件中的任一个,就可以展示出来。
$ tcpdump -nn udp port 53 or host 35.190.27.188
当然,你可以直接用 tcpdump 不加任何参数来抓包,但那样的话,就可能抓取到很多不相干的包。由于我们已经执行过 ping 命令,知道了 geekbang.org 的 IP 地址是 35.190.27.188,也知道 ping 命令会执行 DNS 查询。所以,上面这条命令,就是基于这个规则进行过滤。


接下来,回到终端一,执行相同的 ping 命令:
$ ping -c3 geektime.org
...
--- geektime.org ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 11095ms
rtt min/avg/max/mdev = 81.473/81.572/81.757/0.130 ms


命令结束后,再回到终端二中,查看 tcpdump 的输出:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
14:02:31.100564 IP 172.16.3.4.56669 > 114.114.114.114.53: 36909+ A? geektime.org. (30)
14:02:31.507699 IP 114.114.114.114.53 > 172.16.3.4.56669: 36909 1/0/0 A 35.190.27.188 (46)
14:02:31.508164 IP 172.16.3.4 > 35.190.27.188: ICMP echo request, id 4356, seq 1, length 64
14:02:31.539667 IP 35.190.27.188 > 172.16.3.4: ICMP echo reply, id 4356, seq 1, length 64
14:02:31.539995 IP 172.16.3.4.60254 > 114.114.114.114.53: 49932+ PTR? 188.27.190.35.in-addr.arpa. (44)
14:02:36.545104 IP 172.16.3.4.60254 > 114.114.114.114.53: 49932+ PTR? 188.27.190.35.in-addr.arpa. (44)
14:02:41.551284 IP 172.16.3.4 > 35.190.27.188: ICMP echo request, id 4356, seq 2, length 64
14:02:41.582363 IP 35.190.27.188 > 172.16.3.4: ICMP echo reply, id 4356, seq 2, length 64
14:02:42.552506 IP 172.16.3.4 > 35.190.27.188: ICMP echo request, id 4356, seq 3, length 64
14:02:42.583646 IP 35.190.27.188 > 172.16.3.4: ICMP echo reply, id 4356, seq 3, length 64
这次输出中,前两行,表示 tcpdump 的选项以及接口的基本信息;从第三行开始,就是抓取到的网络包的输出。这些输出的格式,都是 时间戳 协议 源地址.源端口 > 目的地址.目的端口 网络包详细信息(这是最基本的格式,可以通过选项增加其他字段)。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

前面的字段,都比较好理解。但网络包的详细信息,本身根据协议的不同而不同。所以,要理解这些网络包的详细含义,就要对常用网络协议的基本格式以及交互原理,有基本的了解。

当然,实际上,这些内容都会记录在 IETF( 互联网工程任务组)发布的 RFC(请求意见稿)中。

比如,第一条就表示,从本地 IP 发送到 114.114.114.114 的 A 记录查询请求,它的报文格式记录在 RFC1035 中,你可以点击这里查看。在这个 tcpdump 的输出中,

  • 36909+ 表示查询标识值,它也会出现在响应中,加号表示启用递归查询。
  • A? 表示查询 A 记录。
  • geektime.org. 表示待查询的域名。
  • 30 表示报文长度。

接下来的一条,则是从 114.114.114.114 发送回来的 DNS 响应——域名 geektime.org. 的 A 记录值为 35.190.27.188。

第三条和第四条,是 ICMP echo request 和 ICMP echo reply,响应包的时间戳 14:02:31.539667,减去请求包的时间戳 14:02:31.508164 ,就可以得到,这次 ICMP 所用时间为 30ms。这看起来并没有问题。

但随后的两条反向地址解析 PTR 请求,就比较可疑了。因为我们只看到了请求包,却没有应答包。仔细观察它们的时间,你会发现,这两条记录都是发出后 5s 才出现下一个网络包,两条 PTR 记录就消耗了 10s。

再往下看,最后的四个包,则是两次正常的 ICMP 请求和响应,根据时间戳计算其延迟,也是 30ms。

到这里,其实我们也就找到了 ping 缓慢的根源,正是两次 PTR 请求没有得到响应而超时导致的。PTR 反向地址解析的目的,是从 IP 地址反查出域名,但事实上,并非所有 IP 地址都会定义 PTR 记录,所以 PTR 查询很可能会失败。

所以,在你使用 ping 时,如果发现结果中的延迟并不大,而 ping 命令本身却很慢,不要慌,有可能是背后的 PTR 在搞鬼。

知道问题后,解决起来就比较简单了,只要禁止 PTR 就可以。还是老路子,执行 man ping 命令,查询使用手册,就可以找出相应的方法,即加上 -n 选项禁止名称解析。比如,我们可以在终端中执行如下命令:

$ ping -n -c3 geektime.org
PING geektime.org (35.190.27.188) 56(84) bytes of data.
64 bytes from 35.190.27.188: icmp_seq=1 ttl=43 time=33.5 ms
64 bytes from 35.190.27.188: icmp_seq=2 ttl=43 time=39.0 ms
64 bytes from 35.190.27.188: icmp_seq=3 ttl=43 time=32.8 ms
 
--- geektime.org ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 32.879/35.160/39.030/2.755 ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

你可以发现,现在只需要 2s 就可以结束,比刚才的 11s 可是快多了。

到这里, 我就带你一起使用 tcpdump ,解决了一个最常见的 ping 工作缓慢的问题。

案例最后,如果你在开始时,执行了 iptables 命令,那也不要忘了删掉它:

$ iptables -D INPUT -p udp --sport 53 -m string --string googleusercontent --algo bm -j DROP
  • 1

不过,删除后你肯定还有疑问,明明我们的案例跟 Google 没啥关系,为什么要根据 googleusercontent ,这个毫不相关的字符串来过滤包呢?实际上,如果换一个 DNS 服务器,就可以用 PTR 反查到 35.190.27.188 所对应的域名:

 $ nslookup -type=PTR 35.190.27.188 8.8.8.8
Server:	8.8.8.8
Address:	8.8.8.8#53
Non-authoritative answer:
188.27.190.35.in-addr.arpa	name = 188.27.190.35.bc.googleusercontent.com.
Authoritative answers can be found from:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

你看,虽然查到了 PTR 记录,但结果并非 geekbang.org,而是 188.27.190.35.bc.googleusercontent.com。其实,这也是为什么,案例开始时将包含 googleusercontent 的丢弃后,ping 就慢了。因为 iptables ,实际上是把 PTR 响应给丢了,所以会导致 PTR 请求超时。

tcpdump 可以说是网络性能分析最有效的利器。接下来,我再带你一起看看 tcpdump 的更多使用方法。

三、抓包

BPF(Berkeley Packet Filter)利用基于寄存器的虚拟机方式,可以高效稳定地过滤报文。

libpcap 提供 API 给用户空间程序(如 tcpdump、Wireshark等)。

tcpdump、Wireshark等调用 libpcap 的 API 来抓包。

各工具的拓扑结构如下:
在这里插入图片描述

抓包文件格式

  • pcap:是 libpcap 程序的格式,因此基于 libpcap 的旧版本的 tcpdump、Wireshark 的格式也都是 pcap。其中除了报文数据,还包括抓包文件的元信息、版本号、抓包时间、各报文被抓取的最大长度。
  • cap:是由 tcpdump 之外的其他抓包程序使用的格式,例如 Wireshark 可读取此格式。
  • pcapng:因为现代机器有多个网口,此格式会包含哪个网口的信息,新版本的 tcpdump、Wireshark 均用此格式。如下图右侧即为指明为 en0 网口的包:
    在这里插入图片描述

tcpdump 用法

TCPDUMP(8)                                       System Manager's Manual                                       TCPDUMP(8)

NAME
       tcpdump - dump traffic on a network

SYNOPSIS
       tcpdump [ -AbdDefhHIJKlLnNOpqStuUvxX# ] [ -B buffer_size ]
               [ -c count ]
               [ -C file_size ] [ -G rotate_seconds ] [ -F file ]
               [ -i interface ] [ -j tstamp_type ] [ -m module ] [ -M secret ]
               [ --number ] [ -Q in|out|inout ]
               [ -r file ] [ -V file ] [ -s snaplen ] [ -T type ] [ -w file ]
               [ -W filecount ]
               [ -E spi@ipaddr algo:secret,...  ]
               [ -y datalinktype ] [ -z postrotate-command ] [ -Z user ]
               [ --time-stamp-precision=tstamp_precision ]
               [ --immediate-mode ] [ --version ]
               [ expression ]

DESCRIPTION
       Tcpdump  prints out a description of the contents of packets on a network interface that match the boolean expres‐
       sion; the description is preceded by a time stamp, printed, by default, as hours, minutes, seconds, and  fractions
       of  a  second  since  midnight.  It can also be run with the -w flag, which causes it to save the packet data to a
       file for later analysis, and/or with the -r flag, which causes it to read from a saved packet file rather than  to
       read  packets  from  a  network interface.  It can also be run with the -V flag, which causes it to read a list of
       saved packet files. In all cases, only packets that match expression will be processed by tcpdump.

       Tcpdump will, if not run with the -c flag, continue capturing packets until it is interrupted by a  SIGINT  signal
       (generated,  for  example, by typing your interrupt character, typically control-C) or a SIGTERM signal (typically
       generated with the kill(1) command); if run with the -c flag, it will capture packets until it is interrupted by a
       SIGINT or SIGTERM signal or the specified number of packets have been processed.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

如何抓取报文

tcpdump host 192.168.2.99 # 抓去往或来自某IP的报文
tcpdump port 22 # 抓某端口的流量
  • 1
  • 2

其参数如下:
-w 文件名,可以把报文保存到文件;
-c 数量,可以抓取固定数量的报文,这在流冖较高时,可以避免一不小心抓取过多报文;
-s 长度,可以只抓取每个报文的一定长度。例如 tcpdump -s 74 -w file.pcap,默认是抓 1500 字节,而我们可以手动指定每个报文只抓前74个字节,如下图所示:
在这里插入图片描述
-n,不做地址转换(比如P 地址转换为主机名,port 80 转换为 http)
-v/-vv/-vvv,可以打印更加详细的报文信息;
-e,可以打印二层信息,特别是 MAC 地址;
-p,关闭混杂模式。所谓混杂模式,也就是嗅探(Sniffering),就是把目的地址不是本机地址的网络报文也抓取下来。
-X 抓包时显示报文内容
在这里插入图片描述
-r 读取抓包文件, 如 tcpdump -r file. pcap ' tep [tepflags] & (tcp-rst) != 0"
-r -w 过滤后转存,如 tcpdump -r file. pcap ' tep [tcpflags] & (tcp-rst) 1= 0 -W rst.pcap

如何过滤报文

最近我们有个实际的需求,要统计我们某个 HTTPS VIP 的访问流量里,TLS 版本(现在主要是TLS1.0、11、1.2、13) 的分布。为了控制抓包文件的大小,我们义不想什么 TLS报文都抓,只想抓取 TLS 版本信息。这该如何做到呢?要知道,针对这个需求,tcpdump 本身没有一个现成的过滤器。

其实,BPF 本身是基于偏移量来做报文解析的,所以我们也可以在 tcpdump 中使用这种偏移量技术,实现我们的需求。下面这个命令,就可以抓取到 TLS 握手阶段的 Client Hello 报文:

tcpdump -w file.pcap 'dst port 443 && tcp[20]==22 && tcp [25]==1
  • 1

我给你解榉一下上面的三个过滤条件。

  • dst port 443:这个最简单,就是抓取从客户端发过来的访问 HTTPS 的报文。
  • topl20]-=22:这是提取了 TCP 的第21 个字节(因为初始序号是从 。 开始的),由于 TCP 头部占20字节,TLS 又是 TCP 的载荷,那么 TLS 的第1个字节就是 TCP 的第21个字节,也就是 TCP1201,这个位置的值如果是 22(十进制〉,那么就表明这个是 TLS握手报文。
  • top[25]==1:同理,这是 TCP 头部的第26 个字节,如果它等于1,那么就表明这个是 Client Hello 类型的 TLS 握手报文。

下面是抓包文件里的样子:

在这里插入图片描述

这里你可能会有疑问:上面的图里,TCP[20]的位置的值不是16 吗?其实,这个是十六进制的16,换算成十进制,就是22了。

我又画了一张示意图来表示报文偏移量及其含义,希望能帮你理解其中的奥妙,具体 TCP 各位的含义如下:

在这里插入图片描述

tcpdump 预定义了一些相对方便的过滤器

  • 例如若向过滤出 TCP RST 报文,可用如下写法:
用过滤器的写法为       tcpdump -w file.pcap 'tcp[tcpflags]&(tcp-rst) != 0'
或者用偏移量的写法则为 tcpdump -w file.pcap 'tcp[13]&4 != 0'
  • 1
  • 2
  • 例如抓取 TCP SYN 包,可用如下写法:
tcpdump 'tcp[13] = 2' -w file.pcap
tcpdump -s 34 -w file.pcap
tcpdump host xxxx and 'tcp[tcpflags] == tcp-syn'
  • 1
  • 2
  • 3

Wireshark 用法

确认是在哪端抓的包

为了确认是在客户端还是服务端做的抓包,可用 IP 的 TTL 属性判断。

因为客户端(即发起端)的 TTL 都是64、128、256中的某一个,而服务端(即接收端)报文的 TTL 都会因为中间经过若干网络跳数而减少。

因此若报文的 TTL 为 64、128、256之一,则一定是客户端的报文,如下图所示:

在这里插入图片描述

定位应用层的请求和响应

只要在 Wireshark 中选中请求报文,则会自动匹配对应的响应报文。如下图是一个 HTTP Server 端的抓包,向右的箭头表示数据进来,向左的箭头表示数据出去。

在这里插入图片描述

只截到一部分报文

在这里插入图片描述
若出现上图错误,不必关注,可能因为不是用 ctrl + c 方式结束的(如 kill -9),导致 tcpdump 被强行终止,使得一部分被抓取的报文还在内存中,没来得及由 tcpdump 正常写入到 pcap 文件中。

可通过用 ctrl + c、kill (不带 -9参数)来停止 tcpdump。

乱序有问题吗

乱序是通常存在的,但若超过 10% 都乱序,则可能传输质量严重有问题,可能导致传输失败,或者应用层的各种卡顿、报错。

四、TCP 握手

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

其中 SYN 会在两端各发一次,表示 “我准备好了,可以开始连接了”。其中 ACK 也是两端各发一次,表示 “我知道你准备好了,我们开始通信吧!”。

其实很像现在腾讯会议开会或打电话时,若总共有两个人。

  • a 先说:能听到吗?
  • b 听到后再说:我能听到你,你能听到我吗?
  • a 听到后说:我能听到你。开始正题吧!

这里其实共有4个报文,但是是3次发送,因为 server 的 SYN + ACK 是合并发送的(术语是 Piggybacking,即搭顺风车之意),这样可以节省一次发送次数。

如果 server 不想接受这次握手,可以有如下两种选择:

  • 已读未回:术语叫做静默丢包。即不搭理这次连接,当做什么都没发生。这种情况因为 server 做了静默丢包,而 client 就不知道到底是如下哪种行为了。
    • 情况1:client 发送的 SYN 在网络中丢失了,从而导致 server 压根没收到
    • 情况2:client 发送的 SYN 已到达 server,但 server 就是选择已读不回。
    • 情况3:client 发送的 SYN 已到达 server,server 也回复了 SYN + ACK,但 server 回复的包在网络中丢失了从而导致 client 未收到。
    • 通常 client 为了应对上述这三种情况,会重试。
      在这里插入图片描述
  • 已读直接拒绝:这也是非常猛的行为了。

借助 iptables、tcpdump 的重试观察实验

server 设置为 DROP 策略

export PS1='[\u@server]\$'

# server 端,让iptables 静默丢弃发往自己 9999 端口的数据包
root@server:~# iptables -I INPUT -p tcp --dport 9999 -j DROP

# server 端,查看设置的 iptables
root@server:~# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       tcp  --  anywhere             anywhere             tcp dpt:9999

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
export PS1='[\u@client]\$'

# 在 client 端,启动 tcpdump
[root@client]#tcpdump -i any -w telnet-9999.pcap port 9999
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

# 此时在 client,向 server 发起 telnet,会被挂起,直到2min左右才失败退出
[root@client]#telnet 192.168.2.server 9999
Trying 192.168.2.server...

# 我们手动再 client 端,ctrl + c 终止 client, 即可得到 tcpdump 的文件如下:
-rw-r--r--  1 root root  668 Nov 16 12:14 telnet-9999.pcap
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

然后我们用 WireShark 打开这个名为 telnet-9999.pcap 的抓包文件,如下图:
在这里插入图片描述
可看到,因为 client 的握手一直未成功,client 总共发了6个包,后五个均为重试。

  • 每次重试时间间隔都是上次重试的二倍。这里的翻倍时间是 指数退避 策略(Exponential backoff),且每次不是精确的整数秒,这样来让加大重试成功的几率。
  • 到第6次重试失败时,client 就彻底放弃了。这个参数是由 Linux 内核的 net.ipv4.tcp_syn_retries = 6 参数控制的。
[root@client]#sysctl net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_retries = 6

[root@client]#man tcp | grep -A 3 tcp_syn_retries
       tcp_syn_retries (integer; default: 6; since Linux 2.2)
              The  maximum number of times initial SYNs for an active TCP connection attempt will be retransmitted.  This value should not be higher than 255.  The default value is 6, which corresponds to retrying for up to approxi‐
              mately 127 seconds.  Before Linux 3.7, the default value was 5, which (in conjunction with calculation based on other kernel parameters) corresponded to approximately 180 seconds.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

server 设置为 REJECT 策略

export PS1='[\u@server]\$'

# 设置 server 为 REJECT 策略
[root@server]#iptables -I INPUT -p tcp --dport 9999 -j REJECT
[root@server]#iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
REJECT     tcp  --  anywhere             anywhere             tcp dpt:9999 reject-with icmp-port-unreachable
DROP       tcp  --  anywhere             anywhere             tcp dpt:9999

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
client 用 tcpdump 指定监听的 port
# 然后在 client 端,启动 tcpdump 来抓包
[root@client]#tcpdump -i any -w telnet-9999.pcap host 192.168.2.server port 9999
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

# 然后在 client 端,用 telnet 来向 server 握手
[root@client]#telnet 192.168.2.server 9999
Trying 192.168.2.server...
telnet: Unable to connect to remote host: Connection refused # 我们会发现果然立刻就被拒绝了

# 在 client 端,通过 ctrl + c 手动中指 tcpdump 后,日志打印果然抓到了一个包
^C
1 packet captured
1 packet received by filter
0 packets dropped by kernel
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

然后我们用 Wireshark 分析,效果如下,因为用 tcpdump 只指定监听了 9999 端口,而 server 端返回的 拒绝信号是用 ICMP 报文,所以下图只能看到 client 发送的消息,而看不到 server 回复的拒绝消息:
在这里插入图片描述

client 用 tcpdump 不指定监听的 port
# 然后在 client 端,启动 tcpdump 来抓包
[root@client]#tcpdump -i any -w telnet-anyport.pcap host 192.168.2.server
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

# 然后在 client 端,用 telnet 来向 server 握手
[root@client]#telnet 192.168.2.server 9999
Trying 192.168.2.server...
telnet: Unable to connect to remote host: Connection refused # 我们会发现果然立刻就被拒绝了

# 在 client 端,通过 ctrl + c 手动中指 tcpdump 后,日志打印果然抓到了一个包
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

然后我们用 Wireshark 分析,效果如下,可以看到 client 发送的消息,和 server 回复的拒绝消息:

在这里插入图片描述
在这里插入图片描述

其实 server 选择用 ICMP 回复拒绝报文,是因为默认的 iptables DROP 策略是 ICMP 报文,即下文中的reject-with icmp-port-unreachable

root@server]#iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
REJECT     tcp  --  anywhere             anywhere             tcp dpt:9999 reject-with icmp-port-unreachable
DROP       tcp  --  anywhere             anywhere             tcp dpt:9999

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

当然我们也可以设置为 --reject with tcp-reset。但无论哪种拒绝报文(TCP RST 或 ICMP port unreachable),client 端的 connect() 都会返回 ECONNREFUSED,即最终让 telnet 报错 “connection refused”。

实验做完记得用 iptables -D INPUT 1 来删除刚才设置的规则。

TCP 状态转移

出自《UNIX 网络编程:套接字联网 API》一书:
在这里插入图片描述

两端的交互和状态转移如下:
在这里插入图片描述

其中前三次握手的过程如下:

在这里插入图片描述
TCP 的窗口大小,只有第一次握手时的报文会包含,后续报文均不再包含此字段(避免话痨,让重复信息浪费带宽)。因为 TCP 协议是在1981年确立的,当时网络带宽太小了,所以预留的字段不够。因此现代的 TCP 窗口大小,都是用 TCP 扩展部分 TCP Options 里的 WindowScale 字段来标识将 65535 右移的位数。

例如下图的 TCP Options.Kind 为 3,表示意为 Window Scale。而 Shift Offset = 6,则 65536 右移 6 位,即把 65535 乘以 math.pow(2, 6),如下图:

在这里插入图片描述

常见误区的更正

UDP 其实并没有握手

  • 因为 UDP 不是面向连接的,所以只要没有明确拒绝,nc 命令都视为连通。
  • 因为 TCP 是面向连接的,所以只要没有明确接受,nc 命令都视为不连通。
    所以,当我们用 nc 命令探测 UDP 端口时,不通的结果是可信的,而能通的结果却并不可信而只能作为参考。

例如下文实验

# server 端并没有程序在监听 22 端口

# 但在 client 端,用 nc 命令,却得到 established 的响应,而这并不说明 server 的 22 端口被监听
[root@client]#nc -v -w 2 192.168.2.server 22
Connection to 192.168.2.server 22 port [tcp/ssh] succeeded!
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一台机器其实不止 65535 个连接

因为连接是四元组,虽然端口范围是 0 ~ 65535,但如果有 n 个 clientIP 的机器都来连接 serverIP,每个 clientIP 都有最多 65535 个连接,则共有 n * 65535 个连接。

所以一台 Web 服务器,支持 100w 个连接是完全可能的。

两端其实可以同时发起握手

如果两端同时向对端发送 SYN,虽然这种情况很罕见,但其实两端也是可以建立 TCP 连接的。如下图所示:

在这里插入图片描述

五、TCP 挥手

我们经常看到应用层 connection reset by peer 的报错,其实对应到传输层/网络层是指:对端(peer)回复了 TCP RST,从而终止了一次 TCP 连接。

为了解决问题,我们经常要将应用层的信息,翻译成传输层/网络层的信息。

  • 应用层信息包括如下:
    • 应用层日志:成功日志、报错日志
    • 应用层性能数据:RPS(每秒的请求数)、transaction time(处理时间)
    • 应用层载荷:HTTP 请求和响应的 header、body
  • 传输层/网络层信息包括如下:
    • 传输层:TCP 序列号、确号、MSS、接收窗口、拥塞窗口、时延、重复确认、选择性确认、重传、丢包
    • 网络层:IP 的 TTL、MTU、跳数、路由表

connection reset by peer 原因

我们以一个 Nginx server 有很多 connection reset by peer 的报错作为分析案例,报错日志如下:

在这里插入图片描述

过滤报文

既然有了应用层日志,我们就可以用 tcpdump 抓包,用 Wireshark 打开抓包文件,并用如下过滤条件:

ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1
  • 1

我们即可在 Wireshark 中看到很多 RST 报文,而且右下角表示过滤后有 9122 这么多条的结果,共占了 4% 之多:

在这里插入图片描述

然后,选中某行报文,右键单击,选中 Follow => TCP Stream,找到它所属的整个 TCP 报文如下。可以看到这是三次握手的第三次报文,没有得到期望的 ACK,而是得到了 RST + ACK,从而导致握手失败了。但这种连三次握手都未成功而导致连接建立失败,并不会在 Nginx 日志中打印:

在这里插入图片描述

因此我们还需继续打磨 Wireshark 的过滤器,通过如下条件过滤掉握手阶段的 RST:

ip.addr eq 10.255.242.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)
  • 1

这样过滤出的报文如下:

在这里插入图片描述

报文还是太多,我们可以再加上应用层信息的过滤:

ip.addr eq 10.255.242.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1) and frame.time >= "dec 01, 2015 15:49:48" and frame.time <= "dec 01, 2015 15:49:49"
  • 1

终于成功地锁定了只有 3个 RST 的报文,如下:

在这里插入图片描述

接下来,对比这 3个 RST 所在的 TCP 流里的应用层数据(即 HTTP 请求和响应),和应用层 Nginx 日志中的请求和响应做对比,即可得到是哪个 RST 引起的 Nginx 报错了。

分析报文

11393 号报文详情如下:

在这里插入图片描述

11448 号报文详情如下,因为其中可以也看到 11450号报文,所以说明 11448 和 11450 是在同一个流里的:

在这里插入图片描述

因此,3 个 RST 分属于 2 个 HTTP 事务。而我们 Nginx 日志在 “dec 01, 2015 15:49:48” 时的日志的 URL 是http://xxx/yyy/weixin/zzz 信息的,如下图(实际是一行 Nginx 日志,只是在本博文中分为两行来展示):
在这里插入图片描述
在这里插入图片描述

所以,我们匹配到的是 11448 和 11450 这两个报文,其实过程如下如所示:

在这里插入图片描述
其实 server 收到 client 发送的 POST 报文,并且成功回复 HTTP 200 的响应,而且 client 也收到了 HTTP 200 的响应。

但是 client 端随后错误地发起了 RST + ACK 报文,导致 server 端调用的 recv() 方法收到 ECONNRESET 的报错,从而让 server
打印了 connection reset by peer 的日志。(

因为结论是 client 端使用方式有问题,即 client 直接用 RST 来断开连接的方式并不妥当,需要走查代码。

  • 例如可能是由于 client 在 Receive Buffer 中还有未读数据的情况下调用 close() 而引起的
  • 可能由于网络不稳定引起的
  • 可能由于防火墙发出的 RST 引起的

四次挥手确实必须两个 FIN

常规的四次挥手过程如下,其可有 client 端发起,也可有 server 端发起:

在这里插入图片描述

例如下文的抓包文件,看起来只有一个 FIN 并不符合理论,我们就针对这个报文做一个分析:

在这里插入图片描述

我们查看倒数第三行的 POST 报文详情如下,其中就有第一个 FIN,其实是被合并(Piggybacking)到 POST 报文里了,只是 Wireshark 并没有很好的可视化出来而已。:

在这里插入图片描述

所以其实真实情况如下图右半部分所示:

在这里插入图片描述
所以,如果看到 Wireshark 的 FIN,并不能确认其就是四次挥手的第一个 FIN,还需要看其附近的报文详情里是否有 FIN 标志位,才能确认挥手发生的起点。

挥手时候的四个报文是

  1. 发起端:FIN
  2. 对端:ACK
  3. 对端:FIN
  4. 发起端:ACK
    其中,2和3经常会在一起发送。除此以外,1和经常和发起端的其他报文一起发送

挥手其实可以同时发起

两端确实可以同时主动发起 FIN,此时两端同时进入 FIN_WAIT1 状态,都因为收到对方的 FIN 而进入 CLOSING 状态,都因为收到对方的
在这里插入图片描述

挥手一方发送 FIN 时,其实只有一方停止发送数据,另一方会继续发送数据

  • 一端(A)发送 FIN,表示“我要关闭,不再发送新的数据了,但我可以接收新的数据”。
  • 另一端(B)可以回复 ACK,表示“我知道你那头不会再发送了,我这头未必哦”.
  • B 可以继续发送新的数据给 A,A 也会回复 ACK 表示确认收到新数据。
  • 在发送完这些新数据后,B 才启动了自己的关闭过程,也就是发送 FIN 给 A,表示“我的事情终于忙好了,我也要关闭,不会再发送新数据了*。
  • 这时候才是真正的两端都关闭了连接。

还是搬运了 Stevens 的图过来供你參考,也再次致敬 Stexens 大师!

在这里插入图片描述

六、防火墙

抓包后,可以带你左下角 或 Analyze => Analyze Information 来分析报文,如下图:

在这里插入图片描述
在这里插入图片描述

分析后的报文如下:

  • 黄色:警告(可能有问题,值得关注)
  • 浅蓝色:允许范围内的小问题(如TCP本身容许一定程度的重传)
  • 蓝色:正常的TCP、UDP行为

在这里插入图片描述

关注重点报文

选中某行报文,邮件 Follow => TCP Stream 即可看到完整的 TCP 流,如下图:

在这里插入图片描述
在这里插入图片描述

然后,我们即可看到过滤后的,这个 TCP 流的全部报文了,如下图。其中左上角的 tcp.stream eq 8 是 Wireshark 自动生成的过滤条件:

在这里插入图片描述

其中 Wireshark 自动帮我们标注的红色报文是需要我们重点关注的:

  • 189:服务端(HTTPS)回复给客户端的报文,TCP previous segment not captured 意思是,它之前的报文没有在应该出现的位置上被抓到(并不排除这些报文在之后被抓到) 190:客户端回复给服务端的重复确认报文(DupAck),可能(Dup/Ack 报文数量多的话)会引起重传。
  • 191:服务端(HTTPS)给客户端的报文,是 TCP Out-of-Order,即乱序报文。
  • 193:服务端(HTTPS)给客户端的 TCP Retransmission,即重传报文。
  • 195:也是服务端 (HTTPS)给客户端的重传报文。

以上都是根据 Wireshark 给我们提示的信息所做的一些解读,主要是针对 TCP行为方面的, 这也足从 Wireshark 中读取出来的重要信息之一。另外一个重要的信息源是耗时(也就是时间列展示的时间间隔)。显然,在192 和193 号报义之间,有1.020215 秒的时间间隔。

要知道,对于内网通信来说,时间是以毫秒计算的。一般内网的微服务的处理时间,等于网络往返时间 + 应用处理时间。比如,同机房环境内,往返时间(Round Trip Time)一般在 1ms 以内。比如一个应用本身的处理时间是10ms,内网往返时间是 1ms,那么整体耗时就是 11ms。

然而,这里单单一个 193 号报文就引发了1秒的耗时,确实出乎意料。因此我们可以基本判定:这个超长的耗时,很可能就是导致问题的直接原因。

对比两端报文

那为什么有这1s的耗时呢,可能就是因为 TCP 的超时重传(Retransmisstion Timeout)机制导致的,所以需要对比 client 端 和 server 端两端的报文。

可以用 TCP 的序列号,来定位到两端报文的位置。TCP 序列号是4 Byte,可以表示 math.pow(2, 32) 即 4GB的报文,很少有报文会超过 4GB 的,因此我们可用此精确定位此 TCP 流在两端抓包文件中的位置。

首先,记录 client 端抓包文件中那条 TCP 流的某个报文的 TCP 序列号,例如选择 SYN 包的序列号为 4022234701(注意要选择 raw 序列号,而不是 Wireshark 的握手之后的从1开始的相对序列号,需要按下图在设置中取消 Relative sequence numbers 的勾选):

在这里插入图片描述
在这里插入图片描述

得到 client 端的 raw 序列号如下:

在这里插入图片描述

然后,在 server 端通过 tcp.sq_raw eq 4022234701 找到同样的 SYN 包,如下:

在这里插入图片描述

然后,在 server 端,通过 Follow => TCP Stream 跟踪这个 SYN 包所属的整个 TCP 流,两端报文对比(左侧为 server,右侧为 client)如下,其中前四个报文顺序正常,但随后 server 一口气发送的4个包(这里称他们为1、2、3、4),到了 client 却变为了 (4、1、2、3)的顺序。也就是 Wireshark 提升我们的 Out Of Order(包1、2、3)和 TCP previous segment not captured(包4):

在这里插入图片描述

下面,我们再从 server 端的角度,看一下报文顺序、重传、1s 耗时,这3者的关系:

在这里插入图片描述

前面刚说到“服务端发送 4 个报文后,客户端收到的是 4、1、2、3”。因为后面3 个报文的顺序还是正确的,真正乱序的其实只是 4,所以就导致了这样一个状况:乱序是乱序的,但是 “不够乱”,也就是不能满足快速重传的条件“3 个重复确认”。

这样的话,服务端就不得不用另外一种方式做重传,即超时重传。当然,这里的1 秒超时是硬件 LB 的设置值,而 Linux 的默认设置是200毫秒。

不过,撇开这些细节不谈,我们现在知道了一个重要的事实:客户端和服务端之间,有报文乱序的情况。

我们查看了其他 T心P 流,也有很多类似的乱序报文,而这种程度的乱序发生在内网是不应该的,因为内网比公网要稳定很多。以我个人的经验,内网环境常见的丢包率在万分之一上下, 乱序的几率我没有严格考证过,因为跟各个环境的具体拓扑和配置的关系太大了。但从经验上看,乱序几率大概在百分之一以下到千分之一左右都属正常。

我们把这两个抓包文件以及分析过程和推论,发给了网络安全部门。他们对于实际的抓包信息也很重视,经过排查,发回了一个我仙“期待已久”但一直无法证实的推测:问题出现在防火墙上!

具体来说,是这样两个事实:

  1. 在客户端和服务端之间,各有一道防火墙,两者之间设立有隧道;
  2. 因为软件 Bug 的问题,这个隧道在大包的封包拆包的过程中,很容易发生乱序。

就像下图这样:

在这里插入图片描述

  • 为什么隧道会引发乱序?
    首先,隧道本身并不直接引起乱序。隧道是在原有的网络封装上再加上一层额外的封装,比如 PIP 隧道,就是在P 头部外面再包上一层P头部,于是形成了在原有P 层面里的又一个 IP 层,即“隧道”(各种隧道技术也是 SDN 技术的核心基础)。由于这个封装和拆封都会消耗
    统资源,加上代码方面处理不好,那么出 Bug 的概率就大大增加了。这就是在这个案例里, 隧道会引发乱序的原因。

  • 为什么 HTTP 事务没有被影响,只有 HTTPS 被影响?
    在这个案例里,HTTP 确实一直没有被影响到。因为从抓包来看,这个场景的 HTTP 的 TCP 载荷,其实远没有达到一个 MSS 的大小。我们来看一下当时的 HTTP 抓包:

在这里插入图片描述

TCP 载荷只有两三百字节,远小于 MSS 的 1460 字节。这个跟隧道的关系是很大的,因为隧道会增加报文的大小。

比如通常 MTU 为1500 字节的IP 报义,做了IPIP 隧道封装后,就会达到1520 字节,所以一般有隧道的场景下,主机的MTU 都需要改小以适配隧道需求。如果网络没有启用 Jumbo Frame,那这个 1520 字节的报文,就会被路由器/防火墙拆分为2个报文。而到了接收端,又得把这两个报文合并起来。这一拆一合,出问题的概率就大大增加了。

补充:在 Linux 中,设置了 ipip 隧道后,这个隧道接口的MTU 会自动降低 20字节,也就是从默认 1500 降低到1480字节。
这个案例里是特殊的防火墙,它的 MTU 的逻辑跟 Linux 有所不同。

事实上,在大包情况下,这个隧道号发的是两种不同的开销:

  • IPIP 本身的隧道头的封包和拆包;
  • IP 层因为超过 MTU 而引发的报文分片和合片。

因为 HTTPS 是基于 TLS 加密的,TLS握手阶段的多个 TCP 段(segment)就都撑满了 MSS(也就是前面分析的1、2、3 的数据包),于是就触发了防火墙隧道的 Bug.

到这里,你可能又会问了:这个例子中的丢包和乱序问题,其实也不限于防火墙,在路由器交换机层面也是有可能发生的,有没有办法可以更加确定地定位到防火墙,而不是其他网络设备呢?

  • 小结
    这次的“两侧抓包”,实际上就起到了决定性的作用。那么除了当前这个案例,总的来说,还有哪些情况下适合做“两侧抓包”呢?我个人的看法是这样的:
  1. 有条件的话(比如对两侧设备都有权限),就尽量做两侧抓包。这样可以收集到更多的信息。有更全面的信息,也就更容易作出更准确的判断。这好比我们做数学或者物理题,条件越充足,解题也相对越顺利。即使信息有所富余,也不会干扰到排查工作的正确性。当然这会损失一些效率,就看你怎么权衡了。
  2. 有丢包或者重传的情况的话,更应该做两侧抓包。因为只有通过比较两侧报文,才能确定具体的丢包位置等信息,而这些信意对于排查工作十分关键。我们经常会出现的情况是,完全不同的两种故障原因,在一侧(比如客户端)看起来很可能是相同的现象。这就好比,一个一半黑一半白的球体,当其中的一面正对着我们的时候,我们是完全不知道另外一面可能是完全不同的颜色。对于网络排查也是如此。
  3. 有些信息在单侧抓包里就能明确下来的,一般就没必要做两侧抓包了。

另外,一般用使序列号,在两个不同的抓包文件中如何定位到同个报文。

在一侧的文件中找到某个报文的裸序列号,作为搜索条件,在另外一侧的报文中搜索得到同样这个报文。这正是利用了 TCP 裸序列号在网络中传输的一致性(不变性)。后面的课程中, 我还将介绍更多这种“寻找同样报文”的方法,基本思想也都是基于某些信息在网络传输的一致性。

Web 站点访问被 reset

在这里插入图片描述

首先,过滤被访问的网站 IP,如下:

在这里插入图片描述

通过 ip. addr eq 253.61.239.103 and tcp. flags.reset eq 选出有问题的数据包,过滤出整个 TCP 流,并 Follow => TCP Stream 如下:

在这里插入图片描述

然后,Wireshark 会弹窗显示解读好的应用层信息,如下:

在这里插入图片描述

关闭弹窗,即可显示 TCP 流,如下:

在这里插入图片描述

可见,TCP 3次 握手后,client 发起了 GET /overview HTTP/1.1 的 HTTP 请求,但 server 端回复了 TCP RST,从而导致访问失败,如下图:

在这里插入图片描述

其实我们可以借助 TTL 知识来排查:
TTL是P包(网络层)的一个属性,字面上就差不多是生命长度的意思,每一个三层设备都会把路过的IP 包的TTL 值减去1。而IP 包的归宿,无非以下几种:

  • 网络包最终达到目的地;
  • 进入路由黑洞并被丢弃;
  • 因为网络设备问题被中途丢弃;
  • 持续被路由转发并 TTL 减1,直到TTL 为口而被丢弃。

在 RFC791 中规定了 TTL 值为 8位,所以取值范围是 0~255。

在这里插入图片描述

因为 TTL 是从出发后就开始递减的,那么必然,网络上我们能抓到的包,它的当前TTL一定比出发时的值要小。而且,我们可能也早就知道,TTL 从初始值到当前值的差值,就是经过的三层设备的数量。

不同的操作系统其初始TTL 值不同,一般来说 Windows 是128, Linux 是 64。由此,我们就可以做一些快速的判断了。比如我自己测试 ping www.baidu.com,收到的L是 52,意味着这个回包在公网经过了 64 - 52 = 12 跳第三层路由设备,如下图:

root@node:~# ping www.baidu.com
PING www.a.shifen.com (110.242.68.3) 56(84) bytes of data.
64 bytes from 110.242.68.3: icmp_seq=1 ttl=52 time=10.7 ms
64 bytes from 110.242.68.3: icmp_seq=2 ttl=52 time=9.57 ms
64 bytes from 110.242.68.3: icmp_seq=3 ttl=52 time=9.63 ms
64 bytes from 110.242.68.3: icmp_seq=4 ttl=52 time=9.19 ms
^C
--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 9.198/9.787/10.740/0.575 ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

因为内网路由设备很稳定,所以内网同一个连接中的 TTL 一般是稳定的,若波动则不正常。我们按此思路,继续看之前过滤出的 5 个数据包如下:

在这里插入图片描述

因为列表默认不显示 TTL,所以我们可在详情看到,并右键添加到列表里,如下图:

在这里插入图片描述
在这里插入图片描述

你也能很清楚地看到,同样的服务端,在三次握手中(SYN+ACK 报文)的 TTL 是59,在导致连接中断的 RST 包里却变成了 64!显然,这个 RST 包井不是跟我们握手的那个服务端发出的,否则 TTL 值就不会变化。

发出这个包的会是谁呢?其实,一般就是防火墙设备。由于防火週也遵循 IP 协议,而这里的 TTL 值是 64,这就说明这个防火墙跟客户端之间没有别的三层环节,或者说是三层直连的。

我们可以用一张简单的图来概括这个案子:

在这里插入图片描述

这样,我们底气大增!根据我们提供的信息,负责防火墙的同事就去复查了下,果然有发现:

防火墙上对二楼有线网络有一条可疑的策略,跟其他线路不同。这条策略的出发点是:每个网络协议规定了协议数据格式以及标准端口号,所以协议数据跟端口号不匹配的话,就可以认为是“有害”流量。因为 HTTP 协议标准端口是 TCP 80,但是我们这个 Web 站点是3001端口的,被防火墙认为不一致,所以就拒掉了。

我们来看一下当时的防火墙的配置如下图,这里的 application-default 就是说,端口需要跟协议匹配。要不然就会被禁止,也就是回复 RST 给客户端,终止这条连接。这个防火墙策路被修正后,问题也立刻被解決了。

在这里插入图片描述

访问 LDAPS 服务报 connection reset by peer

client 的抓包如下,其中有一个 RST:

在这里插入图片描述

server 的抓包如下,其中也有一个 RST:

在这里插入图片描述

但奇怪的是,client 发送的 Hello Client,却并未在 server 端发现,这是为什么呢?我们猜测如下图:

在这里插入图片描述

  • 进一步,我们排查 server 端的 TTL,从 server 视角来看,它收到的报文只有三个:SYN 包、ACK 包、最后的 RST 包。我们选择 SYN 和 RST,来对比看下它们的TTL是多少。下图是SYN包:

在这里插入图片描述

下图是 RST 包:

在这里插入图片描述

显然,跟之前的案例类似,这里的下TL 也发生了明显的变化。你应该也明白了,这两个包并不是同一个设备发出的

  • 同样,client 端收到的 SyNACK 包的 TTL是 110:

在这里插入图片描述

client 端收到的 RST 包的TTL是 54:

在这里插入图片描述

  • 终于,结合两侧的抓包,我们就可以把这个拼图给拼完整了,而 Client Hello 报文丢失之谜, 也将揭晓。更新一下前面的示意图,会变成下面这样。显然,Client Hello 报文就是被防火墙丢弃了。可见,防火墙的拦截行为可能出现在多个方向上(面对客户端时代表服务端,面对服务端时代表客户端),毕竟报文都要经过它,它如果想乱来,通信两端还真的无法控制它。从上图来看,防火墙两边“截胡”,两边拒绝。两边还都只好乖乖地听话,结束了连接。你都不能说它 “没有武德”,因为整个过程都是完全遵照了 TCP 规范的,防火墙做得不可谓不周到。

在这里插入图片描述

  • 如何应对防火墙发的 RST 报文,对我们的毒害呢?

我们可以对这条 iptables 规则设定精确的限定条件,使得它既能帮助我们丢弃“有害” 的 RST 报文,同时也不影响到其他正常连接的交互。

在报文进采的方向,报文会经过这样的处理流程:PREROUTING -> INPUT -> 本地处理 -> OUTPUT -> POSTROUTING

防火墙配置动手实践

现在“理都懂"了,让我们来动手实操一下。我们需要搭建这公一个测试环境:

虚拟机1(下面简称为1):配置为 client,实验时会在这台上执行 telnet,模拟访问行为。

虚拟机2(下面简称为2):配置为 client 的网关,这样它就可以劫持流量,模拟防火墙行为。

实验1:telnet 第三方站点

在1上,直接 telnet www.baidu.com 443, 可以成功。

在这里插入图片描述
然后我们需要配置一下,让1访问 www.baidu.com 的流量强制经过2,这样后续我们就可以让2来操控1 和 baidu 之间的连接了。接下来步骤稍多,感谢你的耐心。

在虚拟机1上,我们需要完成这么几件事:

# 创建隧道,隧道另一头就是虚拟机2,我们将在那里模拟一个“防火墙”。在上节课里,我们了解了 ipip 隧道,这里的 GRE 隧道也是类似的工作原理。
ip tun add tun0 mode gre remote 172.17.158.46 local 172.17.158.48 ttl 64 
ip Link set tun0 up
ip addr add 100.64.0.1 peer 16016410.2 dev tun0

# 添加路由项,使得本地去往第三方站点的流量,都走这条路由,也就是通过隧到达虚拟机 2,然后2来转发报文。
ip route add 110.242.68.0/24 via 100.64.0.2 dev tun0

# 当然了,虚拟机2上面也需要做对等的隧道配置
ip tun add tun0 mode gre remote 172.17.158.48 Local 172.17.158.46 ttl 64
ip Link set tun0 up
ip addr add 100.64.0.2 peer 100.64.0.1 dev tun0

# 虚拟机1把报文发到虚拟机2,但是如果后者不做配置,默认是会丢弃这些报文的,所以还需要在2上开启 ip_forward
sysctl het.ipv4.ip_forward=1

# 我们在2上运行 tcpdump port 443,然后 1 上运行 telnet owww.baidu.com 443。在2的 topdump 窗口里,已经可以看到从1 过来的流量了!
rooteserver$tcpdump port 443
tcpdump:verbose output suppressed, use -V or -vv for full protocol decode
Listening on eth0, Link-type EN1OMB (Ethernet), capture size 262144 bytes
16:53:36.054124 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 33644156, options [mss 1436, sackOK, TS val 2049305210 ecr O,nop, wscale 7], Length 0 
16:53:37-084514 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 33644156, options [mss 1436, sackOK,TS val 2049306241 ecr O,nop,wscale 7], Length 0
16:53:39.100482 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 33644156, options [mss 1436, sackOK, TS val 2049308257 ecr O, nop,wscale 7], length 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

咦?抓包里只有 SYN 包而没有 SYN+ACK,1这头的 telnet 也挂起,没响应了,这是怎么回事?

在这里插入图片描述

原来,我们还需要设置一下 NAT,要不然出去的报文源 IP 是 100.64.0.1,回包也会回这个地址,显然回不到2了。

在这里插入图片描述
所以还需要在2 上启用 SNAT:

iptables -t nat -A POSTROUTING -d 110.242.68.0/24 -j MASQUERADE
  • 1

我们再试试在1上发起 telnet www.baidu.com 443,果然成功了。

在这里插入图片描述
2 上的 tcpdump 也抓取到了正常连接的报文(这里就不贴了)。

实验2:插入 RST 报文,连接失败

现在,我们需要在2 上配置一个“插入 RST 报文”的动作,这样就可以模拟“防火墙阻隔 TCP 连接”的效果了。

在这里插入图片描述

我们可以在2 上运行这条 iptables 命令:

iptables -I FORWARD -P tcp -m tcp --tcp-flags SYN SYN -j REJECT --reiect-with tcp-reset
  • 1

有了这条命令,2 就用 TCP RST 拒绝了转发链(也就是命令中的 FORWARD 链)上的SYN 报文。1上的 telnet 立刻收到了拒绝:

在这里插入图片描述

2 上的 tcpdump 抓包窗口里也看到了握手和拒绝的报文:

rooteserver$tcpdump -i any port 443
tcpdump: verbose output suppressed, use -V or -vv for. full protocol decode
Listening on any, Link-type LINUX SLL (Linux cooked vi),capture size 262144 bytes
17:02:16.623480 IP 100.64.0.1.34428 > 110.242.68.3.https: Flags [S], seq 33145736, options [mss 1436, sackOK, TS val 2049825780 ecr O, nop, wscale 7], Length 0
17:02:16.623518 IP 110.242.68.3.https > 100.64.0.1.34428: Flags [R.], seq o, ack 3314573699, win 0, Length o
  • 1
  • 2
  • 3
  • 4
  • 5

可见,这个 RST 实实在在地起到了类仪防火墙的作用,让你的连接无法建立。你看,其实防火墙也没那么神秘,我们也可以实现。可以小小地鼓励一下你自己!

实验3:丢弃 RST 报文,连接成功

这套实验的核心目标是实现对 RST 干扰报文的规避,也就是丢弃这类报文。让我们继续实验,在1上添加这么一条 iptables 规则:

iptables -I INPUT -S 110.242.68.0/24 -p tcp =-sport 443 -m tcp --tcp-flags RST RST -m ttl --ttl-eq 64 -j DROP
  • 1

在这里插入图片描述
有了这条规则,我们就对符合条件的 TCP 报义进行了丢弃,这个条件就是“来自 110.242.68.0/24 网段的 TCP 源端口为443 的,带 RST 标志位的,TTL 等于 64的报文”。

这里的 TTL 条件就是关键了。在实际场景下,你就可以根据防火墙插入的RST 报文的TTL 的实际特征,写一条精确匹配的规则,把它跟正常报文区分开,进行精准的丢奔。

我们还是在1 上 telnet,然后发现这次不再被 reset,而是挂起了。在1的 topdump 中,也看到 SYN 发出了,对方也回复了 RST,但是我们并没有被真的被 reset。这里,正是这条丢奔 RST 报文的 iptables 规则起到了效果。

rooteclient2:~# tcpdump -i any host 110.242.68.4 and port 443
tcpdump: verbose output suppressed, use -v Or -VV for full protocol decode 
Listening on any, Link-type LINUX SLL (Linux cooked v1), capture Size 262144 bytes
17:27:50.748194 IP client2.53438> 110.242.68.4.https: Flags [s], seq 1164905016, options [mss 1436, sackOK,TS Val 2201853747 ecr O, nop,wscale 7], Length O
17:27:50.748433 IP 110.242.68.4.https > client2.53438: Flags [R. ], seq o, ack 1164905017, win 0 Length 0
  • 1
  • 2
  • 3
  • 4
  • 5

在实际场景中,只要设置前面提到的 iptables 丢弃特定 RST 报文的规则,就还有很大的几率能让这条连接继续保持下去,应用也运行下去。防火墙居然对你无效了,你除了长舒一口气, 会不会心里也冒出“终于翻身当主人”的感觉?

那么,除了这种丢弃有害 RST 的办法,还有没有别的办法呢?

就上面探讨的丢弃 RST 的方法来说,这是一个“应对式”的策略,也就是有人要“害我”,那我把“毒药”给扔了。但仍然是“被动”的方法。如果思考得更进一步,我们有没有办法,使得别人都没有机会来害我呢?就是你连“下毒”的机会也没有?这就是“主动”的策略了。

我个人看法是,可以到网络层(P层)去寻找机会。利用 IPSec(比如 IPv6 默认启用了 IPsec),我们就获得了在第三层加密的能力。因为就连P 报文本身都是加密的,那么即使防火墙要插入报文,因为它不具备密钥,所以这个报文会被接收端认为非法而被丢弃。这样就有希望真正摆脱防火墙对传输层(TCP/UDP)的这种控制。

  • 小结

通过两端抓包后进行网络包的对比分析,排查定位到防火墙的存在,这种方法对于丢包、乱序等场景特别有用。

通过分析 TTL 值的变化,快速定位到防火墙的存在。这种方法,对于连接被重置(RST)的场景,十分有效。

我们需要记住以下几个关键要点:

  1. 需要在受影响的客户端或者服务端进行抓包.这样你才能获取到你需要的关键信息,而这种信息,单纯通过应用层日志等途径,是很难获取的,这也是应用层排查的天然的不定。对此,你需要有清醒的认识,井深刻理解网络层排查技术的重要性和不可替代性。 分析抓包文件,识别下TL的变化。这里,你需要了解网络层和IP 协议的相关知识点。同时也要明白,即使一个知识点看似简单,其背后的设计原理,都大有文章。对每个技术细节的推敲,能帮助我们打造出更为强大的技术底蕴。

  2. 灵活运用 Wire Shark 自定义列。我们通过添加自定义列,让每个报文的TTL 值都在主视图中展现,极大地方便了对这些 TTL 的比较。所以我们除了掌握协议知识以外,也要挖掘各类工具的使用技巧。所谓“工欲善其事,必先利其器”也。

  3. 另外,在这节课的最后,我们也通过一系列实验,再一次深入理解了 RST 报文的作用,以及可能的规避方法。在这个过程中,我们学习了:
    3.1 GRE 隧道的搭建和用途:你可以用 ip tunnel add 命令创建 GRE 隧道,并用 ip route add 命令配置路由项,让某些网络的流量转而走这个隧道网关。注意,即使是一个二层不可达的 PP,通过隧道也可以“包装成”二层可达,进而可以配置为网关。这一点,如果不借助隧道, 是无法实现的。
    3.2 用 iptables 实现对报文的操控:在你需要模拟一些问题场景的时候,不妨多发掘一下 iptables 的“潜能”,比如可以丢弃符合某种条件的报文:iptables -I INPUT -S 110.242.68.0/24 -p tcp --sport 443 -m tep --tcp-flags RST RST - ttl --ttl-eq 64 一j DROP
    3.3 我们也学习了如何用 iptables 结合内核配置,实现一个简单的 NAT 网关:

iptables -t nat -A POSTROUTING -d 110.242.68.0/24 -j MASQUERADE
sysctl het.ipv4. ip_forward=1
  • 1
  • 2
  1. MAC地址中的组织唯一标识符 (OUI)由,IEEE(电气和电子工程师协会)分配给厂商,那么通过MAC地址可以辨别出厂商,防火墙的主要厂商也不多,从这块信息大约能判断出回包的是不是防火墙,因为是通过二层信息判断,所以这个方法是有局限性的。

  2. traceroute 是可以看到路径上所有的三层设备的,这里强调“三层”,是因为只有工作在IP层的路由性质的设备(包括三层交换机)才会回复ICMP消息。如果是纯二层设备,不会回复ICMP消息,也就在traceroute输出里看不到它。
    防火墙也经常出现在traceroute输出里,不过一般它的ip也不特殊,名称上(如果有反向解析记录的话)也未必说自己是防火墙。当然,事实上很多时候防火墙是没有反向解析记录的,也就是traceroute不加-n,那么别的节点可能显示为名称,但防火墙只是显示为ip,虽然准确率不太高,不过倒是可以用来参考。
    用TTL来判断是非常准的,几乎不会“失手”。但是题目不能用TTL了,那么IP层还有什么可以借用的吗?比如IP ID,因为ID号是通信两端自己各自生成的连续号码,防火墙插入报文的话,一般来说IP ID就不同了。你如果也有被防火墙干扰的抓包文件,可以观察IP ID在RST报文里跟其他正常报文是否不同。

七、保活机制

TCP 长连接为何总中断?

这个应用的架构比较简单:客户在云平台上部署了多台云主机,其中一台云主机专门做加解密,称之为加密服务器。另外几台云主机作为这台加密服务器的客户端,跟这台加密服务器保持TCP长连接。这些客户端会不时地跟加密服务器进行通信,完成加密操作。每45秒,客户端还会发送一次心跳包,这有两个作用:

维持这个长连接不被中断,即心跳保活,让长连接在两端保持下去;

在这里插入图片描述

如果加密服务器能在1秒内对心跳包进行回复,那么客户端就认为服务端正常可用,后续的数据交互(即加密请求)将继续在这条长连接上进行。而如果服务端未能在1秒内回复,那么客户端会认为该长连接已经中断,于是重启应用,发起一条新的长连接,并在日志中记录一次报错。

用类Python语法来描述,大体是这样的:

while true:
	sleep(45)
	if Keep_alive_probe() is true:
		continue
	else :
	restart()
	log_error()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

首先,对于TCP Keep-alive,你需要掌握:

  • 默认TCP 连接并不启用Keep-alive,若要打开的话要显式地调用setsockopt(),来设置保活包的发送间隔、等待时间、重试个数等配置。在全局层面,Linux还默认有3个跟Keep-alive相关的内核配置项可以调整: tcp_Keepalive_time,tcp_Keepalive_probes,还有tcp_Keepalive_intvl。
  • TCP心跳包的特点是,它的序列号是上一个包的序列号-1,而心跳回复包的确认号是这个序列号-1+1 (即还是这个序列号)。

然后,对于HTTP Keep-alive 的知识点,你需要理解:

  • HTTP/1.0默认是短连接,HTTP/1.1和2默认是长连接。
  • Connection: Keep-alive在HTTP/1.0里,能起到维持长连接的作用,而在 HTTP/1.1里面没有这个作用(因为默认就是长连接)。
  • Connection: Close在HTTP/1.1里,可以起到优雅关闭连接的作用。这个头部在流量调度场景下也很有用,能明显加快基于DNS/GSLB的流量调整的收敛速度。

八、MTU 最大分段大小

在 Wireshark 先将过滤后的报文保存成新的抓包文件,再在 Wireshark 中选择 Statistic =》 Flow Graph 可以生成二维数据流图,如下:

在这里插入图片描述

为什么有 DupAck

DupAck 是重复确认,它的出现一般意味着传输中出现丢包、乱序等情况。

下文中,因为两个 DupAck 的 ACK 均为1,所以是握手阶段完成时的确认号。Å即 client 握手成功后并未收到 server 发来的报文,所以其 ACK 停留在 1。如下图:

在这里插入图片描述

完整的流程图如下:

在这里插入图片描述

为什么重传失败

第一个报文就算暂时丢失,后续也有两次重传,为什么这些重传都没成功呢?既然我们同时有成功情况和不成功情况下的抓包文件,那我们直接比较,也许就能找到原因了。

让我们招两个文件中的类似的 TCP 流对比一下:

在这里插入图片描述
你能发现其中的不同吗?这应该还是比较容易发现的,它就是:HTTP 响应报文的大小。两次测试中,虽然HTTP 响应报文都分成了3个 TCP 报文,但最大报文大小不同:左边是1348,右边是1388,相差有 40字节。既然已经提到了报文大小,那你应该会联想到我们这节课的主题,MTU 了吧?

MTU,中文叫最大传输单元,也就是第三层的报文大小的上限。我们知道,网络路径中,小的报文相对容易传输,而大的报文遇到路径中某个 MTU 限制的可能会更大。那么在这里,假如这个问题真的是 MTU 限制导致的,显然,1388会比1348 更容易遇到这个阿题!

在这里插入图片描述

就像上面示意图展示的那样,如果路径中有一个偏小的 MTU 环节,那么完全有可能导致 1388 字节的报文无法通过,而 1348 字节的报文就可以通过。

而且,因为 MTU 是一个静态设置,在同样的路径上,一旦某个尺寸的报文一次没通过,后续的这个尺寸的报文全都不能通过。这样的话,后续重传的两次1388 字节的报文也都失败这个事实,也就可以解释了。

既然问题跟 MTU 有关,我们就检查了客户端到服务端之间的一整条链路,发现了一个之前没

注意到的情况:除了广州到北京之间有一条隧道,在北京 LB 到服务端之间,还有一条额外的隧道。我们在第5讲里学习过,隧道会增加报文的大小。而正是这条额外隧道,造成了报文被封装后,超过了路径最小 MTU 的大小!从下面的示意图中,我们能看到两次路径上的区别所在:

在这里插入图片描述
经过 LB 的时候,报文需要做2次封装(Tunnel 1 和 Tunnel2),市绕过 LB 就只要做 1次封装(只有 Tunnel 1)。跟生活中的例子一样,同样体型的两个人,穿两件衣服的那个看起来比穿单衣的那个要显胖一点,也是理所当然。要显瘦,穿薄点。或者实在要穿两件,那只好自己锻炼瘦身(改小自己的 MTU)了!

另外,由于 Tunnel 1 比 Tunnel 2 的封装更大一些,所以服务端选择了不同的传输尺寸,一个是1388,一个是1348。

为什么重传只有两次?

这个解释就是客户端超时,这一点其实我在前面介绍案例的时候就提到过。从TCP 流来看, 从发送 POST 请求开始到 FIN 结束,一共耗时正好在1 秒左右。我们可以把 Time 列从显示时间差(delta time)改为显示绝对时间 (absolute time),得到下图:

在这里插入图片描述

可见,客户端在 0.72 秒发出了 POST 请求,在1.72 秒发出了 TCP 挥手(第一个 FIN),相差正好 1秒,更多的重传还来不及发生,连接就结束了

这种“整数值”,一般是跟某种特定的(有意的)配置有关,而不是偶然。那么显然,这个案例里,客户端压测程序配置了1秒超时,目的也容易理解:这样可以保证即使一些请求没有得到回复,客户端还是可以快速释放资源,开启下一个测试请求。

MTU 解决方案

调小两端 MTU

其实,我估计你在日常工作中也可能遇到过这种 MTU 引发的问题。那一般来说,我们的对策是把两端的 MTU 往下调整,使得报文发出的时候的尺寸就小于路径最小 MTU,这样就可以规避掉这类问题了。

举个例子,在我的测试机上,执行 ip addr 命令,就可以查看到各个接口的 MTU,比如下面的输出里,eno1 口的当前 MTU 是1500:

2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 0c:c4:7a:80:a8:b8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.99/24 brd 192.168.2.255 scope global eno1
       valid_lft forever preferred_lft forever
    inet6 fe80::ec4:7aff:fe80:a8b8/64 scope link
       valid_lft forever preferred_lft forever
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

而假如,路径上有一个比 1500 更小的 MTU 设备,那为了适配这个状况,我们就需要调小自己的 MTU,这么做很简单,比如执行以下命令,就可以把 MTU 调整为 1400 字节:

sudo ip Link set eno1 mtu 1400
  • 1

在 iptables 的 nat 表和 FORWARD 处添加规则:改报文的 MSS 值

那除了这个方法,是不是就没有别的方法了呢?其实,我喜欢网络的一个重要原因是,它有很强的“可玩性”。只要我们有可能拆解网络报文,然后遵照协议规范做事情,那还是有不少灵活的操作空间的。你可能会好奇:这听起来有点像“灰色地带”一样,难道网络还能玩“潜规则” 吗?

比如这次的案例,网络环节都是软件路由和软件网关,所以“暗箱操作”也成了可能,我们不需要修改两端 MTU 就能解决这个问题。是不是有点神奇?不过,你理解了 TCP 和 MTU 的关系,就会明白这是如何做到的了。

MTU 本身是三层的概念,而在第四层的TCP 层面,有个对应的概念叫 MSS, Miaximum Segment Size(最大分段尺寸),也就是单纯的 TCP 载荷的最大尺寸。MTU 是三层报文的大小,在 MTU 的基础上刨去 IP 头部 20 字节和 TCP 头部 20 字节,就得到了最常见的 MSS 1460 字节。如果你之前对 MTU 和 MSS还分不清楚的话,现在应该能搞清楚了,如下图:

在这里插入图片描述

MSS 在 TCP 里是怎么体现的呢?其实我在 TCP 握手那一讲里提到过 Window Scale,你很容易能联想到,MSS 其实也是在握手阶段完成“通知”的。亡SYN 报文里,客户端向服务端通扱了自己的 MSS。而在 SYN+ACK 里,服务端也做了类似的事情。这样,两端就知道了对端的 MSS,在这条连接里发送报文的时候,双方发送的 TCP 载荷都不会超过对方声明的 MSS.

当然,如果发送端本地网口的 MTU 值,比对方的 MSS + IP header + TCP header 更低,那么会以本地 MTU 为准,这一点也不难理解。这里借用一下 RFC879 里的公式:

SndMaxSegSiz = MIN((MTU sizeof(TCPHiR) - sizeof(IPHDR)), MSS)
  • 1

MTU 是两端的静态配置,除非我们登录机器,否则改不了它们的 MTU。但是,它们的 TCP 报文却是在网络上传送的,而我们做“暗箱操作”的机会在于:TCP 本身不加密,这就使得它可以被改变!也就是我们可以在中间环节修改 TCP 报文,让其中的 MSS 变为我们想要的值,比如把它调小。

这星立功的又是一张熟悉的面孔:iptables。在中间环节(比如某个软件路由或者软件网关) 上,在 iptabes 的 nat 表和 FORWARD 链这个位置,我们可以添加规则,修改报文的 MSS 值。比如在这个案例里,我们通过下面这条命令,把经过这个网络环节的下CP 握手报文里的 MSS,改为1400 字节:

iptables -A FORWARD -P tap --tcp-flags SYN SYN -J TCPMSS --set-mss 1400
  • 1

它工作起来就是下图这样,是不是很巧妙?通过这种途中的修改,两端就以修改后的 MSS 来工作了,这样就避免了用原先过大的 MSS 引!发的问题。我称之为〝暗箱操作”,就是因为这是通信双方都不知道的一个操作,而正是这个操作不动声色地解了问题,如下图:

在这里插入图片描述

什么是网卡的 TSO 和 GRO

前面说的都是操作系统会做TCP 分段的情况。但是,这个工作其实还是有一些 CPU 的开销的,毕竟需要把应用层消息切分为多个分段,然后给它们组装 TCP 头部等。而为了提高性能,网卡厂商们提供了一个特性,就是让这个分段的工作从内核下沉到网卡上来完成,这个特性就是 T®P Segmentation Offload。

这里的 offload,如果仅仅翻译成“卸载”,可能还是有点晦涩。其实,它是 off + load,那什么是 load 呢?就是 CPU 的开销。如果网卡硬件芯片完成了这部分计算任务,那公 CPU 就减轻负担了,这就是 offload 一词的真正含义。

TSO 启用后,发送出去的报文可能会超过 MSS。同样的,在接收报文的方向,我们也可以启用 GRO (Generie Receive Offload)。比如下图中,TCP 载荷就有2800字节,这并不是说这些报文真的是以 2800 字节这个尺寸从网络上传输过来的,而是由手接收端启用了 GRO,由接收端的网卡负责把几个小报文“拼接”成了 2800 字节。

在这里插入图片描述

所以,如果以后你在 Wireshark 里看到这种超过1460 字节的 TCP 段长度,不要觉得奇怪了,这只是因为你启用了 TSO(发送方向),或者是 GRO(接收方向},而不是 TCP 报文真的就有这么大!

想要确认你的网卡是否启用了这些特性,可以用 ethtool 命令,比如下面这样:

root@node:~# ethtool -k eno1 | grep offload
tcp-segmentation-offload: on
udp-fragmentation-offload: off [fixed]
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: off [fixed]
rx-vlan-offload: on
tx-vlan-offload: on
l2-fwd-offload: off [fixed]
hw-tc-offload: off [fixed]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

当然,在上面的输出中,你也能看到有好几种别的 offload。如果你感兴趣,可以自己搜索研究下,这里就不展开了。

对了,要想启用或者关闭 TSO/GRO,也是用 ethtool 命令,比如这样:

$ sudo ethtool -K eno1 tso off 
$ sudo ethtool -k eno1 grep offload 
tcp-segmentation-offload: off
  • 1
  • 2
  • 3

IP 分片

IP层也有跟 TCP 分段类似的机制,它就是1P 分片。很多人搞不清P 分片和 TCP 分段的区别,甚至经常混为一谈。事实上,它们是两个在不同层面的分包机制,互不影响。

在TCP 这一层,分段的对象是应用层发给 TCP 的消息体 (message)。比如应用给 TCP 协议栈发送了 3000 字节的消息,那么 TCP 发现这个消息超过了 MSS(常见值为 1460),就必须要进行分段,比如可能分成 1460,1460,80 这三个 TOP段。

在这里插入图片描述

在 IP 这一层,分片的对象是P 包的载荷,它可以是 TCP 报文,也可以是UDP 报文,还可以是P 层自己的报文比如 ICMP。

为了帮助你理解 segmentation 和 fragmentation 的区别,我现在假设一个“奇葩”的场景, 也就是 MSS为1460 字节,而 MTU 却只有1000 字节,那么 segmentation 和 fragmentation 将按照如下示意图来工作:

在这里插入图片描述

补充:为了方便讨论,我们假设TCP 头部就是没有 Option 扩展的20字节。但实际场景里,很可能 MSS 小于 1460 字节,而 TCP 头部也超过20字节。

当然,实际的操作系统不太会做这种自我矛盾的傻事,这是因为它自身会解决好 MSS 跟 MTU 的关系,比如一般来说,MSS 会自动调整为 MTU 减去 40 字节。但是我们如果把视野扩大到局域网,也就是主机再加上网络设备,那么就有可能发生这样的情况:1460 字节的 TCP 分段由这台主机完成,1000 字节的P 分片由路径中葉台 MTU 为1000 的网络设备完成。

这里其实也有个隐含的条件,就是主机发出的 1500 字节的报文,不能设置 DF (Don’t Fragment)位,否则它既超过了1000这个路径最小 MTU,又不允许分片,那么网络设备只能把它丢弃。

在 Wireshark 里,我们可以清楚地看到 P 报文的这几个标志位:

在这里插入图片描述
现在我们假设主机发出的报文是不带 DF 位的,那么在这种情况下,这台风络设备会把它切分为一个 1000(也就是960+20+20)字节的报文和一个 520(也就是500+20)字节的报文。1000 字节的P 报文的MF 位(More Fragment) 会设置为1,表示后续还有更多分片,而520字节的IP 报文的 MF 字段为 0。

这样的话,接收端收到第一个 P 报文时发现 MF 是1,就会等第二个 1P 报文到达,又因为第二个报文的MF 是口,那么结合第二个报文的 fragment offset 信息(这个报文在分片流中的位置),就把这两个报文重组为一个新的完整的IP 报文,然后进入正常处理流程,也就是上报给 TCP。

不过在现实场景里,P 分片是需要尽量避免的,原因有很多,主要是因为互联网是一个松散的架构,这就导致路径中的各个环节未必会完全遵照所有的约定。比如你发出了大于 PMTU 的报文,寄希望于 MTU 较小的那个网络环节为你做分片,但事实上它可能不做分片,而是直接丢弃,比如下面两种情况:

  • 它考虑到开销等问题,未必做分片,所以直接丢弃。
  • 如果你的报文有 DF 标志位,那么也是直接丢弃。

即使它帮你做了分片,但因为开销比较大,增加的时延对性能也是一个不利因装。

另外一个原因是,分片后,TCP 报文头部只在第一个 IP 分片中,后续分片不带 TCP 头部, 那么防火墙就不知道后面这几个报文用的传输层协议是什么,可能判断为有害报文而丢弃。

总之,为了避免这些麻烦,我们还是不要开启P 分片功能。事实上,Linux 默认的配置就是,发出的P 报文都设置了 DF 位,就是明确告诉每个三层设备:“不要对我的报文做分片, 如果超出了你的 MTU,那就直接丢奔,好过你慢腾腾地做分片,反而降低了网络性能”。

  • 小结

这次,我们通过拆解一个典型的 MTU 引发的传输问题,学习了 MTU 和MSS、 分段和分片、各种卸载(offload)机制等概念。这里,我帮你再提炼几个要点:

  • 在案例分析的过程中,我们解读了 Wireshark 里的信息,特别是两次 DupAck 和两次重传,推导出了问题的根因。这里,你需要了解200ms 超时重传这个知识点,这在平时排查重传问题时也经常用到。
  • 借助 Wireshark 的 Flow graph,我们可以更加清晰地看到两端报文的流动过程,这对我们推导问题提供了便利。
  • 如果能稳定重现成功和失败这两种不同场景,那就对我们排查工作提供了极大的便利。我们通过对比成功和失败两种场景下的不同的抓包文件,能比较快地定位到问题根因。
  • 如果排查中遇到有“整数值”出现,可以重点查一下,一般这跟人为的设置有关系,也有可能就是根因,或者与根因有关。
  • 如果你对网络中间环节(包括 LB、网关、防火墙等)有权限,又不想改动两端机器的 MTU,那么可以选择在中间环节实施“暗箱操作”,也就是用 iptables 规则改动双方的 MSS,从而间接地达到“双方不发送超过 MTU 的报文”的目的。
  • 我们也学习了如何用 ethtool 工具查看 offload 相关特性,包括 TSO、 LRO、 GRO 等等。
  • 同样通过 ethtool,我们还可以对这些特性进行启用或者禁用,这为我们的排查和调优工作提供了更大的余地。
  • 抓包示例文件:https://gitee.com/steelvictor/network-analysis/tree/master/08
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号