大师兄

08 | 工欲善其事必先利其器:学会使用各种工具

你好,我是盛延敏,这里是网络编程实战第8讲,欢迎回来。

上一讲我们讲到了本地套接字,加上前面介绍的TCP、UDP套接字,你会发现我们已经比较全面地接触了套接字。

其实在平常使用套接字开发和测试过程中,我们总会碰到这样或那样的问题。学会对这些问题进行诊断和分析,其实需要不断地积累经验。而Linux平台下提供的各种网络工具,则为我们进行诊断分析提供了很好的帮助。在这一讲里,我将会选择几个重点的工具逐一介绍。

必备工具: ping

这个命令我想大家都不陌生,“ping”这个命名来自于声呐探测,在网络上用来完成对网络连通性的探测,这个命名可以说是恰如其分了。

$ ping www.sina.com.cn
PING www.sina.com.cn (202.102.94.124) 56(84) bytes of data.
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=1 ttl=63 time=8.64 ms
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=2 ttl=63 time=11.3 ms
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=3 ttl=63 time=8.66 ms
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=4 ttl=63 time=13.7 ms
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=5 ttl=63 time=8.22 ms
64 bytes from www.sina.com.cn (202.102.94.124): icmp_seq=6 ttl=63 time=7.99 ms
^C
--- www.sina.com.cn ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5006ms
rtt min/avg/max/mdev = 7.997/9.782/13.795/2.112 ms

在上面的例子中,我使用ping命令探测了和新浪网的网络连通性。可以看到,每次显示是按照sequence序列号排序显示的,一并显示的,也包括TTL(time to live),反映了两个IP地址之间传输的时间。最后还显示了ping命令的统计信息,如最小时间、平均时间等。

我们需要经常和Linux下的ping命令打交道,那么ping命令的原理到底是什么呢?它是基于TCP还是UDP开发的?

都不是。

其实,ping是基于一种叫做ICMP的协议开发的,ICMP又是一种基于IP协议的控制协议,翻译为网际控制协议,其报文格式如下图:


ICMP在IP报文后加入了新的内容,这些内容包括:

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

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

IP报文通过ARP协议,源地址和目的地址被翻译成MAC地址,经过数据链路层后,报文被传输出去。当报文到达目的地址之后,目的地址所在的主机也按照ICMP协议进行应答。之所以叫做协议,是因为双方都会遵守这个报文格式,并且也会按照格式进行发送-应答。

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

可以说,ICMP协议为我们侦测网络问题提供了非常好的支持。另外一种对路由的检测命令Traceroute也是通过ICMP协议来完成的,这里就不展开讲了。

基本命令: ifconfig

很多熟悉Windows的同学都知道Windows有一个ipconfig命令,用来显示当前的网络设备列表。事实上,Linux有一个对应的命令叫做ifconfig,也用来显示当前系统中的所有网络设备,通俗一点的说,就是网卡列表。

vagrant@ubuntu-xenial-01:~$ ifconfig
cni0 Link encap:Ethernet HWaddr 0a:58:0a:f4:00:01
inet addr:10.244.0.1 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::401:b4ff:fe51:bcf9/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1450 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)

我稍微解释一下这里面显示的数据。

Link encap:Ethernet HWaddr 02:54:ad:ea:60:2e

上面这段表明这是一个以太网设备,MAC地址为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

这里显示的是网卡的IPv4和IPv6地址,其中IPv4还显示了该网络的子网掩码以及广播地址。

在每个IPv4子网中,有一个特殊地址被保留作为子网广播地址,比如这里的10.0.2.255就是这个子网的广播地址。当向这个地址发送请求时,就会向以太网网络上的一组主机发送请求。

通常来说,这种被称作广播(broadcast)的技术,是用UDP来实现的。

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

这里显示的是网卡的状态,MTU是最大传输单元的意思,表示的是链路层包的大小。1500表示的是字节大小。

Metric大家可能不知道是干啥用的,这里解释下,Linux在一台主机上可以有多个网卡设备,很可能有这么一种情况,多个网卡可以路由到目的地。一个简单的例子是在同时有无线网卡和有线网卡的情况下,网络连接是从哪一个网卡设备上出去的?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)

netstat和lsof:对网络状况了如指掌

在平时的工作中,我们最常碰到的问题就是某某进程对应的网络状况如何?是不是连接被打爆了?还是有大量的TIME_WAIT连接?

netstat可以帮助我们了解当前的网络连接状况,比如我想知道当前所有的连接详情,就可以使用下面这行命令:

netstat -alepn

可能的结果为:


netstat会把所有IPv4形态的TCP,IPV6形态的TCP、UDP以及UNIX域的套接字都显示出来。

对于TCP类型来说,最大的好处是可以清楚地看到一条TCP连接的四元组(源地址、源端口、目的地地址和目的端口)。

例如这里的一条信息:

tcp 0 0 127.0.0.1:2379 127.0.0.1:52464 ESTABLISHED 0 27710 3496/etcd

它表达的意思是本地127.0.0.1的端口52464连上本地127.0.0.1的端口2379,状态为ESTABLISHED,本地进程为etcd,进程为3496。

这在实战分析的时候非常有用,比如你可以很方便地知道,在某个时候是不是有很多TIME_WAIT的TCP连接,导致端口号被占用光,以致新的连接分配不了。

当然,我们也可以只对UNIX套接字进行筛查。

netstat Socket -x -alepn


UNIX套接字的结果稍有不同,最关键的信息是Path,这个信息显示了本地套接字监听的文件路径,比如这条:

unix 3 [ ] STREAM CONNECTED 23209 1400/dockerd /var/run/docker.sock

这其实就是大名鼎鼎的Docker在本地套接字的监听路径。/var/run/docker.sock是本地套接字监听地址,dockerd是进程名称,1400是进程号。

netstat命令可以选择的参数非常之多,这里只关注了几个简单的场景,你可以通过帮助命令或者查阅文档获得更多的信息。

lsof的常见用途之一是帮助我们找出在指定的IP地址或者端口上打开套接字的进程,而netstat则告诉我们IP地址和端口使用的情况,以及各个TCP连接的状态。Isof和netstst可以结合起来一起使用。

比如说,我们可以通过lsof查看到底是谁打开了这个文件:

lsof /var/run/docker.sock

下面这张图显示了是dockerd打开了这个本地文件套接字:


lsof还有一个非常常见的用途。如果我们启动了一个服务器程序,发现这个服务器需要绑定的端口地址已经被占用,内核报出“该地址已在使用”的出错信息,我们可以使用lsof找出正在使用该端口的那个进程。比如下面这个代码,就帮我们找到了使用8080端口的那个进程,从而帮助我们定位问题。

lsof -i :8080

抓包利器: tcpdump

tcpdump这样的抓包工具对于网络编程而言是非常有用的,特别是在一些“山重水复疑无路”的情形下,通过tcpdump这样的抓包工具,往往可以达到“柳暗花明又一村”的效果。

tcpdump具有非常强大的过滤和匹配功能。

比如说指定网卡:

tcpdump -i eth0

再比如说指定来源:

tcpdump src host hostname

我们再来一个复杂一点的例子。这里抓的包是TCP,且端口是80,包来自IP地址为192.168.1.25的主机地址。

tcpdump 'tcp and port 80 and src host 192.168.1.25'

如果我们对TCP协议非常熟悉,还可以写出这样的tcpdump命令:

tcpdump 'tcp and port 80 and tcp[13:1]&2 != 0'

这里tcp[13:1]表示的是TCP头部开始处偏移为13的字节,如果这个值为2,说明设置了SYN分节,当然,我们也可以设置成其他值来获取希望类型的分节。注意,这里的偏移是从0开始算起的,tcp[13]其实是报文里的第14个字节。

tcpdump在开启抓包的时候,会自动创建一个类型为AF_PACKET的网络套接口,并向系统内核注册。当网卡接收到一个网络报文之后,它会遍历系统中所有已经被注册的网络协议,包括其中已经注册了的AF_PACKET网络协议。系统内核接下来就会将网卡收到的报文发送给该协议的回调函数进行一次处理,回调函数可以把接收到的报文完完整整地复制一份,假装是自己接收到的报文,然后交给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

此外,tcpdump还可以对每条TCP报文的细节进行显示,让我们可以看到每条报文的详细字节信息。这在对报文进行排查的时候很有用。

小结

本章我讲述了一些常见的网络诊断工具,这些工具需要你了解之后活学活用。用好它们,对加深网络编程的理解,以及对问题情况进行排查等都有非常大的帮助。

我再来总结一下这几个命令的作用:

  • ping可以用来帮助我们进行网络连通性的探测。
  • ifconfig,用来显示当前系统中的所有网络设备。
  • netstat和lsof可以查看活动的连接状况。
  • tcpdump可以对各种奇怪的环境进行抓包,进而帮我们了解报文,排查问题。

思考题

最后给大家留两个思考题。

本章我讲到了强大的抓包工具tcpdump,你知道tcpdump这个工具还可以对UDP包进行抓包处理吗?你不妨尝试一下。

另外,netstat输出时,监听状态的套接字所对应的Foreign Address显示的*.*表示的是什么意思呢?

欢迎你在评论区写下你的思考,我会和你一起交流,也欢迎你把这篇文章分享给你的朋友或者同事,一起讨论下这几个工具。