阅读视图

发现新文章,点击刷新页面。
🔲 ☆

学习网络的一点经验

在网络抓包系列的最后,再来说一下网络学习的一些经验。毕竟,能通过抓包来解决问题的前提是具备网络协议的知识,不然的话 Wireshark 和 tcpdump 用得再熟练也看不懂抓到的包。

以下是我觉得一些比较好的学习方法,我从中获益良多。

和朋友讨论学到的东西

学到了一个新的知识点或者新的协议,可以尝试解释给朋友或者同事听。比如在吃饭的时候,「嘿,你知道吗?TCP 三次握手的第三个包实际上是可以携带数据的!」然后他们会想这个问题,问你「要是有些 Server 不能处理带数据的第三个包怎么办?」这样,你就要重新思考你学的知识,看它是否考虑周全。教是最好的学习方法。如果没有朋友的话,可以考虑写一个博客(像我一样?呵呵)。

认识实际的网络,也可以从公司的网络结构开始。平时总结一下自己的问题,多阅读一些内部的文档。在内部 traceroute 跑一下,看看每一个 hop 都是什么。找时间请网络团队的同事吃饭,然后问他们你的问题。

举一反三

要学会提问。网络协议的目的一部分是传输数据,更重要的,它需要处理各种异常情况,保证即使出现各种意外,也能尽最大努力工作。所以,我们在学习的时候可以经常问题自己:如果这时候出现 xx 会怎么样呢?

问自己问题,然后思考,尝试给自己解答。在思考的过程中,会产生很多解决方案,大部分可能都是有问题的,然后找到其中的问题,推翻自己的方案。比如,网络为什么要分开三层和二层?这个世界上只有 IP 层,没有链路层行不行?或者只有链路层,不要 IP 层了行不行,整个世界都是一个「大二层」,会有什么问题?

网络相关的知识常常是零碎散乱。我的经验是,不要一开始就跳到兔子洞里面去。面对一个新的概念的时候,可以这样来入门。举个例子说,比如 SOCKS5 协议:

  • 第一步是问:「这个东西是来解决什么问题的?」这个问题必须始终记在心里的,如果搞不清楚,后面会有更多的困惑。
  • 第二步,可以自己想出来一种方案来解决这个问题。方案一开始不必完美,只要解决问题即可。很多方案就是这样设计出来的,现有一个简单的方法,再想想有没有什么缺陷,然后试图优化方案来解决缺陷。
  • 如果觉得自己的方案应该没有问题了,就可以进行第三步了,去深入理解 SOCKS5 协议。可以直接去读 RFC1,不要害怕,RFC 读起来没有那么难!阅读 RFC,可以和自己的方案做对比,看看有哪些问题是还没有想到的。

其实我觉得,网络协议的设计在最开始的时候考虑并没有很周全,比如 TCP 的一些问题,HOL Blocking;比如 OSPF 协议的奇怪名字 NSSA 之类的。感觉是设计出来之后,然后发现在现实中会有问题,就会在给原来的东西打上补丁。

研究网络协议

很多网络协议设计的是很美的,我喜欢那些简单又能解决问题的协议。比如 VRRP,只有一种数据格式包。像 TCP,DHCP 这种就复杂许多,它们的魅力在于解决了复杂的问题,适应各种复杂的环境。

了解网络协议除了按照上面的方法自己来挑战设计,还可以格物致知,看协议的每一个字段,搞清楚这些字段都在解决什么问题。一般每一个字段都是为了解决某种问题或需求而存在的。

面对复杂的协议的时候,把它拆成一个一个小的部分,单独攻破。比如 BGP,状态,选路之类的太复杂了。方法就是 break down,一个一个来。

光学习 Wireshark 这种工具是不行的,工具是围绕网络协议设计出来的,网络协议设计出来是为了解决真实的问题的。所以学习网络协议,它们解决问题的方法,才是主要的。另外,只有懂网络协议才能用好工具。比如,精通 ICMP 和 IP 的 TTL 设计,才能精通 traceroute 的使用。

多读书

像 TCP 的书从协议设计到实现,有数不清的书来介绍。读一本是不够的,因为对于网络协议,不同的作者有不同的理解,有不同的比喻。读书就像和作者聊天一样,和不同的人探讨自己对一个东西的认识,有助于加深理解。

上面通过自己设计协议来理解协议的方式,就是 Computer Networking: A Top-Down Approach2 这本书作者用的,作者用从零开始设计 TCP 的方式来教授为什么 TCP 是今天这个样子的。

同一本书也可以读第二遍,所谓书读百遍,其义自见。

PS:不要看国内的大学讲课视频

如果一个视频明显是那种中国大学的讲课录屏,基本就没有必要看了。这些视频通常知识和现实脱节,讲的内容很多也在现实中见不到,照本宣科,避重就轻。除了磨灭读者的兴趣,把有趣变成枯燥,就没什么价值了。没有兴趣和热情,就学不好。


说到最后,兴趣是最好的老师,对我来说,根据自己的知识和工具拨开迷雾,解决一个一个的问题,是再有意思不过的了。每个人会在人生的一个时间,发现这个世界不是公平的。但是在计算机的世界里,是很公平的,多花一点时间,多思考一些,就会多一分收获,它与天分无关。技术会过时,自己辛苦习得的技能也可能被 AI 轻而易举取代,但是曾经在学习和进步中体会到的乐趣是真实存在的,那些经验运用到新的技术中也多少是有所帮助的。祝愿读者在自己的领域体会到平静和乐趣。


==抓包破案录==

这篇文章是抓包破案录系列文章(之前叫做《计算机网络实用技术》,后来改名了)中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网络抓包与分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

如果您正在阅读的是题目类的文章,这个目录内容正好用来隔离其他读者的评论。读完题目可以稍作暂停,进行思考,继续向下滑动,可能会被其他的读者剧透答案。

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?答案和解析
  7. 网工闯了什么祸?答案和解析阅读加餐!
  8. 重新认识 TCP 的握手和挥手答案和解析
  9. 3.5 秒初始延迟问题答案和解析
  10. 网络断断续续……答案和解析
  11. 延迟增加了多少?答案和解析
  12. 压测的时候 QPS 为什么上不去?答案和解析
  13. TCP 下载速度为什么这么慢?答案和解析
  14. 请求为什么超时了?答案和解析
  15. 0.01% 的概率超时问题答案和解析
  16. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。
  1. 如果你感兴趣的话,socks5 的 RFC 是 https://datatracker.ietf.org/doc/html/rfc1928 ↩
  2. https://www.amazon.sg/Computer-Networking-Top-Down-James-Kurose/dp/0133594149 ↩
🔲 ☆

Linux interface Vlan 和 Bond 配置错误问题排查

昨天同事报告了一个 Linux 机器网络问题,现象是:一台服务器无法 ping 192.168.1.253,但是可以 ping 192.168.1.252 和 192.168.1.254. 这三个 IP 都是交换机的 IP,并且和和服务器的 IP 在同一个子网下。

服务器使用了 bond1 分别连接两台交换机2,两台交换机通过 VRRP 协议提供一个高可用的网关 IP3。其中,网段的最高位一般是 VRRP 的 VIP,即 192.168.1.254,而最高位 -1 和 -2 分别是两个交换机的物理 IP,即 192.168.1.253 和 192.168.1.252 分别是两台交换机。

于是,看到这个现象,自然而然地想到是其中一台交换机有问题,192.168.1.253 已经挂了,192.168.1.252 还存活,并且担任了 192.168.1.254 的 VIP 的责任。

先去这台服务器 ping 了一下,果然是 ping 不通的,ping 显示的错误信息是 Destination Host Unreachable。然后在服务器抓包,确认下 ICMP reply 确实没有发送回来。tcpdump -i bond0 icmp. 抓包确实没有看到 ICMP reply 包,但是奇怪的是,居然连 ICMP echo 也没有抓到

之后又去检查了交换机的配置,包括 channel-group,VLAN 配置,ACL 等等,也确认了下两台交换机之间的横连状态是正常的。这时候看起来不像是交换机的问题了。使用另一台服务器 ping 了一下这三个 IP,.252, .253, .254 都是通的。那应该是服务器的问题而不是交换机的问题。

其实这部分有些走弯路,因为 ping 明确显示 Destination Host Unreachable,说明这个包并没有发出去;而且 tcpdump 也没有抓到包,也可以印证。

接下来继续在服务器上定位问题。

ICMP 发包有问题,就先检查一下发包链路。之前遇到过类似错误,是 iptables 的 OUTPUT chain 把包 drop 了,于是先检查了 iptables,确认没有相关的 DROP。

ICMP 是基于 IP 层的协议,IP 层的协议依赖 ARP 协议来找到 MAC 地址,然后封装成二层 Frame,才能发出去,接下来去检查 ARP。(其实上一步直接检查 iptables 是不合理的,ARP 是第一步,有了 ARP 才可能构造出来完整的 Frame 开始发送,应该先从 ARP 开始排查)。

检查 arp -a | grep .253,发现 ARP 的 cache 结果是 <incomplete>. 然后用 arping 192.168.1.253 验证 ARP request 是否能得到正常的 reply,发现结果都是 Timeout。

到这里已经知道为什么 ping 会失败了,因为服务器得不到这个 IP 对应的 ARP 请求,所以 ping 无法将 ICMP request 的包发送出去,直接报错了。

接下来就定位为什么 ARP 会失败。

正常来说,ARP 应该从 bond0 接口发送出去一个 request,然后收到一个 reply,刷新服务器的 ARP cache entry。

服务器的 interface 配置如下,服务器所在的 VLAN 是 1000,和交换机做了 Trunking4,发送包的路由是走 bond0.1000@bond0 这个 interface,bond0.1000@bond0 是一个虚拟 interface,主要的功能是,发包的时候对包进行 802.1Q VLAN 封装,然后通过底层的 interface——在这里是 bond0——发送出去,收包的时候对 VLAN 进行解封装。

root@ubuntu-1:/$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff
3: bond0.1000@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff
4: eth0.1000@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff
143: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc fq_codel master bond0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff
144: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc fq_codel master bond0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff
接口的逻辑图

我首先在 bond0 抓包,确认 ARP 的发送和接收在协议上是正常的。

结果在这一步就发现问题了,bond0 抓包发现,只有发出去的包,没有收到的包。

为啥交换机不响应 ARP 了呢?

这时候又怀疑是交换机的问题,去检查了交换机的两个端口配置。没有发现问题。而且在其他机器上,ping 和 arping 都是没有问题的,交换机设备的问题可能性比较小。

也不会是服务器安全策略的问题,如果是的话,tcpdump 也会先抓到包的,在后面才会被 iptables 之类的 DROP 掉。

于是仔细想一想交换机和服务器之间经过了哪些组件,网卡收包,中断,网卡 driver,bond driver,协议栈处理。抓包都没抓到,说明问题出在协议栈之前,于是怀疑到 bond driver 头上去。

下一步,在物理 interface 上抓包,确认物理 interface 到底收到了 ARP reply 了没有。结果是,发现 eth0 这个 interface 收到了 ARP reply!

ARP reply 在 eth0 上收到了,但是 bond0 上没收到。这下感觉快要得到答案了。bond 有两个 slave,我把 eth0 shutdown 了,只留下 eth1,然后网路正常了。那要么是 bond driver 真的有问题,要么是我们的配置有问题。从经验上看,Linux driver 存在 bug 的概率要远远小于我们的配置错误。于是我去检查 bond 相关的配置。

检查 bond 状态 (/proc/net/bonding/bond0 文件), bond 配置,都没发现问题。可能是 eht0 这个接口有问题?

在重新看 interface 的时候(即上面的 ip link 命令和输出),我发现了可疑的一条 interface:

4: eth0.1000@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:db:e6:76:dd:8a brd ff:ff:ff:ff:ff:ff

这里多出来一个 VLAN interface。

所以,实际上的 interface 配置应该是如下这样。由于 eth0.1000 的存在,我怀疑 eth0 收到的 ARP reply 实际上是送给了 eth0.1000@eth0 而不是 bond0,然后在 ARP 协议处理的时候,Linux 认为我们没有从 eth0.1000 发送出去 ARP request,但是却收到了 ARP 响应,属于 Gratuitous ARP5. 而发送 ARP request 的 bond0,从来没有收到 ARP reply。ARP cache 是 per interface 的,所以 bond0 无法发送 ICMP 出去。

eth0.1000 的配置

证明这个猜测很简单,只要在 eth0.1000@eth0 抓包,看是否有 ARP reply 就好了。抓包发现果然有。

并且把这个接口的 arp_accept 打开,让其接受 Gratuitous ARP,发现 ARP cache 出现了如下记录:

proot@ubuntu-1:/$ arp -a
? (192.168.1.253) at c6:34:22:fc:78:b4 [ether] on eth0.1000

说明这个结论是正确的。到这里就发现,其实问题不仅仅是 ARP 的问题,因为 bond 的两个 slave 有一个不对,收包的时候可能是从 eth0 收,也可能是从 eth1 收,取决于交换机的 hash 策略6。如果从 eth0 进来,那么协议栈的 skb 的 device 就会是 eth0.1000@eth0,所有有连接的协议处理都匹配不上。

于是我 shutdown eth0.1000@eth0 这个接口,理论上机器的配置应该都是对的了。

结果不是,问题依然存在,有点让人怀疑人生。由于接口 down 了就无法抓包了,不太好确认包是不是还在往 eth0.1000@eth0 送了。此处又花了一些时间排查,因为怀疑自己的推论是错误的,是不是有别的地方导致这个问题?一通误打误撞,决定删除这个多余的接口,然后网路就完全恢复了。从结果看,只 shutdown 这个接口不能阻止包往这个 vlan 接口送,得删除才行。

事后我们得知,这台服务器在 infra 团队交付的时候存在问题,应该配置 bonding,但是没有配置,只是在一条线(eth0)上配置了 VLAN。我们的同事拿到机器之后修复了 bonding 问题,但是并没有删除 eth0.1000@eth0 这个 VLAN 虚拟接口,导致产生了非预期的行为。

后来看了下源代码,发现 VLAN 的处理确实优先级比较高,在 __netif_receive_skb_core7 这里就会执行 vlan_do_recieve8,然后会把 device 的 id 设置在 skb 上。这个逻辑比 bond driver 的逻辑靠前,导致后续协议栈的处理,会认为这个包是从 eth0.1000@eth0 收到的,而不是从 bond0 收到的。

  1. 数据中心网络高可用技术之从服务器到交换机:802.3 ad ↩
  2. 数据中心网络高可用技术之从交换机到交换机:MLAG, 堆叠技术 ↩
  3. 数据中心网络高可用技术之从服务器到网关:首跳冗余协议 VRRP ↩
  4. VLAN Trunking Protocol ↩
  5. 特殊的 ARP 用法:Gratuitous ARP, ARP Probe 和 ARP Announce ↩
  6. 数据中心网络高可用技术之从服务器到交换机:链路聚合 (balance-xor, balance-rr, broadcast) ↩
  7. https://elixir.bootlin.com/linux/v6.12.6/source/net/core/dev.c#L5457 ↩
  8. https://elixir.bootlin.com/linux/v6.12.6/source/net/8021q/vlan_core.c#L10 ↩
🔲 ⭐

特殊的 ARP 用法:Gratuitous ARP, ARP Probe 和 ARP Announce

在 Ethernet 环境中,所有的数据最终都要以二层 Ethernet Frame 的形式发送,要添加 Src MAC, Dst MAC, 以及其他的 header,比如 CRC 等,Ethernet 就会把数据送达到目的地。

我们在编程的时候经常指定 IP 和 Port,但是几乎从没有指定过 MAC 地址,那么 Frame 是怎么发出去的呢?这是操作系统帮我做了这件事。如果系统不知道一个 IP 对应的 MAC 地址是什么,系统会发送一个广播的 ARP 请求,(由于这个请求也是要通过 Ethernet 发送出去的,所以也要填充 Dst MAC,Dst MAC 就是 ff:ff:ff:ff:ff:ff。)询问谁有目的 IP,如果有,请回复给 MAC-B。广播域中所有的 Host 都会收到这个请求,只有拥有这个 IP 的 Host 才会回复给 MAC-B 自己的 MAC 地址,这样,B 就知道了这个 IP 对应的 MAC 地址。

这就是普通的 ARP request 和 response 的过程。

其中,里面的几个值得注意的 field 如下:

对于 ARP request 来说:

  • Operation: 1 (1表示 request, 2 表示 response);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:是要询问的 IP;

对于 ARP Response 来说:

  • Operation: 2
  • Src MAC: sender(sender 在这里是响应者) 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:receiver (在这里是 ARP 问题的发出者)的 MAC;
  • Dst IP:receiver IP;

ARP 是一个很简单的协议,但是人们基于交换机和主机的工作原理,设计了其他的用法。这些用法其实并不复杂,最重要的就是理解这些设备最基本的工作原理,这样就可以理解这些特殊的 ARP 设计意图了。

Gratuitous ARP

在之前的博客中介绍过两次使用 Gratuitous ARP 的例子。Gratuitous ARP 的目的是更新其他设备的 ARP cache,或者交换机 mac-address table。

在 VRRP1 中,目的主要是切换网关,更新的对象是 mac-address table。交换机的 mac-address table 主要是通过收到的包的 Src MAC 来学习 MAC 对应的物理 port 的,所以最重要的是 Src MAC,其他的都不重要。

所以在 VRRP 中的 Gratuitous ARP 是这样的:

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:全1;

在 Linux 的 balance-alb bonding 模式2中,ARP 的目的是为了刷新其他主机的 ARP Cache,并且希望不同的主机拿到不一样的 MAC 地址,所以使用的 ARP 是 response,并且是 unicast。

  • Operation: 2 (response);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:bond driver 记录的 MAC;
  • Dst IP:bond driver 记录的 IP;

取决于使用场景,Gratuitous ARP 也是 broadcast 的 reply,将 response 信息广播给所有的主机3

ARP Probe

ARP Probe 的作用是,在分配到一个 IP 之后,但是在 IP 使用之前,可以先用 ARP 协议来检查一下当前的 LAN 内有没有人在使用这个 IP。

检查的方式就是用 ARP 询问这个 IP 的 MAC 地址,如果有主机正在使用,那么就会收到 ARP reply。

但是这里有一个问题。一个主机如果收到了 ARP request,询问谁有 IP X?请发送回复给 IP 地址 Y at MAC 地址 Z。即使这个主机没有 IP X,也不会单纯丢弃这个 ARP request,而是会从这个 ARP request 中学习到,IP Y 对应 MAC Z,会将它放到自己的 ARP cache 中。

这里我们只是想检查一个 IP 是否正在被使用,我们还没有真正地开始使用这个 IP。如果用普通的 ARP 请求来询问,假设这个 IP 已经被使用,那么发出去的 ARP request 就会传达错误的信息,其他主机就会根据这个 ARP request 来更新自己的 ARP cache。

为了解决这个问题,ARP Probe 使用的 request 将 Src IP 设置为 0.0.0.0,这样,这个 ARP request 就完全无害了。

ARP 请求如下(加粗部分是和普通的 ARP request 不一样的部分)。

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: 0.0.0.0
  • Dst MAC:全1,即广播给所有主机;
  • 询问的 IP:自己将要使用的 IP;

ARP Announce

ARP Announce 用于在 ARP Probe 确定没有问题之后,决定使用这个 IP,但是再一次确定唯一性。

ARP Announce 发送一个普通的 ARP request, 在 LAN 内询问自己将要使用的 IP,正常情况下不会收到任何回复,因为这个 IP 是自己的。异常情况下收到回复,那么说明 LAN 已经有人在使用这个 IP 了,需要终止继续使用这个 IP。

ARP Announce 是普通的 ARP 请求,和 Gratuitous ARP 很像。询问的目标 IP 是自己的 IP。

ARP Announce 又叫做 Unsolictied ARP。

ARP Announce 和 ARP Probe 的唯一区别就是 Src IP 不同,ARP Announce 使用自己的 IP 来发送,所以也会更新 LAN 内其他设备的 ARP cache。

  • Operation: 1 (request);
  • Src MAC: sender 的 MAC;
  • Src IP: sender 的 IP;
  • Dst MAC:全1,即广播给所有主机;
  • Dst IP:自己即将使用的 IP;

参考资料:

  1. 首跳冗余协议 VRRP ↩
  2. 数据中心网络高可用技术之从服务器到交换机:balance-tlb 和 balance-alb ↩
  3. Gratuitous ARP https://www.practicalnetworking.net/series/arp/gratuitous-arp/ ↩
❌