plantegg

java tcp mysql performance network docker Linux

就是要你懂网络–一个网络包的旅程


写在最前面的

我相信你脑子里关于网络的概念都在下面这张图上,但是乱成一团麻,这就是因为知识没有贯通、没有实践、没有组织

image.png

上面的概念在RFC1180中讲的无比的通熟易懂和连贯,但是抱歉,当时你也许看懂了,但是一个月后又忘记了,或者碰到问题才发现之前即使觉得看懂了的东西实际没懂,我发现大多人看 RFC1180、教科书基本当时都能看到,但就是一到实践就不会了,这里的鸿沟我分析应该就是缺少实践认知。

所以这篇文章希望解决书本知识到实践的贯通,希望把网络概念之间的联系通过实践来组织起来

从一个网络ping不通的问题开始

当时的网络链路是(大概是这样,略有简化):

容器1->容器1所在物理机1->交换机->物理机2
  • 从容器1 ping 物理机2 不通;
  • 从物理机1上的容器2 ping物理机2 通;
  • 物理机用一个vlan,容器用另外一个vlan
  • 交换机都做了trunk,让两个vlan都允许通过(肯定没问题,因为容器2是通的)
  • 同时发现即使是通的,有的容器 ping物理机1只需要0.1ms,有的容器需要200ms以上(都在同一个交换机下),不合理
  • 所有容器 ping 其它外网IP反而是通的

这个问题扯了一周是因为容器的网络是我们自己配置的,交换机我们没有权限接触,由客户配置。出问题的时候都会觉得自己没问题对方有问题,另外就是对网络基本知识认识不够所以都觉得自己没问题。

这个问题的答案在大家看完本文的基础知识后会总结出来。

开始前大家先想想,假如有个面试题是:输入 ping IP后 敲回车,然后发生了什么?

route 路由表

$route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric RefUse Iface
0.0.0.0     10.125.15.254   0.0.0.0 UG0  00 eth0
10.0.0.0    10.125.15.254   255.0.0.0   UG0  00 eth0
10.125.0.0  0.0.0.0 255.255.240.0   U 0  00 eth0
11.0.0.0    10.125.15.254   255.0.0.0   UG0  00 eth0
30.0.0.0    10.125.15.254   255.0.0.0   UG0  00 eth0
100.64.0.0  10.125.15.254   255.192.0.0 UG0  00 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002   00 eth0
172.16.0.0  10.125.15.254   255.240.0.0 UG0  00 eth0
172.17.0.0  0.0.0.0 255.255.0.0 U 0  00 docker0
192.168.0.0 10.125.15.254   255.255.0.0 UG0  00 eth0

假如你现在在这台机器上ping 172.17.0.2 根据上面的route表得出 172.17.0.2这个IP匹配到下面这条路由:

172.17.0.0  0.0.0.0 255.255.0.0 U 0  00 docker0

那么ping 包会从docker0这张网卡发出去。

但是如果是ping 10.125.4.4 根据路由规则应该走eth0这张网卡。

也就是:route/路由表 来帮我们匹配目标地址(一个目标地址只能匹配一条路由,匹配不到就报no route to host 错误)

现在根据路由我们已经知道目标ip将要走哪个网卡出去,接下来就要判断目标IP是否在同一个子网了

ifconfig

首先来看看这台机器的网卡情况:

$ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    inet 172.17.42.1  netmask 255.255.0.0  broadcast 0.0.0.0
    ether 02:42:49:a7:dc:ba  txqueuelen 0  (Ethernet)
    RX packets 461259  bytes 126800808 (120.9 MiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 462820  bytes 103470899 (98.6 MiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 10.125.3.33  netmask 255.255.240.0  broadcast 10.125.15.255
    ether 00:16:3e:00:02:67  txqueuelen 1000  (Ethernet)
    RX packets 280918095  bytes 89102074868 (82.9 GiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 333504217  bytes 96311277198 (89.6 GiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
    inet 127.0.0.1  netmask 255.0.0.0
    loop  txqueuelen 0  (Local Loopback)
    RX packets 1077128597  bytes 104915529133 (97.7 GiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 1077128597  bytes 104915529133 (97.7 GiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

这里有三个IP,三个子网掩码(netmask),根据目标路由走哪张网卡,得到这个网卡的子网掩码,来计算目标IP是否在这个子网内。

如果目标ip在子网内,那就是大家说的在同一个二层网络,直连可以通;如果目标ip和本机不在同一个子网那么本机只管将网络包发给本机网关,剩下的由网关按照以上逻辑不停地往外走直到发送给目标机器(也就是网关拿到这个包后先查自己的路由,然后按照路由扔给下一跳)

直连可通的意思是:本机发广播包对方能收到,这个时候就要来到ARP 广播找对方机器的Mac地址了(如果不是同一个二层,就是转发给网关,那么这里同样也是ARP 广播找网关机器的Mac–本机和网关一定在同一个子网)

ARP协议

网络包在物理层传输的时候依赖的mac 地址而不是上面目的的IP地址,也就是根据mac地址来决定把包发到哪里去。

ARP 协议就是查询某个IP地址的mac地址是多少,由于这种对应关系一般不太变化,所以每个os都有一份arp缓存(一般15分钟过期),也可以手工清理,下面是arp缓存的内容:

$arp -a
e010125011202.bja.tbsite.net (10.125.11.202) at 00:16:3e:01:c2:00 [ether] on eth0
? (10.125.15.254) at 0c:da:41:6e:23:00 [ether] on eth0
v125004187.bja.tbsite.net (10.125.4.187) at 00:16:3e:01:cb:00 [ether] on eth0
e010125001224.bja.tbsite.net (10.125.1.224) at 00:16:3e:01:64:00 [ether] on eth0
v125009121.bja.tbsite.net (10.125.9.121) at 00:16:3e:01:b8:ff [ether] on eth0
e010125009114.bja.tbsite.net (10.125.9.114) at 00:16:3e:01:7c:00 [ether] on eth0
v125012028.bja.tbsite.net (10.125.12.28) at 00:16:3e:00:fb:ff [ether] on eth0
e010125005234.bja.tbsite.net (10.125.5.234) at 00:16:3e:01:ee:00 [ether] on eth0

进入正题,ping后回车后发生什么

首先 OS需要把ping命令封成一个icmp包,需要填上包头(包括IP、mac地址),那么OS先根据目标IP和本机的route规则计算使用哪个interface(网卡),每条路由规则基本都包含目标IP范围、网关、网卡这样几个基本元素。

如果目标IP在同一子网

如果目标IP和本机IP是同一个子网(根据本机ifconfig上的每个网卡的netmask来判断),并且本机arp缓存没有这条IP对应的mac记录,那么给整个子网的所有机器广播发送一个 arp查询

比如我ping 10.125.3.42,然后tcpdump抓包看到的arp请求:

$sudo tcpdump -i eth0  arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
16:22:01.792501 ARP, Request who-has e010125003042.bja.tbsite.net tell e010125003033.bja, length 28
16:22:01.792566 ARP, Reply e010125003042.bja.tbsite.net is-at 00:16:3e:01:8d:ff (oui Unknown), length 28

Host to Host through a Switch - Switch Functions animation

上面就是本机发送广播消息,10.125.3.42的mac地址是多少,很快10.125.3.42回复了自己的mac地址。
收到这个回复后,先缓存起来,下个ping包就不需要再次arp广播了。
然后将这个mac地址填写到ping包的包头的目标Mac(icmp包),然后发出这个icmp request包,同一个子网,按照MAC地址,正确到达目标机器,然后对方正确回复icmp reply【对方回复也要查路由规则,arp查发送放的mac,这样回包才能正确路由回来,略过】。

来看一次完整的ping 10.125.3.43,tcpdump抓包结果:

$sudo tcpdump -i eth0  arp or icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
16:25:15.195401 ARP, Request who-has e010125003043.bja.tbsite.net tell e010125003033.bja, length 28
16:25:15.195459 ARP, Reply e010125003043.bja.tbsite.net is-at 00:16:3e:01:0c:ff (oui Unknown), length 28
16:25:15.211505 IP e010125003033.bja > e010125003043.bja.tbsite.net: ICMP echo request, id 27990, seq 1, length 64
16:25:15.212056 IP e010125003043.bja.tbsite.net > e010125003033.bja: ICMP echo reply, id 27990, seq 1, length 64

接着再 ping 一次同一个IP地址,arp有缓存了就看不到arp广播查询过程了。

如果目标IP不是同一个子网

arp只是同一子网广播查询,如果目标IP不是同一子网的话就要经过本IP网关就行转发,如果本机没有缓存网关mac(一般肯定缓存了),那么先发送一次arp查询网关的mac,然后流程跟上面一样,只是这个icmp包发到网关上去了(mac地址填写的是网关的mac)

从本机10.125.3.33 ping 11.239.161.60的过程,因为不是同一子网按照路由规则匹配,根据route表应该走10.125.15.254这个网关,如下截图:

image.png

首先是目标IP 11.239.161.60 符合最上面红框中的路由规则,又不是同一子网,所以查找路由规则中的网关10.125.15.254的Mac地址,arp cache中有,于是将 0c:da:41:6e:23:00 填入包头,那么这个icmp request包就发到10.125.15.254上了,虽然包头的mac是 0c:da:41:6e:23:00,但是IP还是 11.239.161.60.

看看目标IP 11.239.161.60 的真正mac信息(跟ping包包头的Mac是不同的):

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 11.239.161.60  netmask 255.255.252.0  broadcast 11.239.163.255
    ether 00:16:3e:00:04:c4  txqueuelen 1000  (Ethernet)

这个包根据Mac地址路由到了网关上

网关接下来怎么办

为了简化问题,假设两个网关直连

网关收到这个包后(因为mac地址是她的),打开一看IP地址是 11.239.161.60,不是自己的,于是继续查自己的route和arp缓存,发现11.239.161.60这个IP的网关是11.239.163.247,于是把包的目的mac地址改成11.239.163.247的mac继续发出去。

11.239.163.247这个网关收到包后,一看 11.239.161.60是自己同一子网的IP,于是该arp广播找mac就广播,cache有就拿cache的,然后这个包才最终到达目的11.239.161.60上。

整个过程中目标mac地址每一跳都在变,IP地址不变,每经过一次变化可以简单理解从一跳。

实际上可能要经过多个网关多次跳跃才能真正到达目标机器

目标收到这个icmp包后的回复过程一样,略过。

arp广播风暴和arp欺骗

广播风暴:如果一个子网非常大,机器非常多,每次arp查询都是广播的话,也容易因为N*N的问题导致广播风暴。

arp欺骗:同样如果一个子网中的某台机器冒充网关或者其他机器,当收到arp查询的时候总是把自己的mac冒充目标机器的mac发给你,然后你的包先走到他,为了不被发现达到自己的目的后再转发给真正的网关或者机器,所以在里面都点什么手脚,看看你发送的内容都还是很容易的

讲完基础再来看开篇问题的答案

分别在两个物理机上抓包

在物理机2上抓包:

image.png

tcpdump: listening on em1, link-type EN10MB (Ethernet), capture size 65535 bytes
f4:0f:1b:ae:15:fb > 18:66:da:f0:15:90, ethertype 802.1Q (0x8100), length 102: vlan 134, p 0, ethertype IPv4, (tos 0x0, ttl 63, id 5654, offset 0, flags [DF], proto ICMP (1), length 84)
10.159.43.162 > 10.159.43.1: ICMP echo request, id 6285, seq 1, length 64
18:66:da:f0:15:90 > 00:00:0c:9f:f0:86, ethertype 802.1Q (0x8100), length 102: vlan 134, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 21395, offset 0, flags [none], proto ICMP (1), length 84)
10.159.43.1 > 10.159.43.162: ICMP echo reply, id 6285, seq 1, length 64

这个抓包能看到核心证据,ping包有到达物理机2,同时物理机2也正确回复了(mac、ip都对)

同时在物理机1上抓包只能看到ping包出去,回包没有到物理机1(所以回包肯定不会到容器里了)

所以问题的核心在交换机没有正确把物理机2的回包送到物理机1上面。

同时观察到的不正常延时:
image.png

过程中的其它测试:

  1. 新拿出一台物理机配置上不通的容器的IP,这是通的,所以客户坚持是容器网络的配置;
  2. 怀疑不通的IP所使用的mac地址冲突,在交换机上清理了交换机的arp缓存,没有帮助,还是不通

对于1能通,我认为这个测试不严格,新物理机所用的mac不一样,并且所接的交换机口也不一样,影响了测试结果。

最终的原因

最后在交换机上分析包没正确发到物理机1上的原因跟客户交换机使用了HSRP(热备份路由器协议,就是多个交换机HA高可用,也就是同一子网可以有多个网关的IP),停掉HSRP后所有IP容器都能通了,并且前面的某些容器延时也恢复正常了。

通俗点说就是HSRP把回包拐跑了,有些回包拐跑了又送回来了(延时200ms那些)

至于HSRP为什么会这么做,要厂家出来解释了。

大概结构如下图:

undefined

关于HSRP和VRRP

VRRP是虚拟路由冗余协议的简称,这个协议的目的是为了让多台路由器共同组成一个虚拟路由器,从而解决单点故障。

使用VRRP的网络架构大致如上面这个图所示,其中Master和Slave共同组成了一个虚拟路由器,这台虚拟路由器的IP是1.1.1.1,同时还会有一个虚拟的mac地址,所有主机的默认网关IP都将设置成1.1.1.1。

假设主机H1需要对外发送数据,在发送IP数据包时主机H1需要知道1.1.1.1这个IP对应的物理地址,因此H1会向外广播一个ARP请求,询问1.1.1.1这个IP数据包对应的物理地址。此时,Master将会负责响应这个APR请求,将虚拟的mac地址报告给主机H1,主机H1就用这个物理地址发送IP数据包。

当IP数据包到达交换机Switch A的时候,Switch A需要知道应该把这个数据包转发到哪条链路去,这个时候Switch A也会广播一个ARP请求,看看哪条链路会响应这个ARP请求。同样,Master会响应这个ARP请求,从而Switch A就知道了应该把数据包从自己的eth0对应的这条链路转发出去。此时,Master就是真正负责整个网络对外通信的路由器。

当Master出现故障的时候,通过VRRP协议,Slave可以感知到这个故障(通过类似于心跳的方式),这个时候Slave会主动广播一个ARP消息,告诉Switch A应该从eth1对应的链路转发物理地址是虚拟mac地址的数据包。这样就完成了主备路由器的切换,这个过程对网络中的主机来说是透明的。

通过VRRP不仅可以实现1主1备的部署,还可以实现1主多备的部署。在1主多备的部署结构下,当Master路由器出现故障,多个Backup路由器会通过选举的方式产生一个新的Master路由器,由这个Master路由器来响应ARP请求。

除了利用VRRP屏蔽单点故障之外,还可以实现负载均衡。在主备部署的情况下,Backup路由器其实是空转的,并不负责数据包的路由工作,这样显然是有点浪费的。此时,为了让Backup也负责一部分的路由工作,可以将两台路由器配制成互为主备的模式,这样就形成了两台虚拟路由器,网络中的主机可以选择任意一台作为默认网关。这种互为主备的模式也可以应用到1主多备的部署方式下。比如由3台路由器,分别是R1,R2和R3,用这3台路由器可以组成3台虚拟路由器,一台虚拟路由器以R1为Master,R2和R3为Backup路由器,另外一台以R2为Master,R1和R3为Backup路由器,第三台则以R3为Master,R1和R2为Backup路由器。

通过VRRP,可以实现LVS的主备部署,屏蔽LVS单点故障对应用服务器的影响。

网络到底通不通是个复杂的问题

讲这个过程的核心目的是除了真正的网络不通,有些是服务不可用了也怪网络。很多现场的同学根本讲不清自己的服务(比如80端口上的tomcat服务)还在不在,网络通不通,网络不通的话该怎么办?

实际这里涉及到四个节点(以两个网关直连为例),srcIP -> src网关 -> dest网关 -> destIP.如果ping不通(也有特殊的防火墙限制ping包不让过的),那么分段ping(二分查找程序员应该最熟悉了)。 比如前面的例子就是网关没有把包转发回来

抓包看ping包有没有出去,对方抓包看有没有收到,收到后有没有回复。

ping自己网关能不能通,ping对方网关能不能通

接下来说点跟程序员日常相关的

如果网络能ping通,服务无法访问

那么尝试telnet IP port 看看你的服务监听的端口是否还在,在的话是否能正常响应新的连接。有时候是进程挂掉了,端口也没人监听了。有时候是进程还在但是死掉了,所以端口也不响应新的请求了。

如果端口还在也是正常的话,telnet应该是好的:

$telnet 11.239.161.60 2376
Trying 11.239.161.60...
Connected to 11.239.161.60.
Escape character is '^]'.
^C
Connection closed by foreign host.

假如我故意换成一个不存在的端口,目标机器上的OS直接就拒绝了这个连接(抓包的话一般是看到reset标识):

$telnet 11.239.161.60 2379
Trying 11.239.161.60...
telnet: connect to address 11.239.161.60: Connection refused

一个服务不响应,然后首先怀疑网络不通、丢包的Case

当时的反馈应用代码抛SocketTimeoutException,怀疑网络问题:

  1. tsar检查,发现retran率特别高,docker容器(tlog-console)内达到50,物理机之间的retran在1-2之间。
  2. Tlog连接Hbase,出现大量连接断开,具体日志见附件,Hbase服务器完全正常,Hbase同学怀疑retran比较高导致。
  3. 业务应用连接Diamond 偶尔会出现超时异常,具体日志见附件。
  4. 业务很多这样的异常日志:[Diamond SocketTimeoutException]
  5. 有几台物理机io偶然情况下会飙升到80多。需要定位解决。

其实当时看到tsar监控retran比较高,我也觉得网络有问题,但是我去看的时候网络又非常好,于是我看了一下出问题时间段的网卡的流量信息也非常正常:

image.png

上图是通过sar监控到的9号 10.16.11.138(v24d9e0f23d40) 这个网卡的流量,看起来也是正常,流量没有出现明显的波动(10.16.11.138 出问题容器对应的网卡名:v24d9e0f23d40)

为了监控网络到底有没有问题,接着在出问题的两个容器上各启动一个http server,然后在对方每1秒钟互相发一次发http get请求,基本认识告诉我们如果网络丢包、卡顿,那么我这个http server的监控日志时间戳也会跳跃,如果应用是因为网络出现异常那么我启动的http服务也会出现异常。

实际监控来看,应用出异常的时候我的http服务是正常的(写了脚本判断日志的连续性,没问题):

image.png

这也强有力地证明了网络没问题,所以大家集中火力查看应用的问题。后来的实际调查发现是应用假死掉了(内部线程太多,卡死了),服务端口不响应请求了。

TCP建连接过程跟前面ping一样,只是把ping的icmp协议换成TCP协议,也是要先根据route,然后arp。

总结

网络丢包,卡顿,抖动很容易做背包侠,找到正确的原因解决问题才会更快,要不在错误的路径上怎么发力都不对。准的方向要靠好的基础知识和正确的逻辑以及证据来支撑,而不是猜测

  • 有重传的时候(或者说重传率高的时候),ping有可能是正常的(icmp包网卡直接返回);
  • 重传高,一般是tcp retrans,可能应用不响应,可能操作系统软中断太高等
  • ping只是保证网络链路是否通畅

这些原理基本都在RFC1180中阐述的清晰简洁,图文并茂,结构逻辑合理,但是对于90%的程序员没有什么卵用,因为看完几周后就忘得差不多。对于普通人来说还是要通过具体的案例来加深理解。

一流的人看RFC就够了,差一些的人看《TCP/IP卷1》,再差些的人要看一个个案例带出来的具体知识的书籍了,比如《wireshark抓包艺术》,人和人的学习能力有差别必须要承认。


参考文章:

https://tools.ietf.org/html/rfc1180

https://www.practicalnetworking.net/series/packet-traveling/packet-traveling/

Computer Networking Introduction - Ethernet and IP (Heavily Illustrated) 这篇凑合吧,其实没我这篇写得好,不过这个博客还有些别的文章也不错

netstat定位性能案例

netstat 和 ss 都是小工具,但是在网络性能、异常的窥探方面真的是神器。ss用法见这里

下面的案例通过netstat很快就发现为什么系统总是压不上去了(主要是快速定位到一个长链条的服务调用体系中哪个节点碰到瓶颈了)

netstat 命令

netstat跟ss命令一样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes;而 Send 则是发送队列中没有被远程主机确认的 bytes 数

$netstat -tn  
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address   Foreign Address State  
tcp0  0 server:8182  client-1:15260 SYN_RECV   
tcp0 28 server:22    client-1:51708  ESTABLISHED
tcp0  0 server:2376  client-1:60269 ESTABLISHED

netstat -tn 看到的 Recv-Q 跟全连接半连接没有关系,这里特意拿出来说一下是因为容易跟 ss -lnt 的 Recv-Q 搞混淆。

Recv-Q 和 Send-Q 的说明

Recv-Q
Established: The count of bytes not copied by the user program connected to this socket.
Listening: Since Kernel 2.6.18 this column contains the current syn backlog.

Send-Q
Established: The count of bytes not acknowledged by the remote host.
Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.

通过 netstat 发现问题的案例

自身太慢,比如如下netstat -t 看到的Recv-Q有大量数据堆积,那么一般是CPU处理不过来导致的:

image.png

下面的case是接收方太慢,从应用机器的netstat统计来看,也是client端回复太慢(本机listen 9108端口)

image.png

send-q表示回复从9108发走了,没收到对方的ack,基本可以推断client端到9108之间有瓶颈

实际确实是前端到9108之间的带宽被打满了,调整带宽后问题解决

netstat -s 统计数据

所有统计信息基本都有

Linux Module and make debug

Makefile 中的 tab 键

$sudo make
Makefile:4: *** missing separator.  Stop.

Makefile 中每个指令前面必须是tab(不能是4个空格)!

pwd

$sudo make
make -C /lib/modules/4.19.48-002.ali4000.test.alios7.x86_64/build M= modules
make[1]: Entering directory `/usr/src/kernels/4.19.48-002.ali4000.test.alios7.x86_64'
make[2]: *** No rule to make target `arch/x86/entry/syscalls/syscall_32.tbl', needed by `arch/x86/include/generated/asm/syscalls_32.h'.  Stop.
make[1]: *** [archheaders] Error 2
make[1]: Leaving directory `/usr/src/kernels/4.19.48-002.ali4000.test.alios7.x86_64'
make: *** [all] Error 2

Makefile中的:
make -C /lib/modules/$(shell uname -r)/build M=$(pwd) modules

$(pwd) 需要修改成:$(shell pwd)

makefile调试的法宝

makefile调试的法宝1

$ make --debug=a,m SHELL="bash -x" > make.log  2>&1                # 可以获取make过程最完整debug信息
$ make --debug=v,m SHELL="bash -x" > make.log  2>&1                # 一个相对精简版,推荐使用这个命令
$ make --debug=v  > make.log  2>&1                                 # 再精简一点的版本
$ make --debug=b  > make.log  2>&1                                 # 最精简的版本

推荐版本(会输出执行的具体命令):
make --debug=b SHELL="bash -x"  > make.log.simple  2>&1
or
make V=1

makefile调试的法宝2

上面的法宝1更多的还是在整体工程的makefile结构、makefile读取和makefile内部的rule之间的关系方面有很好的帮助作用。但是对于makefile中rule部分之前的变量部分的引用过程则表现的不是很充分。在这里,我们有另外一个法宝,可以把变量部分的引用过程给出一个比较好的调试信息。具体命令如下。

$ make -p 2>&1 | grep -A 1 '^# makefile' | grep -v '^--' | awk '/# makefile/&&/line/{getline n;print $0,";",n}' | LC_COLLATE=C sort -k 4 -k 6n > variable.log
$ cat variable.log
# makefile (from `Makefile', line 1) ; aa := 11
# makefile (from `Makefile', line 3) ; cc := 11
# makefile (from `Makefile', line 4) ; bb := 9999
# makefile (from `cfg_makefile', line 1) ; MAKEFILE_LIST :=  Makefile cfg_makefile
# makefile (from `cfg_makefile', line 1) ; xx := 4444
# makefile (from `cfg_makefile', line 2) ; yy := 4444
# makefile (from `cfg_makefile', line 3) ; zz := 4444
# makefile (from `sub_makefile', line 1) ; MAKEFILE_LIST :=  sub_makefile
# makefile (from `sub_makefile', line 1) ; aaaa := 222222
# makefile (from `sub_makefile', line 2) ; bbbb := 222222
# makefile (from `sub_makefile', line 3) ; cccc := 222222

makefile调试的法宝3

法宝2可以把makefile文件中每个变量的最终值清晰的展现出来,但是对于这些变量引用过程中的中间值却没有展示。此时,我们需要依赖法宝3来帮助我们。

$(warning $(var123))

很多人可能都知道这个warning语句。我们可以在makefile文件中的变量引用阶段的任何两行之间,添加这个语句打印关键变量的引用过程。

make 时ld报找不到lib

make总是报找不到libc,但实际我执行 ld -lc –verbose 从debug信息看又能够正确找到libc,debug方法

image.png

image.png

实际原因是make的时候最后有一个参数 -static,这要求得装 ***-static lib库,可以去掉 -static

依赖错误

编译报错缺少的组件需要yum install一下(bison/flex)

hping3

构造半连接:

sudo hping3 -i u100 -S -p 3306 10.0.186.79

tcp sk_state

enum {
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING,    /* Now a valid state */

    TCP_MAX_STATES  /* Leave at the end! */
};

kdump

启动kdump(kexec-tools), 系统崩溃的时候dump 内核(/var/crash)

sudo systemctl start kdump

参考:Linux 系统内核崩溃分析处理简介

Kdump 的概念出现在 2005 左右,是迄今为止最可靠的内核转存机制,已经被主要的 linux™ 厂商选用。kdump是一种先进的基于 kexec 的内核崩溃转储机制。当系统崩溃时,kdump 使用 kexec 启动到第二个内核。第二个内核通常叫做捕获内核,以很小的内存启动以捕获转储镜像。

第一个内核保留了内存的一部分给第二个内核启动用。由于 kdump 利用 kexec 启动捕获内核,绕过了 BIOS,所以第一个内核的内存得以保留。这是内核崩溃转储的本质。

kdump 需要两个不同目的的内核,生产内核和捕获内核。生产内核是捕获内核服务的对象。捕获内核会在生产内核崩溃时启动起来,与相应的 ramdisk 一起组建一个微环境,用以对生产内核下的内存进行收集和转存。

什么是 kexec ?

Kexec 是实现 kdump 机制的关键,它包括 2 一是组成部分:一是内核空间的系统调用 kexec_load,负责在生产内核(production kernel 或 first kernel)启动时将捕获内核(capture kernel 或 sencond kernel)加载到指定地址。二是用户空间的工具 kexec-tools,他将捕获内核的地址传递给生产内核,从而在系统崩溃的时候能够找到捕获内核的地址并运行。

没有 kexec 就没有 kdump。先有 kexec 实现了在一个内核中可以启动另一个内核,才让 kdump 有了用武之地。kexec 原来的目的是为了节省时间 kernel 开发人员重启系统的时间,谁能想到这个“偷懒”的技术却孕育了最成功的内存转存机制呢?

crash

sudo yum install crash -y
//手动触发crash
#echo 1 > /proc/sys/kernel/sysrq
#echo c > /proc/sysrq-trigger
//系统crash,然后重启,重启后分析:
sudo crash /usr/lib/debug/lib/modules/4.19.57-15.1.al7.x86_64/vmlinux /var/crash/127.0.0.1-2020-04-02-14\:40\:45/vmcore

可以触发dump但是系统没有crash, 以下两个命令都可以

1
2
3
4
5
sudo crash /usr/lib/debug/usr/lib/modules/4.19.91-19.1.al7.x86_64/vmlinux /proc/kcore
sudo crash /usr/lib/debug/usr/lib/modules/4.19.91-19.1.al7.x86_64/vmlinux /dev/mem

写内存hack内核,那就在crash命令执行前,先执行下面的命令:
stap -g -e 'probe kernel.function("devmem_is_allowed").return { $return = 1 }'

内核函数替换

image.png

static int __init hotfix_init(void)
{
  unsigned char e8_call[POKE_LENGTH];
  s32 offset, i;

  addr = (void *)kallsyms_lookup_name("tcp_reset");
  if (!addr) {
    printk("一切还没有准备好!请先加载tcp_reset模块。\n");
    return -1;
  }

  _text_poke_smp = (void *)kallsyms_lookup_name("text_poke");
  _text_mutex = (void *)kallsyms_lookup_name("text_mutex");

  stub = (void *)test_stub1;

  offset = (s32)((long)stub - (long)addr - FTRACE_SIZE);

  e8_call[0] = 0xe8;
  (*(s32 *)(&e8_call[1])) = offset;
  for (i = 5; i < POKE_LENGTH; i++) {
    e8_call[i] = 0x90;
  }
  get_online_cpus();
  mutex_lock(_text_mutex);
  _text_poke_smp(&addr[POKE_OFFSET], e8_call, POKE_LENGTH);
  mutex_unlock(_text_mutex);
  put_online_cpus();

  return 0;
}

void test_stub1(void)
{
  struct sock *sk = NULL;
  unsigned long sk_addr = 0;
  char buf[MAX_BUF_SIZE];
  int size=0;
  asm ("push %rdi");

  asm ( "mov %%rdi, %0;" :"=m"(sk_addr) : :);
  sk = (struct sock *)sk_addr;

  printk("aaaaaaaa yes :%d  dest:%X  source:%X\n",
      sk->sk_state,
      sk->sk_rcv_saddr,
      sk->sk_daddr);
/*
  size = snprintf(buf, MAX_BUF_SIZE-1, "rst %lu %d %pI4:%u->%pI4:%u \n",
                     get_seconds(),
                     sk->sk_state,
                     &(inet_sk(sk)->inet_saddr),
                     ntohs(inet_sk(sk)->inet_sport),
                     ntohs(inet_sk(sk)->inet_dport),
                     &(inet_sk(sk)->inet_daddr));
*/
//  tcp_rt_log_output(buf,size,1);

  asm ("pop %rdi");
}

参考文档

https://blog.sourcerer.io/writing-a-simple-linux-kernel-module-d9dc3762c234

https://stackoverflow.com/questions/16710047/usr-bin-ld-cannot-find-lnameofthelibrary

Linux系统中如何彻底隐藏一个TCP连接

定制 Linux Kernel

Linux 里面有一个工具,叫 Grub2,全称 Grand Unified Bootloader Version 2。顾名思义,就是搞系统启动的。

修改启动参数

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
$cat change_kernel_parameter.sh 
#cat /sys/devices/system/cpu/vulnerabilities/*
#grep '' /sys/devices/system/cpu/vulnerabilities/*
#https://help.aliyun.com/document_detail/102087.html?spm=a2c4g.11186623.6.721.4a732223pEfyNC

#cat /sys/kernel/mm/transparent_hugepage/enabled
#transparent_hugepage=always
#noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off
#追加nopti nospectre_v2到内核启动参数中
sudo sed -i 's/\(GRUB_CMDLINE_LINUX=".*\)"/\1 nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off transparent_hugepage=always"/' /etc/default/grub

//从修改的 /etc/default/grub 生成 /boot/grub2/grub.cfg 配置
//如果是uefi引导,则是 /boot/efi/EFI/redhat/grub.cfg
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

#limit the journald log to 500M
sed -i 's/^#SystemMaxUse=$/SystemMaxUse=500M/g' /etc/systemd/journald.conf
#重启系统
#sudo reboot

## 选择不同的kernel启动
#sudo grep "menuentry " /boot/grub2/grub.cfg | grep -n menu
##grub认的index从0开始数的
#sudo grub2-reboot 0; sudo reboot
or
#grub2-set-default "CentOS Linux (3.10.0-1160.66.1.el7.x86_64) 7 (Core)" ; sudo reboot

GRUB 2 reads its configuration from the /boot/grub2/grub.cfg file on traditional BIOS-based machines and from the /boot/efi/EFI/redhat/grub.cfg file on UEFI machines. This file contains menu information.

The GRUB 2 configuration file, grub.cfg, is generated during installation, or by invoking the /usr/sbin/grub2-mkconfig utility, and is automatically updated by grubby each time a new kernel is installed. When regenerated manually using grub2-mkconfig, the file is generated according to the template files located in /etc/grub.d/, and custom settings in the /etc/default/grub file. Edits of grub.cfg will be lost any time grub2-mkconfig is used to regenerate the file, so care must be taken to reflect any manual changes in /etc/default/grub as well.

查看kernel编译参数

一般在 /boot/config-** 文件内放置所有内核编译参数

1
2
3
4
5
6
7
//启用 tcp_rt 模块
cat /boot/config-4.19.91-24.8.an8.x86_64 |grep TCP_RT
CONFIG_TCP_RT=y

//启用 RPS
cat /boot/config-4.19.91-24.8.an8.x86_64 |grep RPS
CONFIG_RPS=y

修改是否启用透明大页

1
2
$cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

制作启动盘

Windows 上用 UltraISO、rufus 烧制,macOS 上就比较简单了,直接用 dd 就可以做好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ diskutil list
/dev/disk6 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: *31.5 GB disk6

# 找到 U 盘的那个设备,umount
$ diskutil unmountDisk /dev/disk3

# 用 dd 把 ISO 文件写进设备,注意这里是 rdisk3 而不是 disk3,在 BSD 中 r(IDENTIFIER)
# 代表了 raw device,会快很多
$ sudo dd if=/path/image.iso of=/dev/rdisk3 bs=1m

# 弹出 U 盘
$ sudo diskutil eject /dev/disk3

Linux 下制作步骤

1
2
3
umount /dev/sdn1
sudo mkfs.vfat /dev/sdn1
dd if=/data/uniontechos-server-20-1040d-amd64.iso of=/dev/sdn1 status=progress

iommu passthrough

在内核参数中加上iommu.passthrough=1 可以关闭iommu,iommu.strict=0是nostrict模式,iommu.strict=1是strict模式(这种性能较差),也是默认的模式。Strict和nostrict主要是处理 无效TLB中缓存的页表项 的方法不同, 一种是批量处理, 一种是一次处理一个。

在X86中加 intel_iommu=off 去关闭的。

IOMMU 硬件单元

DMA Remapping Feature 的工作是通过 CPU 硬件平台的 IOMMU(I/O MMU,Input/Output Memory Management Unit,I/O 内存管理硬件单元)来完成的。IOMMU 的出现,实现了地址空间上的隔离,使设备只能访问规定的内存区域。

image-20220718111233654

参考资料:https://lenovopress.lenovo.com/lp1467.pdf

image-20220729162624318

1
2
3
4
5
6
7
/*
* This variable becomes 1 if iommu=pt is passed on the kernel command line.
* If this variable is 1, IOMMU implementations do no DMA translation for
* devices and allow every device to access to whole physical memory. This is
* useful if a user wants to use an IOMMU only for KVM device assignment to
* guests and not for driver dma translation.
*/

说明配置了iommu=pt 的话函数iommu_no_mapping返回1,那么驱动就直接return paddr,并不会真正调用到domain_pfn_mapping,直接用了物理地址少了一次映射性能当然会高一些。如果是跑KVM建议 passthrough=0,物理机场景 passthrough=1

iommu=pt并不会影响kvm/dpdk/spdk的性能,这三者本质上都是用户态驱动,iommu=pt只会影响内核驱动,能让内核驱动设备性能更高。

SMMU:

ChatGPT:SMMU代表的是”System MMU”,是一种硬件单元,通常用于处理设备DMA(直接内存访问)请求,以允许安全而有效地使用设备,同时保护系统内存不受意外访问和恶意攻击。SMMU的主要功能是将设备发出的DMA请求映射到正确的物理内存地址,同时确保设备无法访问不属于其权限范围的内存区域。SMMU通常与ARM和其他芯片架构一起使用,以提高系统安全性和性能。

Google: SMMU(System Memory Management Unit)是Arm平台的IOMMU, SMMU为设备提供用设备可见的IOVA地址来访问物理内存的能力,体系结构中可能存在多个设备使用IOVA经过IOMMU来访问物理内存,IOMMU需要能够区分不同的设备,从而为每个设备引入了一个Stream ID,指向对应的STE(Stream Table Entry),所有的STE在内存中以数组的形式存在,SMMU记录STE数组的首地址。在操作系统扫描设备的时候会为其分配独有的Stream ID简称sid,设备通过IOMMU进行访存的所有配置都写在对应sid的STE中。

在非虚拟化场景下使能IOMMU/SMMU会带来性能衰减,主要是因为在DMA场景下要iova 到 pa的翻译,带来开销。当前集团的ARM机型,在非云化环境下都是SMMU OFF的,云化机型才是开启SMMU。

定制内存

物理内存700多G,要求OS只能用512G:

1
2
3
4
5
6
7
8
9
10
11
12
24条32G的内存条,总内存768G
# dmidecode -t memory |grep "Size: 32 GB"
Size: 32 GB
…………
Size: 32 GB
Size: 32 GB
root@uos15:/etc# dmidecode -t memory |grep "Size: 32 GB" | wc -l
24

# cat /boot/grub/grub.cfg |grep 512
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE

高级版 按numa限制内存

每个numa 128G内存,总共1024G(32条*32G),8个numa node,需要将每个numa node内存限制在64G

在grub中cmdline中加入如下配置,每个node只用64G内存:

1
memmap=64G\$64G memmap=64G\$192G memmap=64G\$320G memmap=64G\$448G memmap=64G\$576G memmap=64G\$704G memmap=64G\$832G memmap=64G\$960G

或者:

1
2
3
4
5
6
7
8
9
10
11
#cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=1024M,high resume=/dev/mapper/klas-swap rd.lvm.lv=klas/root rd.lvm.lv=klas/swap video=efifb:on rhgb quiet quiet noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off tsx=on tsx_async_abort=off mitigations=off iommu.passthrough=1 memmap=64G\\\$64G memmap=64G\\\$192G memmap=64G\\\$320G memmap=64G\\\$448G memmap=64G\\\$576G memmap=64G\\\$704G memmap=64G\\\$832G memmap=64G\\\$960G"
GRUB_DISABLE_RECOVERY="true"

然后执行生成最终grub启动参数
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

比如在一个4node的机器上,总共768G内存(32G*24),每个node使用64G内存

1
linux16 /vmlinuz-0-rescue-e91413f0be2c4c239b4aa0451489ae01 root=/dev/mapper/centos-root ro crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet memmap=128G\$64G memmap=128G\$256G memmap=128G\$448G memmap=128G\$640G

128G表示相对地址,$64G是绝对地址,128G\$64G 的意思是屏蔽64G到(64+128)G的地址对应的内存

检查

检查正在运行的系统使用的grub参数:

1
cat /proc/cmdline

内存信息

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
#dmidecode -t memory
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 3.2.1 present.
# SMBIOS implementations newer than version 3.2.0 are not
# fully supported by this version of dmidecode.

Handle 0x0033, DMI type 16, 23 bytes
Physical Memory Array
Location: System Board Or Motherboard
Use: System Memory
Error Correction Type: Multi-bit ECC
Maximum Capacity: 2 TB //最大支持2T
Error Information Handle: 0x0032
Number Of Devices: 32 //32个插槽

Handle 0x0041, DMI type 17, 84 bytes
Memory Device
Array Handle: 0x0033
Error Information Handle: 0x0040
Total Width: 72 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: DIMM
Set: None
Locator: CPU0_DIMMA0
Bank Locator: P0 CHANNEL A
Type: DDR4
Type Detail: Synchronous Registered (Buffered)
Speed: 2933 MT/s //内存最大频率
Manufacturer: SK Hynix
Serial Number: 220F9EC0
Asset Tag: Not Specified
Part Number: HMAA4GR7AJR8N-WM
Rank: 2
Configured Memory Speed: 2400 MT/s //内存实际运行速度--比如内存条数插太多会给内存降频
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Memory Technology: DRAM
Memory Operating Mode Capability: Volatile memory
Module Manufacturer ID: Bank 1, Hex 0xAD
Non-Volatile Size: None
Volatile Size: 32 GB

#lshw
*-bank:19
description: DIMM DDR4 Synchronous Registered (Buffered) 2933 MHz (0.3 ns) //内存最大频率
product: HMAA4GR7AJR8N-WM
vendor: SK Hynix
physical id: 13
serial: 220F9F63
slot: CPU1_DIMMB0
size: 32GiB //实际所插内存大小
width: 64 bits
clock: 2933MHz (0.3ns)

内存速度对延迟的影响

左边两列是同一种机型和CPU、内存,只是最左边的开了numa,他们的内存Speed: 2400 MT/s,但是实际运行速度是2133;最右边的是另外一种CPU,内存速度更快,用mlc测试他们的延时、带宽。可以看到V52机型带宽能力提升特别大,时延变化不大

image-20220123094155595

image-20220123094928794

image-20220123100052242

对比一下V62,intel8269 机型

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
./Linux/mlc
Intel(R) Memory Latency Checker - v3.9
Measuring idle latencies (in ns)...
Numa node
Numa node 0 1
0 77.9 143.2
1 144.4 78.4

Measuring Peak Injection Memory Bandwidths for the system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using traffic with the following read-write ratios
ALL Reads : 225097.1
3:1 Reads-Writes : 212457.8
2:1 Reads-Writes : 210628.1
1:1 Reads-Writes : 199315.4
Stream-triad like: 190341.4

Measuring Memory Bandwidths between nodes within system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Numa node
Numa node 0 1
0 113139.4 50923.4
1 50916.6 113249.2

Measuring Loaded Latencies for the system
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Inject Latency Bandwidth
Delay (ns) MB/sec
==========================
00000 261.50 225452.5
00002 263.79 225291.6
00008 269.02 225184.1
00015 261.96 225757.6
00050 260.56 226013.2
00100 264.27 225660.1
00200 130.61 195882.4
00300 102.65 133820.1
00400 95.04 101353.2
00500 91.56 81585.9
00700 87.94 58819.1
01000 85.54 41551.3
01300 84.70 32213.6
01700 83.14 24872.5
02500 81.74 17194.3
03500 81.14 12524.2
05000 80.74 9013.2
09000 80.09 5370.0
20000 78.92 2867.2

Measuring cache-to-cache transfer latency (in ns)...
Local Socket L2->L2 HIT latency 51.6
Local Socket L2->L2 HITM latency 51.7
Remote Socket L2->L2 HITM latency (data address homed in writer socket)
Reader Numa Node
Writer Numa Node 0 1
0 - 111.3
1 111.1 -
Remote Socket L2->L2 HITM latency (data address homed in reader socket)
Reader Numa Node
Writer Numa Node 0 1
0 - 175.8
1 176.7 -

[root@numaopen.cloud.et93 /home/admin]
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3199.902
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103

#dmidecode -t memory
Handle 0x003C, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0026
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: DIMM
Set: None
Locator: CPU1_DIMM_E1
Bank Locator: NODE 2
Type: DDR4
Type Detail: Synchronous
Speed: 2666 MHz
Manufacturer: Samsung
Serial Number: 14998029
Asset Tag: CPU1_DIMM_E1_AssetTag
Part Number: M393A4K40BB2-CTD
Rank: 2
Configured Clock Speed: 2666 MHz
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V

BIOS定制

ipmitool

直接在linux内设置主板bios,然后重启就可以生效:

1
2
3
4
5
6
7
//Hygon C86 7260 24-core Processor 设置numa node(hygon 7280 就不行了)
ipmitool raw 0x3e 0x5c 0x00 0x01 0x81
ipmitool raw 0x3e 0x5c 0x05 0x01 0x81

//查询是否生效, 注意是 0x5d
#ipmitool raw 0x3e 0x5d 0x00 0x01
01 81

ipmitool使用

基本语法:ipmitool raw 0x3e 0x5c index 0x01 value

raw 0x3e 0x5c 固定不需要改,

Index表示需要修改的配置项, 接下来的 0x01 固定不需要改

value 表示值,0x81表示enable; 0x80表示disable

中间件的vipclient服务在centos7上域名解析失败

我们申请了一批ECS,操作系统是centos7,这些ECS部署了中间件的DNS服务(vipclient),但是发现这个时候域名解析失败,而同样的配置在centos6上就运行正确

抓包分析

分别在centos6、centos7上nslookup通过同一个DNS Server解析同一个域名,并抓包比较得到如下截图(为了方便我将centos6、7抓包做到了一张图上):

image.png

绿色部分是正常的解析(centos6),红色部分是解析,多了一个OPT(centos7)

赶紧Google一下OPT,原来DNS协议还有一个extention,参考这里

而centos7默认启用edns,但是vipclient实现的时候没有支持edns,所以 centos7 解析域名就出了问题

通过 dig 命令来查看dns解析过程

在centos7上,通过命令 dig edas.console.cztest.com 解析失败,但是改用这个命令禁用edns后就解析正常了:dig +noedns edas.console.cztest.com

vipclient会启动一个53端口,在上面监听dns query,也就是自己就是一个DNS Service

img

分析vipclient域名解析返回的包内容

image.png

把上图中最后4个16进制翻译成10进制IP地址,这个IP地址正是域名所对应的IP,可见vipclient收到域名解析后,因为看不懂edns协议,就按照自己的理解返回了结果,客户端收到这个结果后按照edns协议解析不出来IP,也就是两个的协议不对等导致了问题

总结

centos7之前默认都不启用edns,centos7后默认启用edns,但是vipclient目前不支持edns
通过命令:dig +noedns edas.console.cztest.com 能解析到域名所对应的IP
但是命令:dig edas.console.cztest.com 解析不到IP,因为vipclient(相当于这里的dns server)没有兼容edns,实际返回的结果带了IP但是客户端不支持edns协议所以解析不到(vipclient返回的格式、规范不对)

Docker中的DNS解析过程

问题描述

同一个Docker集群中两个容器中通过 nslookup 同一个域名,这个域名是容器启动的时候通过net alias指定的,但是返回来的IP不一样

如图所示:

image.png

图中上面的容器中 nslookup 返回来了两个IP,但是只有146是正确的,155是多出来,不对的。

要想解决这个问题抓包就没用了,因为Docker 中的net alias 域名是 Docker Daemon自己来解析的,也就是在容器中做域名解析(nslookup、ping)的时候,Docker Daemon先看这个域名是不是net alias的,是的话返回对应的ip,如果不是(比如 www.baidu.com) ,那么Docker Daemon再把这个域名丢到宿主机上去解析,在宿主机上的解析过程就是标准的DNS,可以抓包分析。但是Docker Daemon内部的解析过程没有走DNS协议,不好分析,所以得先了解一下 Docker Daemon的域名解析原理

具体参考文章: http://www.jianshu.com/p/4433f4c70cf0 http://www.bijishequ.com/detail/261401?p=70-67

继续分析所有容器对这个域名的解析

继续分析所有容器对这个域名的解析发现只有某一台宿主机上的有这个问题,而且这台宿主机上所有容器都有这个问题,结合上面的文章,那么这个问题比较明朗了,这台有问题的宿主机的Docker Daemon中残留了一个net alias,你可以理解成cache中有脏数据没有清理。

进而跟业务的同学们沟通,确实155这个IP的容器做过升级,改动过配置,可能升级前这个155也绑定过这个域名,但是升级后绑到146这个容器上去了,但是Docker Daemon中还残留这条记录。

重启Docker Daemon后问题解决(容器不需要重启)

重启Docker Daemon的时候容器还在正常运行,只是这段时间的域名解析会不正常,其它业务长连接都能正常运行,在Docker Daemon重启的时候它会去检查所有容器的endpoint、重建sandbox、清理network等等各种事情,所以就把这个脏数据修复掉了。

在Docker Daemon重启过程中,会给每个容器构建DNS Resovler(setup-resolver),如果构建DNS Resovler这个过程中容器发送了大量域名查询过来同时这些域名又查询不到的话Docker Daemon在重启过程中需要等待这个查询超时,然后才能继续往下走重启流程,所以导致启动流程拉长问题严重的时候导致Docker Daemon长时间无法启动

Docker的域名解析为什么要这么做,是因为容器中有两种域名解析需求:

  1. 容器启动时通过 net alias 命名的域名
  2. 容器中正常对外网各种域名的解析(比如 baidu.com/api.taobao.com)

对于第一种只能由docker daemon来解析了,所以容器中碰到的任何域名解析都会丢给 docker daemon(127.0.0.11), 如果 docker daemon 发现这个域名不认识,也就是不是net alias命名的域名,那么docker就会把这个域名解析丢给宿主机配置的 nameserver 来解析【非常非常像 dns-f/vipclient 的解析原理】

容器中的域名解析

容器启动的时候读取宿主机的 /etc/resolv.conf (去掉127.0.0.1/16 的nameserver)然后当成容器的 /etc/resolv.conf, 但是实际在容器中看到的 /etc/resolve.conf 中的nameserver只有一个:127.0.0.11,因为如上描述nameserver都被代理掉了

容器 -> docker daemon(127.0.0.11) -> 宿主机中的/etc/resolv.conf 中的nameserver

如果宿主机中的/etc/resolv.conf 中的nameserver没有,那么daemon默认会用8.8.8.8/8.8.4.4来做下一级dns server,如果在一些隔离网络中(跟外部不通),那么域名解析就会超时,因为一直无法连接到 8.8.8.8/8.8.4.4 ,进而导致故障。

比如 vipserver 中需要解析 armory的域名,如果这个时候在私有云环境,宿主机又没有配置 nameserver, 那么这个域名解析会发送给 8.8.8.8/8.8.4.4 ,长时间没有响应,超时后 vipserver 会关闭自己的探活功能,从而导致 vipserver 基本不可用一样。

修改 宿主机的/etc/resolv.conf后 重新启动、创建的容器才会load新的nameserver

如果容器中需要解析vipserver中的域名

  1. 容器中安装vipclient,同时容器的 /etc/resolv.conf 配置 nameserver 127.0.0.1
  2. 要保证vipclient起来之后才能启动业务

kubernetes中dns解析偶尔5秒钟超时

dns解析默认会发出ipv4和ipv6,一般dns没有配置ipv6,会导致ipv6解析等待5秒超时后再发出ipv4解析得到正确结果。应用表现出来就是偶尔卡顿了5秒

img

(高亮行delay 5秒才发出查询,是因为高亮前一行等待5秒都没有等到查询结果)

解析异常的strace栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
不正常解析的strace日志:
1596601737.655724 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
1596601737.655784 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, 16) = 0
1596601737.655869 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
1596601737.655968 sendmmsg(5, {{{msg_name(0)=NULL, msg_iov(1)=[{"\20\v\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1", 48}], msg_controllen=0, msg_flags=MSG_TRUNC|MSG_EOR|MSG_FIN|MSG_RST|MSG_ERRQUEUE|MSG_NOSIGNAL|MSG_MORE|MSG_WAITFORONE|MSG_FASTOPEN|0x1e340010}, 48}, {{msg_name(0)=NULL, msg_iov(1)=[{"\207\250\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1", 48}], msg_controllen=0, msg_flags=MSG_WAITALL|MSG_FIN|MSG_ERRQUEUE|MSG_NOSIGNAL|MSG_FASTOPEN|MSG_CMSG_CLOEXEC|0x156c0000}, 48}}, 2, MSG_NOSIGNAL) = 2
1596601737.656113 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
1596601737.659251 ioctl(5, FIONREAD, [141]) = 0
1596601737.659330 recvfrom(5, "\207\250\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1\7cluster\5local\0\0\6\0\1\0\0\0\10\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
=========
1596601737.659421 poll([{fd=5, events=POLLIN}], 1, 4996) = 0 (Timeout) //这里就是问题所在
=========
1596601742.657639 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
1596601742.657735 sendto(5, "\20\v\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1", 48, MSG_NOSIGNAL, NULL, 0) = 48
1596601742.657837 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
1596601742.660929 ioctl(5, FIONREAD, [141]) = 0
1596601742.661038 recvfrom(5, "\20\v\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\3\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
1596601742.661129 poll([{fd=5, events=POLLOUT}], 1, 4996) = 1 ([{fd=5, revents=POLLOUT}])
1596601742.661204 sendto(5, "\207\250\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1", 48, MSG_NOSIGNAL, NULL, 0) = 48
1596601742.661313 poll([{fd=5, events=POLLIN}], 1, 4996) = 1 ([{fd=5, revents=POLLIN}])
1596601742.664443 ioctl(5, FIONREAD, [141]) = 0
1596601742.664519 recvfrom(5, "\207\250\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1\7cluster\5local\0\0\6\0\1\0\0\0\3\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 65536, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
1596601742.664600 close(5) = 0

原因分析

DNS client (glibc 或 musl libc) 会并发请求 A 和 AAAA 记录,跟 DNS Server 通信自然会先 connect (建立fd),后面请求报文使用这个 fd 来发送,由于 UDP 是无状态协议, connect 时并不会发包,也就不会创建 conntrack 表项, 而并发请求的 A 和 AAAA 记录默认使用同一个 fd 发包,send 时各自发的包它们源 Port 相同(因为用的同一个socket发送),当并发发包时,两个包都还没有被插入 conntrack 表项,所以 netfilter 会为它们分别创建 conntrack 表项,而集群内请求 kube-dns 或 coredns 都是访问的CLUSTER-IP,报文最终会被 DNAT 成一个 endpoint 的 POD IP,当两个包恰好又被 DNAT 成同一个 POD IP时,它们的五元组就相同了,在最终插入的时候后面那个包就会被丢掉,而single-request-reopen的选项设置为俩请求被丢了一个,会等待超时再重发 ,这个就解释了为什么还存在调整成2s就是2s的异常比较多 ,因此这种场景下调整成single-request是比较好的方式,同时k8s那边给的dns缓存方案是 nodelocaldns组件可以考虑用一下

关于recolv的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
single-request (since glibc 2.10) 串行解析,
Sets RES_SNGLKUP in _res.options. By default, glibc
performs IPv4 and IPv6 lookups in parallel since
version 2.9. Some appliance DNS servers cannot handle
these queries properly and make the requests time out.
This option disables the behavior and makes glibc
perform the IPv6 and IPv4 requests sequentially (at the
cost of some slowdown of the resolving process).
single-request-reopen (since glibc 2.9) 并行解析,少收到一个解析回复后,再开一个socket重新发起解析,因此看到了前面调整timeout是1s后,还是有挺多1s的解析
Sets RES_SNGLKUPREOP in _res.options. The resolver
uses the same socket for the A and AAAA requests. Some
hardware mistakenly sends back only one reply. When
that happens the client system will sit and wait for
the second reply. Turning this option on changes this
behavior so that if two requests from the same port are
not handled correctly it will close the socket and open
a new one before sending the second request.

getaddrinfo 关闭ipv6的解析

基本上所有测试下来,网上那些通过修改配置的基本都不能关闭ipv6的解析,只有通过在代码中指定

hints.ai_family = AF_INET; /* or AF_INET6 for ipv6 addresses */

来只做ipv4的解析

Prefer A (IPv4) DNS lookups before AAAA(IPv6) lookups

https://man7.org/linux/man-pages/man3/getaddrinfo.3.html:

1
2
3
4
5
6
7
8
9
If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
addresses are returned in the list pointed to by res only if the
local system has at least one IPv4 address configured, and IPv6
addresses are returned only if the local system has at least one
IPv6 address configured. The loopback address is not considered
for this case as valid as a configured address. This flag is
useful on, for example, IPv4-only systems, to ensure that
getaddrinfo() does not return IPv6 socket addresses that would
always fail in connect(2) or bind(2).

c code demo:

1
2
3
4
5
6
7
struct addrinfo hints, *result;
int s;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; /* or AF_INET6 for ipv6 addresses */
s = getaddrinfo(NULL, "ftp", &hints, &result);
...

or

In the Wireshark capture, 172.25.50.3 is the local DNS resolver; the capture was taken there, so you also see its outgoing queries and responses. Note that only an A record was requested. No AAAA lookup was ever done.

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
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>

int main(void) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int s;
char host[256];

memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;

s = getaddrinfo("www.facebook.com", NULL, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}

for (rp = result; rp != NULL; rp = rp->ai_next) {
getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
printf("%s\n", host);
}
freeaddrinfo(result);
}

or:https://unix.stackexchange.com/questions/9940/convince-apt-get-not-to-use-ipv6-method

/etc/gai.conf getaddrinfo的配置文件

Prefix Precedence Label Usage
::1/128 50 0 Localhost
::/0 40 1 Default unicast
::ffff:0:0/96 35 4 IPv4-mapped IPv6 address
2002::/16 30 2 6to4
2001::/32 5 5 Teredo tunneling
fc00::/7 3 13 Unique local address
::/96 1 3 IPv4-compatible addresses (deprecated)
fec0::/10 1 11 Site-local address (deprecated)
3ffe::/16 1 12 6bone (returned)

来源于维基百科

0:0:0:0:0:ffff:0:0/96 10 4 IPv4映射地址(这个地址网络上信息较少,地址范围::: ffff:0.0.0.0~:: ffff:255.255.255.255 地址数量2 128−96 = 2 32 = 4 294 967 296,用于软件,目的是IPv4映射的地址。 )

参考资料

Kubernetes >= 1.13 + kube-proxy IPVS mode 服务部署不平滑

linux ipv4 ipv6双栈 (优先ipv4而不使用ipv6配置)

windows7的wifi总是报DNS域名异常无法上网

Windows7笔记本+公司wifi(dhcp)环境下,用着用着dns服务不可用(无法通过域名上网,通过IP地址可以访问),这里有个一模一样的Case了:https://superuser.com/questions/629559/why-is-my-computer-suddenly-using-nbns-instead-of-dns 一样的环境,看来这个问题也不只是我一个人碰到了。

其实之前一直有,一个月偶尔出来一两次,以为是其他原因就没管,这次换了新电脑还是这个毛病有点不能忍,于是决定彻底解决一下。

这个问题出现后,通过下面三个办法都可以让DNS恢复正常:

  1. 重启系统大法,恢复正常
  2. 禁用wifi驱动再启用,恢复正常
  3. 不用DHCP,而是手工填入一个DNS服务器,比如114.114.114.114【公司域名就无法解析了】

如果只是停用一下wifi再启用问题还在。

找IT升级了网卡驱动不管用

重现的时候抓包看看

image.png

这肯定不对了,254上根本就没有跑DNS服务,可是当时没有检查 ipconfig,看看是否将网关IP动态配置到dns server里面去了,等下次重现后再确认吧。

第二次重现后抓包,发现不一样了:

image.png

出来一个 NBNS 的鬼东西,赶紧查了一下,把它禁掉,如下图所示:

image.png

把NBNS服务关了就能上网了,同时也能抓到各种DNS Query包

事情没有这么简单

过一段时间后还是会出现上面的症状,但是因为NBNS关闭了,所以这次 ping www.baidu.com 的时候没有任何包了,没有DNS Query包,也没有NBNS包,这下好尴尬。

尝试Enable NBNS,又恢复了正常,看来开关 NBNS 仍然只是一个workaround,他不是导致问题的根因,开关一下没有真正解决问题,只是临时相当于重启了dns修复了问题而已。

继续在网络不通的时候尝试直接ping dns server ip,发现一个奇怪的现象,丢包很多,丢包的时候还总是从 192.168.0.11返回来的,这就奇怪了,我的笔记本基本IP是30开头的,dns server ip也是30开头的,route 路由表也是对的,怎么就走到 192.168.0.11 上了啊(参考我的另外一篇文章,网络到底通不通),赶紧 ipconfig /all | grep 192

image.png

发现这个IP是VirtualBox虚拟机在笔记本上虚拟出来的网卡IP,这下我倒是能理解为啥总是我碰到这个问题了,因为我的工作笔记本一拿到后第一件事情就是装VirtualBox 跑虚拟机。

VirtualBox为啥导致了这个问题就是一个很偏的方向,我实在无能为力了,尝试找到了一个和VirtualBox的DNS相关的开关命令,只能死马当活马医了(像极了算命大师和老中医)

./VBoxManage.exe  modifyvm "ubuntu" --natdnshostresolver1 on

执行完上面的命令观察了3个月了,暂时没有再出现这个问题,相对于以前轻则一个月2、3次,重则一天出现5、6次,应该算是解决了,同时升级 VirtualBox 也无法解决这个问题。

route 信息:

$route PRINT -4
===========================================================================
接口列表
 23...00 ff c1 57 7f 12 ......Sangfor SSL VPN CS Support System VNIC
 18...f6 96 34 38 76 06 ......Microsoft Virtual WiFi Miniport Adapter #2
 17...f6 96 34 38 76 07 ......Microsoft Virtual WiFi Miniport Adapter
 15...00 ff 1f 24 e6 6c ......Sophos SSL VPN Adapter
 12...f4 96 34 38 76 06 ......Intel(R) Dual Band Wireless-AC 8260
 11...54 ee 75 d4 99 ae ......Intel(R) Ethernet Connection I219-V
 14...0a 00 27 00 00 0e ......VirtualBox Host-Only Ethernet Adapter
  1...........................Software Loopback Interface 1
 25...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter
 19...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #9
 26...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #2
 27...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #3
 22...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #7
 21...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #5
 20...00 00 00 00 00 00 00 e0 Microsoft 6to4 Adapter #2
 16...00 00 00 00 00 00 00 e0 Teredo Tunneling Pseudo-Interface
 24...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #8
===========================================================================

IPv4 路由表
===========================================================================
活动路由:
网络目标网络掩码  网关   接口   跃点数
  0.0.0.0  0.0.0.0192.168.0.250169.254.24.89266
  0.0.0.0  0.0.0.030.27.115.254 30.27.112.21 20
  30.27.112.0255.255.252.0在链路上  30.27.112.21276
 30.27.112.21  255.255.255.255在链路上  30.27.112.21276
30.27.115.255  255.255.255.255在链路上  30.27.112.21276
127.0.0.0255.0.0.0在链路上 127.0.0.1306
127.0.0.1  255.255.255.255在链路上 127.0.0.1306
  127.255.255.255  255.255.255.255在链路上 127.0.0.1306
  169.254.0.0  255.255.0.0在链路上 169.254.24.89266
169.254.24.89  255.255.255.255在链路上 169.254.24.89266
  169.254.255.255  255.255.255.255在链路上 169.254.24.89266
224.0.0.0240.0.0.0在链路上 127.0.0.1306
224.0.0.0240.0.0.0在链路上 169.254.24.89266
224.0.0.0240.0.0.0在链路上  30.27.112.21276
  255.255.255.255  255.255.255.255在链路上 127.0.0.1306
  255.255.255.255  255.255.255.255在链路上 169.254.24.89266
  255.255.255.255  255.255.255.255在链路上  30.27.112.21276
===========================================================================
永久路由:
  网络地址  网络掩码  网关地址  跃点数
  0.0.0.0  0.0.0.0192.168.0.250 默认
  0.0.0.0  0.0.0.0192.168.0.250 默认
===========================================================================

另外DHCP也许可以做一些事情,至少同样的用法在以前的公司网络环境没有出过问题

下面是来自微软官方的建议:

One big advise – do not disable the DHCP Client service on any server, whether the machine is a DHCP client or statically configured. Somewhat of a misnomer, this service performs Dynamic DNS registration and is tied in with the client resolver service. If disabled on a DC, you’ll get a slew of errors, and no DNS queries will get resolved.

No DNS Name Resolution If DHCP Client Service Is Not Running. When you try to resolve a host name using Domain Name Service (DNS), the attempt is unsuccessful. Communication by Internet Protocol (IP) address (even to …

http://support.microsoft.com/kb/268674

from: https://blogs.msmvps.com/acefekay/2009/11/29/dns-wins-netbios-amp-the-client-side-resolver-browser-service-disabling-netbios-direct-hosted-smb-directsmb-if-one-dc-is-down-does-a-client-logon-to-another-dc-and-dns-forwarders-algorithm/#section4

NBNS也许会导致nslookup OK but ping fail的问题

https://www.experts-exchange.com/questions/28894006/NetBios-name-resolution-instead-of-DNS.html

The Windows Client Resolver(ping dns流程)

  1. Windows checks whether the host name is the same as the local host name.
  2. If the host name and local host name are not the same, Windows searches the DNS client resolver cache.
  3. If the host name cannot be resolved using the DNS client resolver cache, Windows sends DNS Name Query Request messages to its configured DNS servers.
  4. If the host name is a single-label name (such as server1) and cannot be resolved using the configured DNS servers, Windows converts the host name to a NetBIOS name and checks its local NetBIOS name cache.
  5. If Windows cannot find the NetBIOS name in the NetBIOS name cache, Windows contacts its configured WINS servers.
  6. If Windows cannot resolve the NetBIOS name by querying its configured WINS servers, Windows broadcasts as many as three NetBIOS Name Query Request messages on the directly attached subnet.
  7. If there is no reply to the NetBIOS Name Query Request messages, Windows searches the local Lmhosts file.
    Ping

windows下nslookup 流程

  1. Check the DNS resolver cache. This is true for records that were cached via a previous name query or records that are cached as part of a pre-load operation from updating the hosts file.
  2. Attempt NetBIOS name resolution.
  3. Append all suffixes from the suffix search list.
  4. When a Primary Domain Suffix is used, nslookup will only take devolution 3 levels.

总结

碰到问题绕过去也不是长久之计,还是要从根本上了解问题的本质,这个问题在其它公司没有碰到过,我觉得跟公司的DNS、DHCP的配置也有点关系吧,但是这个我不好确认,应该还有好多用Windows本本的同学同样会碰到这个问题的,希望对你们有些帮助

https://support.microsoft.com/en-us/help/172218/microsoft-tcp-ip-host-name-resolution-order

http://www.man7.org/linux/man-pages/man5/resolv.conf.5.html


本文附带鸡汤:

有些技能初学很难,大家水平都差不多,但是日积月累就会形成极强的优势,而且一旦突破某个临界点,它就会突飞猛进,这种技能叫指数型技能,是值得长期投资的,比如物理学就是一种指数型技能。

那么抓包算不算呢?​​

nslookup 域名结果正确,但是 ping 域名 返回 unknown host

2018-02 update : 最根本的原因 https://access.redhat.com/solutions/1426263

下面让我们来看看这个问题的定位过程

先Google一下: nslookup ok but ping fail, 这个关键词居然被Google自动提示了,看来碰到这个问题同学的好多

Google到的帖子大概有如下原因:

  • 域名最后没有加 . 然后被自动追加了 tbsite.net aliyun.com alidc.net,自然 ping不到了
  • /etc/resolv.conf 配置的nameserver 要保证都是正常服务的
  • /etc/nsswitch.conf 中的这行:hosts: files dns 配置成了 hosts: files mdns dns,而server不支持mdns
  • 域名是单标签的(domain 单标签; domain.com 多标签),单标签在windows下走的NBNS而不是DNS协议

检查完我的环境不是上面描述的情况,比较悲催,居然碰到了一个Google不到的问题

抓包看为什么解析不了

DNS协议是典型的UDP应用,一来一回就搞定了查询,效率比TCP三次握手要高多了,DNS Server也支持TCP,不过一般不用TCP

sudo tcpdump -i eth0 udp and port 53 

抓包发现ping 不通域名的时候都是把域名丢到了 /etc/resolv.conf 中的第二台nameserver,或者根本没有发送 dns查询。

这里要多解释一下我们的环境, /etc/resolv.conf 配置了2台 nameserver,第一台负责解析内部域名,另外一台负责解析其它域名,如果内部域名的解析请求丢到了第二台上自然会解析不到。

所以这个问题的根本原因是挑选的nameserver 不对,按照 /etc/resolv.conf 的逻辑都是使用第一个nameserver,失败后才使用第二、第三个备用nameserver。

比较奇怪,出问题的都是新申请到的一批ECS,仔细对比了一下正常的机器,发现有问题的 ECS /etc/resolv.conf 中放了一个词rotate,赶紧查了一下rotate的作用(轮询多个nameserver),然后把rotate去掉果然就好了。

风波再起

本来以为问题彻底解决了,结果还是有一台机器ping仍然是unknow host,眼睛都看瞎了没发现啥问题,抓包发现总是把dns请求交给第二个nameserver,或者根本不发送dns请求,这就有意思了,跟我们理解的不太一样。

看着像有cache之类的,于是在正常和不正常的机器上使用 strace ,果然发现了点不一样的东西:

image.png

ping的过程中访问了 nscd(name service cache daemon) 同时发现 nscd返回值图中红框的 0,跟正常机器比较发现正常机器红框中是 -1,于是检查 /var/run/nscd/ 下面的东西,kill 掉 nscd进程,然后删掉这个文件夹,再ping,一切都正常了。

从strace来看所有的ping都会尝试看看 nscd 是否在运行,在的话找nscd要域名解析结果,如果nscd没有运行,那么再找 /etc/resolv.conf中的nameserver做域名解析

而nslookup和dig这样的命令就不会尝试找nscd,所以没有这个问题。

如下文字摘自网络:

NSCD(name service cache daemon)是GLIBC关于网络库的一个组件,服务基于glibc开发的各类网络服务,基本上来讲我们能见到的一些编程语言和开发框架最终均会调用到glibc的网络解析的函数(如GETHOSTBYNAME or GETHOSTBYADDR等),因此绝大部分程序能够使用NSCD提供的缓存服务。当然了如果是应用端自己用socker编写了一个网络client就无法使用NSCD提供的缓存服务,比如DNS领域常见的dig命令不会使用NSCD提供的缓存,而作为对比ping得到的DNS解析结果将使用NSCD提供的缓存

connect函数返回值的说明:

RETURN VALUE
   If  the  connection or binding succeeds, zero is returned.  On error, -1 is returned,and errno is set appropriately.

Windows下客户端是默认有dns cache的,但是Linux Client上默认没有dns cache,DNS Server上是有cache的,所以忽视了这个问题。这个nscd是之前看ping不通,google到这么一个命令,但是应该没有搞明白它的作用,就执行了一个网上的命令,把nscd拉起来,然后ping 因为rotate的问题,还是不通,同时nscd cache了这个不通的结果,导致了新的问题

域名解析流程(或者说 glibc的 gethostbyname() 函数流程–背后是NameServiceSwitch)

  • DNS域名解析的时候先根据 /etc/nsswitch.conf 配置的顺序进行dns解析(name service switch),一般是这样配置:hosts: files dns 【files代表 /etc/hosts ; dns 代表 /etc/resolv.conf】(ping是这个流程,但是nslookup和dig不是)
  • 如果本地有DNS Client Cache,先走Cache查询,所以有时候看不到DNS网络包。Linux下nscd可以做这个cache,Windows下有 ipconfig /displaydns ipconfig /flushdns
  • 如果 /etc/resolv.conf 中配置了多个nameserver,默认使用第一个,只有第一个失败【如53端口不响应、查不到域名后再用后面的nameserver顶上】
  • 如果 /etc/resolv.conf 中配置了rotate,那么多个nameserver轮流使用. 但是因为glibc库的原因用了rotate 会触发nameserver排序的时候第二个总是排在第一位

nslookup和dig程序是bind程序包所带的工具,专门用来检测DNS Server的,实现上更简单,就一个目的,给DNS Server发DNS解析请求,没有调gethostbyname()函数,也就不遵循上述流程,而是直接到 /etc/resolv.conf 取第一个nameserver当dns server进行解析

glibc函数

glibc 的解析器(revolver code) 提供了下面两个函数实现名称到 ip 地址的解析, gethostbyname 函数以同步阻塞的方式提供服务, 没有超时等选项, 仅提供 IPv4 的解析. getaddrinfo 则没有这些限制, 同时支持 IPv4, IPv6, 也支持 IPv4 到 IPv6 的映射选项. 包含 Linux 在内的很多系统都已废弃 gethostbyname 函数, 使用 getaddrinfo 函数代替. 不过从现实的情况来看, 还是有很多程序或网络库使用 gethostbyname 进行服务.

备注:
线上开启 nscd 前, 建议做好程序的测试, nscd 仅支持通过 glibc, c 标准机制运行的程序, 没有基于 glibc 运行的程序可能不支持 nscd. 另外一些 go, perl 等编程语言网络库的解析函数是单独实现的, 不会走 nscd 的 socket, 这种情况下程序可以进行名称解析, 但不会使用 nscd 缓存. 不过我们在测试环境中使用go, java 的常规网络库都可以正常连接 nscd 的 socket 进行请求; perl 语言使用 Net::DNS 模块, 不会使用 nscd 缓存; python 语言使用 python-dns 模块, 不会使用 nscd 缓存. python 和 perl 不使用模块的时候进行解析还是遵循上述的过程, 同时使用 nscd 缓存.

下面是glibc中对rotate的处理:

这是glibc 2.2.5(2010年的版本),如果有rotate逻辑就是把第一个nameserver总是丢到最后一个去(为了均衡nameserver的负载,保护第一个nameserver):

image.png

在2017年这个代码逻辑终于改了,不过还不是默认用第一个,而是随机取一个,rotate搞成random了,这样更不好排查问题了

image.png

image.png

也就是2010年之前的glibc版本在rotate模式下都是把第一个nameserver默认挪到最后一个(为了保护第一个nameserver),这样rotate模式下默认第一个nameserver总是/etc/resolov.conf配置文件中的第二个,到2017年改掉了这个问题,不过改成随机取nameserver, 作者不认为这是一个bug,他觉得配置rotate就是要平衡多个nameserver的性能,所以random最公平,因为大多程序都是查一次域名缓存好久,不随机轮询的话第一个nameserver压力太大

参考 glibc bug

Linux内核与glibc

getaddrinfo() is a newer function that was introduced to replace gethostbyname() and provides a more flexible and robust way of resolving hostnames. getaddrinfo() can resolve both IPv4 and IPv6 addresses, and it supports more complex name resolution scenarios, such as service name resolution and name resolution with specific protocol families.

linux主要是基于glibc,分析glibc源码(V2.17)getaddrinfo 逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// search1、search2等是resolv.conf中配置的search域,会根据resolv.conf中ndots配置和qname的级数来决定
// 先查加search域的域名还是后查加search域的域名,除没有配置nameserver外,任何错误都会重试下一个
foreach domain(qname、qname.search1、qname.search2...)
foreach retry // resolv.conf中配置的retry数目
foreach ns // resolv.conf中配置的nameserver,最多有效的只有前三个,增加rotate 的option后,会从
//nameserver中(最多前三个)随机选择一个进行请求
foreach qtype(A、AAAA)
// 根据请求显试指定和系统是双栈还是单栈v4、or单栈v6来决定,如果两个
// 类型的话会同时并发请求,不会等另外一个返回。如果在resov.conf中指定
// single-request-reopen,则会每个类型请求新建一个链接,否则复用同一个
foreach (without_edns0, with_edns0 )
// 注意:默认不支持edns0,如果要支持的话需要设置宏RES_USE_EDNS0,并重编译
if (timeout || rcode == servfail
|| rcode == notimp || rcode == refuse)
// timeout时间第一次为resolv.conf中配置,后面的依据下面公式
//计算:(timeout<<index_of_dns_server)/num_of_dns_server
conftinue;

还有一种情况,开了easyconnect这样的vpn软件,同时又配置了自己的dns server

这个时候ping 某个自己的dns server中才有的域名是没法解析到的,即使你配置了自己的dns server,如果这个时候你通过 nslookup 自己的域名, 自己的dns-server-ip 确实是能够解析到的。但是你只是 nslookup 自己的域名 就不行,明显可以看到这个时候nslookup把域名发给了127.0.0.1:53来解析,而这个端口正是easyconnect这个软件在监听,你也可以理解easyconnect这样的软件的工作方式就是必须要挟持你的dns解析,可以理解的是这个时候nslookup肯定解析不到你的域名(只把dns解析丢给第一个nameserver–127.0.0.1),但是不能理解的是还是ping不通域名,正常ping的逻辑丢给第一个127.0.0.1解析不到域名的话,会丢给第二个dns-server继续尝试解析,但是这里的easyconnect没有进行第二次尝试,这也许是它实现上没有考虑到或者故意这样实现的。

其他情况

resolv.conf中search只能支持最多6个后缀(代码中写死了): This cannot be modified for RHEL 6.x and below and is resolved in RHEL7 glibc package versions at or exceeding glibc-2.17-222.el7.

nameserver:指定nameserver,必须配置,每行指定一个nameserver,最多只能生效3行

总结

  • /etc/resolv.conf rotate参数的关键作用
  • nscd对域名解析的cache
  • nslookup背后执行原理和ping不一样(前者不调glibc的gethostbyname() 函数), nslookup不会检查 /etc/hosts、/etc/nsswitch.conf, 而是直接从 /etc/resolv.conf 中取nameserver; 但是ping或者我们在程序一般最终都是通过调glibc的gethostbyname() 函数对域名进行解析的,也就是按照 /etc/nsswitch.conf 指示的来
  • 在没有源代码的情况下strace和抓包能够看到问题的本质
  • 根因: https://access.redhat.com/solutions/1426263
  • resolv.conf中最多只能使用前六个搜索域

下一篇介绍《在公司网下,我的windows7笔记本的wifi总是报dns域名异常无法上网(通过IP地址可以上网)》困扰了我两年,最近换了新笔记本还是有这个问题才痛下决心咬牙解决

参考资料

https://superuser.com/questions/495759/why-is-ping-unable-to-resolve-a-name-when-nslookup-works-fine

https://stackoverflow.com/questions/330395/dns-problem-nslookup-works-ping-doesnt

来自redhat上原因的描述,但是从代码的原作者的描述来看,他认为rotate下这个行为是合理的

Linux 系统如何处理名称解析

网络丢包

查看网卡是否丢包,一般是ring buffer太小

ethtool -S eth0 | grep rx_ | grep errors

当驱动处理速度跟不上网卡收包速度时,驱动来不及分配缓冲区,NIC接收到的数据包无法及时写到sk_buffer(由网卡驱动直接在内核中分配的内存,并存放数据包,供内核软中断的时候读取),就会产生堆积,当NIC内部缓冲区写满后,就会丢弃部分数据,引起丢包。这部分丢包为rx_fifo_errors,在 /proc/net/dev中体现为fifo字段增长,在ifconfig中体现为overruns指标增长。

查看ring buffer的大小设置

ethtool ‐g eth0  

Socket buffer太小导致的丢包(一般不多见)

内核收到包后,会给对应的socket,每个socket会有 sk_rmem_alloc/sk_wmem_alloc/sk_omem_alloc、sk_rcvbuf(bytes)来存放包

When sk_rmem_alloc >
sk_rcvbuf the TCP stack will call a routine which “collapses” the receive queue

查看collapses:

netstat -sn | egrep "prune|collap"; sleep 30; netstat -sn | egrep "prune|collap"
17671 packets pruned from receive queue because of socket buffer overrun
18671 packets pruned from receive queue because of socket buffer overrun

测试发现在小包情况下,这两个值相对会增大且比较快。增大 net.ipv4.tcp_rmem 和 net.core.rmem_max、net.core.rmem_default 后没什么效果 – 需要进一步验证

net.core.netdev_budget

sysctl net.core.netdev_budget //默认300, The default value of the budget is 300. This will
cause the SoftIRQ process to drain 300 messages from the NIC before getting off the CPU
如果 /proc/net/softnet_stat 第三列一直在增加的话需要,表示SoftIRQ 获取的CPU时间太短,来不及处理足够多的网络包,那么需要增大这个值
net/core/dev.c->net_rx_action 函数中会按netdev_budget 执行softirq,budget每次执行都要减少,一直到没有了,就退出softirq

一般默认软中断只绑定在CPU0上,如果包的数量巨大的话会导致 CPU0利用率 100%(主要是si),这个时候可以检查文件 /proc/net/softnet_stat 的第三列 或者 RX overruns 是否在持续增大

net.core.netdev_max_backlog

enqueue_to_backlog函数中,会对CPU的softnet_data 实例中的接收队列(input_pkt_queue)进行判断,如果队列中的数据长度超过netdev_max_backlog ,那么数据包将直接丢弃,这就产生了丢包。

参数net.core.netdev_max_backlog指定的,默认大小是 1000。

netdev_max_backlog 接收包队列(网卡收到还没有进行协议的处理队列),每个cpu core一个队列,如果/proc/net/softnet_stat第二列增加就表示这个队列溢出了,需要改大。

/proc/net/softnet_stat:(第一列和第三列的关系?)
The 1st column is the number of frames received by the interrupt handler. (第一列是中断处理程序接收的帧数)
The 2nd column is the number of frames dropped due to netdev_max_backlog being exceeded. netdev_max_backlog
The 3rd column is the number of times ksoftirqd ran out of netdev_budget or CPU time when there was still work to be done net.core.netdev_budget

rp_filter

https://www.yuque.com/plantegg/weyi1s/uc7a5g

关于ifconfig的种种解释

  • RX errors: 表示总的收包的错误数量,这包括 too-long-frames 错误,Ring Buffer 溢出错误,crc 校验错误,帧同步错误,fifo overruns 以及 missed pkg 等等。
  • RX dropped: 表示数据包已经进入了 Ring Buffer,但是由于内存不够等系统原因,导致在拷贝到内存的过程中被丢弃。
  • RX overruns: 表示了 fifo 的 overruns,这是由于 Ring Buffer(aka Driver Queue) 传输的 IO 大于 kernel 能够处理的 IO 导致的,而 Ring Buffer 则是指在发起 IRQ 请求之前的那块 buffer。很明显,overruns 的增大意味着数据包没到 Ring Buffer 就被网卡物理层给丢弃了,而 CPU 无法及时地处理中断是造成 Ring Buffer 满的原因之一,上面那台有问题的机器就是因为 interruprs 分布的不均匀(都压在 core0),没有做 affinity 而造成的丢包。
  • RX frame: 表示 misaligned 的 frames。

dropped数量持续增加,建议增大Ring Buffer ,使用ethtool ‐G 进行设置。

txqueuelen:1000 对应着qdisc队列的长度(发送队列和网卡关联着)

而对应的接收队列由内核参数来设置:

net.core.netdev_max_backlog

Adapter buffer defaults are commonly set to a smaller size than the maximum//网卡进出队列大小调整 ethtool -G eth rx 8192 tx 8192

image.png

核心流程

image.png

接收数据包是一个复杂的过程,涉及很多底层的技术细节,但大致需要以下几个步骤:

  1. 网卡收到数据包。
  2. 将数据包从网卡硬件缓存转移到服务器内存中。
  3. 通知内核处理。
  4. 经过TCP/IP协议逐层处理。
  5. 应用程序通过read()从socket buffer读取数据。

通过 dropwatch来查看丢包点

dropwatch -l kas (-l 加载符号表) // 丢包点位置等于 ip_rcv地址+ cf(偏移量)

image.png

一个典型的接收包调用堆栈:

 0xffffffff8157af10 : tcp_may_send_now+0x0/0x160 [kernel]
 0xffffffff815765f8 : tcp_fastretrans_alert+0x868/0xb50 [kernel]
 0xffffffff8157729d : tcp_ack+0x8bd/0x12c0 [kernel]
 0xffffffff81578295 : tcp_rcv_established+0x1d5/0x750 [kernel]
 0xffffffff81582bca : tcp_v4_do_rcv+0x10a/0x340 [kernel]
 0xffffffff81584411 : tcp_v4_rcv+0x831/0x9f0 [kernel]
 0xffffffff8155e114 : ip_local_deliver_finish+0xb4/0x1f0 [kernel]
 0xffffffff8155e3f9 : ip_local_deliver+0x59/0xd0 [kernel]
 0xffffffff8155dd8d : ip_rcv_finish+0x7d/0x350 [kernel]
 0xffffffff8155e726 : ip_rcv+0x2b6/0x410 [kernel]
 0xffffffff81522d42 : __netif_receive_skb_core+0x582/0x7d0 [kernel]
 0xffffffff81522fa8 : __netif_receive_skb+0x18/0x60 [kernel]
 0xffffffff81523c7e : process_backlog+0xae/0x180 [kernel]
 0xffffffff81523462 : net_rx_action+0x152/0x240 [kernel]
 0xffffffff8107dfff : __do_softirq+0xef/0x280 [kernel]
 0xffffffff8163f61c : call_softirq+0x1c/0x30 [kernel]
 0xffffffff81016fc5 : do_softirq+0x65/0xa0 [kernel]
 0xffffffff8107d254 : local_bh_enable_ip+0x94/0xa0 [kernel]
 0xffffffff81634f4b : _raw_spin_unlock_bh+0x1b/0x40 [kernel]
 0xffffffff8150d968 : release_sock+0x118/0x170 [kernel]

如果客户端建立连接的时候抛异常,可能的原因(握手失败,建不上连接):

  • 网络不通,诊断:ping ip
  • 端口不通, 诊断:telnet ip port
  • rp_filter 命中(rp_filter=1, 多网卡环境), 诊断: netstat -s | grep -i filter ;
  • snat/dnat的时候宿主机port冲突,内核会扔掉 syn包。 troubleshooting: sudo conntrack -S | grep insert_failed //有不为0的
  • 全连接队列满的情况,诊断: netstat -s | egrep “listen|LISTEN”
  • syn flood攻击, 诊断:同上
  • 若远端服务器的内核参数 net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps 的值都为 1,则远端服务器会检查每一个报文中的时间戳(Timestamp),若 Timestamp 不是递增的关系,不会响应这个报文。配置 NAT 后,远端服务器看到来自不同的客户端的源 IP 相同,但 NAT 前每一台客户端的时间可能会有偏差,报文中的 Timestamp 就不是递增的情况。nat后的连接,开启timestamp。因为快速回收time_wait的需要,会校验时间该ip上次tcp通讯的timestamp大于本次tcp(nat后的不同机器经过nat后ip一样,保证不了timestamp递增),诊断:是否有nat和是否开启了timestamps
  • NAT 哈希表满导致 ECS 实例丢包 nf_conntrack full

iptables和tcpdump

sudo iptables -A INPUT -p tcp –destination-port 8089 -j DROP

tcpdump 是直接从网卡驱动拿包,也就是包还没进入内核tcpdump就拿到了,而iptables是工作在内核层,也就是即使被DROP还是能tcpdump到8089的packet。

参考资料:

https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651747704&idx=3&sn=cd76ad912729a125fd56710cb42792ba&chksm=bd12ac358a6525235f51e3937d99ea113ed45542c51bc58bb9588fa1198f34d95b7d13ae1ae2&mpshare=1&scene=1&srcid=07047U4tN9Y3m97WQUJSLENt#rd

http://blog.hyfather.com/blog/2013/03/04/ifconfig/

网络环路

本文主要探讨网络环路的成因,危害以及预防

交换机之间多条网线导致环路

image.png

如图sw1/2/3 三个交换机形成一个环路,一个arp广播包从sw1出来到sw2,然后到sw3,再然后又从sw3回到sw1,形成一个环路,这个arp包会重复前面的传播过程进而导致这个包一直在三个交换机之间死循环,进而把三个交换机的CPU、带宽全部打满,整个网络瘫痪

对这种网络环路网络工程师们非常忌惮,因为一旦形成非常不好排查,并且整个网络瘫痪,基本上是严防死守。同时交换机也提供了各种功能(算法、策略)来自动检测网络环路并阻断网络环路。

比如上图中交换机能检测到虚线形成了环路,并自动把这个交换机口Down掉以阻止成环。

交换机对环路的阻断–STP(Spanning TreeProtocol)协议

STP协议的基本思想十分简单。大家知道,自然界中生长的树是不会出现环路的,如果网络也能够像一棵树一样生长就不会出现环路。于是,STP协议中定义了根桥(RootBridge)、根端口(RootPort)、指定端口(DesignatedPort)、路径开销(PathCost)等概念,目的就在于通过构造一棵自然树的方法达到裁剪冗余环路的目的,同时实现链路备份和路径最优化。用于构造这棵树的算法称为生成树算法SPA(Spanning TreeAlgorithm)。(摘自:http://network.51cto.com/art/201307/404013.htm)

STP是通过BPDU的网络包来在交换机之间交换信息、判断是否成环

一个STP的Case

下图是抓到的STP网络包
image.png

STP协议的后果就是带宽效率低,所以出现了PVST、PVST+、RSTP、MISTP、MSTP,这些协议可能不同厂家的交换机都不一样,互相之间也不一定兼容,所以是否生效要以实际测试为准

用tcpdump抓取stp包

$ sudo tcpdump -vvv -p -n -i eth1 stp
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes

15:44:10.772423 STP 802.1d, Config, Flags [none], bridge-id  8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:12.768245 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC8.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:14.766513 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:16.766478 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:18.767851 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0	

交换机上看到的STP

C4948-D2-08-36U#show run int g1/31
Building configuration...

Current configuration : 482 bytes
!
interface GigabitEthernet1/31
 description to D2-9-09/10U-GWR730-eth1
 switchport access vlan 270
 switchport mode access
 switchport port-security maximum 50
 switchport port-security
 switchport port-security aging time 2
 switchport port-security violation restrict
 switchport port-security aging type inactivity
 switchport port-security aging static
 storm-control broadcast level 20.00
 spanning-tree portfast
 spanning-tree bpduguard enable
 spanning-tree guard root
end

SDN或者说OVS对网络环路的影响

前面讨论的都是硬件交换机之间的网络环路以及硬件交换机对这些环路的处理,那么在SDN和OVS的场景下有没有可能成环呢? 成环后硬件交换机能不能检测到,或者软交换机自己能否检测到并阻止这些环路呢?

来看一个OVS场景下的成环Case

image.png

上图中红色虚线部分组成了一个环路,是为了组成环路而人为构造的场景,同时发现OVS只支持STP算法,打开也没有用,因为OVS和硬件交换机之间没法通过BPDU来协商判断环路(物理交换机丢掉了硬件交换机的BPDU包)。

也就是在硬件网络环境固定的情况下,我们可以在Linux环境下鼓捣出来一个网络环路,同时让Linux所在的物理二层网络瘫痪掉(好屌)

在这种网络环路下后果

  • 整个二层网络瘫痪,所有交换机CPU 100%,带宽100%
  • 连接在交换机上的所有服务器SYS CPU飙升到 30%左右(没有啥意义了,服务器没法跟外部做任何交流了)

交换机的CPU状态:

image.png

成环后抓到的arp广播风暴网络包(实际我只发了一个arp包):

image.png

其它网络环路

  • 直接把两个交换机用两根网线连接起来就是个环路
  • 拿一根网线两头连接在同一个交换机的两个网口上(短路) 2006年的一个Case: https://www.zhihu.com/question/49545070,不过现在的交换机基本上都能识别这种短路
  • 两个交换机之间做bond失败,导致环路或者三角形(三角形的话会导致多个网口对应同一个mac地址,进而导致这个mac地址网络不通,三角形不会形成网络风暴)

参考资料:

https://www.zhihu.com/question/49545070

http://network.51cto.com/art/201307/404013.htm

0%