网络包排错指南-类linux 平台

背景信息

最近一直在测试k8s,如果你了解或者解接触过docker,那你一定知道docker 相关的网络很大部分在桥接、路由、Iptables 上做文章。如果你凑巧接触过k8s,并且了解其后面的原理,那你一定知道kube-proxy 把iptables 玩的简直要飞起来。当然你可能会想到一些排错工具,比如我之前常用的抓包工具,或者路由跟踪工具,但这些工具在目前这样复杂的环境下,是不太趁手的,特别是包在本机的多个网卡或者虚拟网卡里转来转去,还有很多个iptables策略,路由等让包在内核空间中转来转去。抓包工具抓不到这些信息,traceroute 跟踪路由时你会发现你需要跟踪一个src,dst 还有port的包的路由信息是没有法达成的。

这里介绍一些新的排错工具:

  • IPTables 跟踪排错
  • 本地路由 排错
  • 一些网络相关的内核参数设置。

Iptables 跟踪排错

说到Iptables 排错,我不得不拿出这张逻辑非常清晰的图出来,建议Iptables 排错时常常对照下这张图,看下数据包的传递路径。在我之前的IPtables 知识范畴里,我以为它多个表之间传递时是没有路由选择这个操作的,结果实际的排错加上这样图来看。原来在不同的table 之间可能经过Routing decision.

请参考我的这篇K8s Issue 中的排错过程。

Netfilter-packet-flow.svg

然后我不得不说下Iptables 的TRACE Target,没有了解到这个Target之前,我用LOG Target,结果发现要写好多个IPtables你也不一定能跟踪的全每个包经过的策略,以及策略如何处理的。

我目前演示的在ubuntu 上面:

######### 检查是TRACE相关的mod 是否载入
modprobe nf_log_ipv4

########## TRACE Target 只能应用于RAW Table

sudo iptables -t raw -I PREROUTING -p tcp -m tcp --dport 8081   -j TRACE
sudo iptables -t raw -I OUTPUT -p tcp -m tcp --dport 8081   -j TRACE

########### grep TRACE in /var/log/kern.log

grep TRACE /var/log/kern.log

ubuntu@ceph3:~$ grep TRACE /var/log/kern.log|grep 2213090174
May  8 16:30:29 ceph3 kernel: [324781.838361] TRACE: raw:OUTPUT:policy:2 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.43.206.251 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838389] TRACE: nat:OUTPUT:rule:1 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.43.206.251 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838417] TRACE: nat:KUBE-SERVICES:rule:9 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.43.206.251 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838439] TRACE: nat:KUBE-SVC-ZP4VKUJYTBCROZYY:rule:1 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.43.206.251 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838454] TRACE: nat:KUBE-SEP-OR6JECCPPINGGGRC:rule:2 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.43.206.251 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838479] TRACE: filter:OUTPUT:rule:1 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838493] TRACE: filter:KUBE-SERVICES:return:2 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838505] TRACE: filter:OUTPUT:rule:2 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838518] TRACE: filter:KUBE-FIREWALL:return:2 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838531] TRACE: filter:OUTPUT:rule:4 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838551] TRACE: filter:OUTPUT:policy:6 IN= OUT=enp3s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838564] TRACE: nat:POSTROUTING:rule:1 IN= OUT=enp5s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838577] TRACE: nat:KUBE-POSTROUTING:return:2 IN= OUT=enp5s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838589] TRACE: nat:POSTROUTING:rule:8 IN= OUT=enp5s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000
May  8 16:30:29 ceph3 kernel: [324781.838609] TRACE: nat:POSTROUTING:policy:10 IN= OUT=enp5s0 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57266 DF PROTO=TCP SPT=18130 DPT=8081 SEQ=2213090174 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04D5CCCC0000000001030307) UID=1000 GID=1000

解释一下,每个trace 会记录表名字比如raw:OUTPUT:policy:2或者nat:OUTPUT:rule:1,表明为Table:Chain:显式策略为Rule,Table默认策略为Policy:rule 编号。我一般用grep ID=57266 这种方法去过滤同一个包。

以上都抓取的iptables 的日志,如果中间遇到路由问题呢,比如我这个问题包日志如下,包到了nat:PREROUTING:policy:3 就没有下文了,本来应该继续进mangle:INPUT 或者filter:INPUT,结果都没有,参考以上数据包图,可以发现这里有个route decision的过程。那么接下来我看看如果排除本地路由的问题。

ubuntu@ceph2:~$ grep TRACE /var/log/kern.log|grep 1726587944
May  8 15:51:07 ceph2 kernel: [309854.514762] TRACE: raw:PREROUTING:policy:2 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514799] TRACE: nat:PREROUTING:rule:1 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514841] TRACE: nat:KUBE-SERVICES:rule:13 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514861] TRACE: nat:KUBE-NODEPORTS:return:1 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514881] TRACE: nat:KUBE-SERVICES:return:14 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514897] TRACE: nat:PREROUTING:rule:2 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514914] TRACE: nat:DOCKER:return:2 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)
May  8 15:51:07 ceph2 kernel: [309854.514930] TRACE: nat:PREROUTING:policy:3 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)

本地路由 排错

一般情况下本地路由并没有多少条,所以一般传统方法是逐条对下路由条目,然后人工判断最终会丢到那里,如果没有发现路由能处理,就会被DROP了。新版本的linux 上用ip rule 和ip route 显示和操作路由表,ip 属于iproute2 包中的套件,后面大致看了下文档,才发现有种还有这种操作的的感觉。

######## ip rule to list ip route tables
ubuntu@ceph2:~$ ip rule
0:      from all lookup local
32766:  from all lookup main
32767:  from all lookup default

######## 这里有三个table ,先查0编号的local,然后查32766的main,然后查32767的default
######## 列出每个table 中的rule
ubuntu@ceph2:~$ ip route list table local
broadcast 10.0.1.0 dev enp5s0  proto kernel  scope link  src 10.0.1.12
local 10.0.1.12 dev enp5s0  proto kernel  scope host  src 10.0.1.12
broadcast 10.0.1.255 dev enp5s0  proto kernel  scope link  src 10.0.1.12
local 10.42.2.0 dev flannel.1  proto kernel  scope host  src 10.42.2.0
broadcast 10.42.2.0 dev cni0  proto kernel  scope link  src 10.42.2.1
local 10.42.2.1 dev cni0  proto kernel  scope host  src 10.42.2.1
broadcast 10.42.2.255 dev cni0  proto kernel  scope link  src 10.42.2.1
broadcast 127.0.0.0 dev lo  proto kernel  scope link  src 127.0.0.1
local 127.0.0.0/8 dev lo  proto kernel  scope host  src 127.0.0.1
local 127.0.0.1 dev lo  proto kernel  scope host  src 127.0.0.1
broadcast 127.255.255.255 dev lo  proto kernel  scope link  src 127.0.0.1
broadcast 172.17.0.0 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown
local 172.17.0.1 dev docker0  proto kernel  scope host  src 172.17.0.1
broadcast 172.17.255.255 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown
broadcast 192.168.235.0 dev enp3s0  proto kernel  scope link  src 192.168.235.12
local 192.168.235.12 dev enp3s0  proto kernel  scope host  src 192.168.235.12
broadcast 192.168.235.255 dev enp3s0  proto kernel  scope link  src 192.168.235.12

ubuntu@ceph2:~$ ip route list table main
default via 192.168.235.2 dev enp3s0 onlink
10.0.1.0/24 dev enp5s0  proto kernel  scope link  src 10.0.1.12
10.42.0.0/24 via 10.42.0.0 dev flannel.1 onlink
10.42.1.0/24 via 10.42.1.0 dev flannel.1 onlink
10.42.2.0/24 dev cni0  proto kernel  scope link  src 10.42.2.1
10.42.3.0/24 via 10.42.3.0 dev flannel.1 onlink
10.42.4.0/24 via 10.42.4.0 dev flannel.1 onlink
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown
192.168.235.0/24 dev enp3s0  proto kernel  scope link  src 192.168.235.12

ubuntu@ceph2:~$ ip route list table default
ubuntu@ceph2:~$

前面说了一般方法是逐条对路由来看路由条目对特定的包是否有规则对应,但这种方法需要你对路由规则非常熟悉,而且人工容易判断漏。那么这里介绍一个测试路由规则的命令ip route get

###### 偷懒摘抄下man 8 ip 里的说明
ip route get - get a single route

this command gets a single route to a destination and prints its contents exactly as the kernel sees it.

to ADDRESS (default) #the destination address.
from ADDRESS #the source address.
tos TOS
dsfield TOS # TOS=the Type Of Service.
iif NAME #the device from which this packet is expected to arrive.
oif NAME #force the output device on which this packet will be routed.

以上面iptables 跟踪部分的这条日志为例

May  8 15:51:07 ceph2 kernel: [309854.514930] TRACE: nat:PREROUTING:policy:3 IN=enp5s0 OUT= MAC=00:23:7d:5b:96:ec:00:21:5a:ef:39:fe:08:00 SRC=192.168.235.13 DST=10.0.1.12 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28265 DF PROTO=TCP SPT=14024 DPT=8081 SEQ=1726587944 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A04CCCA410000000001030307)

假设我们要判断这个包的路由选择会匹配哪条路由,我们可以下面命令来看,初一看结果会第一反应是不是我命令语法有问题报错呢?来看看我当时参考了rp_filter_kernel_setting后的运行结果

ubuntu@ceph2:~$ ip route get from 192.168.235.13 to 10.0.1.12 iif enp5s0 tos 0x00
RTNETLINK answers: Invalid cross-device link

######### change rp_filter kernel setting
ubuntu@ceph2:~$ sudo bash
root@ceph2:~# echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter
root@ceph2:~# echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter

######### Ok 在更改rp_filter 内核参数后,我们的同样的命令有匹配结果了。

root@ceph2:~# ip route get from 192.168.235.13 to 10.0.1.12 iif enp5s0 tos 0x00
local 10.0.1.12 from 192.168.235.13 dev lo
    cache <local>  iif enp5s0
root@ceph2:~#

到这里我想你可以再尝试一些其他的ip route get 命令来连连手,看看输出结果,比如

ubuntu@ceph2:~$ ip route get from 172.18.0.3 to 10.0.1.12
RTNETLINK answers: Invalid argument # 本机根本没法从172.18.0.3 路由到10.0.1.12
ubuntu@ceph2:~$ ip route get from 192.168.235.3 to 10.0.1.12
RTNETLINK answers: Invalid argument # 本机也没法从192.168.235.3 路由到10.0.1.12
ubuntu@ceph2:~$ ip route get from 192.168.235.12 to 10.0.1.12 #从本地的一个卡为192.168.235.12 可以从lo 上路由到10.0.1.12
local 10.0.1.12 from 192.168.235.12 dev lo
    cache <local>
ubuntu@ceph2:~$ ip route get from 192.168.235.12 to 10.0.1.13 #从本地的一个卡为192.168.235.12 可以从enp5s0 上路由到10.0.1.13(另外一个主机)
10.0.1.13 from 192.168.235.12 dev enp5s0
    cache

最后我们详细解读下路由规则的显示意思,具体可以参考【iproute2 doc】

以这条比较长的broadcast 10.0.1.0 dev enp5s0 proto kernel scope link src 10.0.1.12为例

  • broadcast 10.0.1.0 第一个为路由类型,可以为broadcast,unicast,local等等,如果不写,则为unicast,10.0.1.0 为目的网络。
  • dev enp5s0 这代表出去的时候走网卡enp5s0
  • via 10.42.3.0 你可能在有些规则中看到这句,代表下一跳网关是10.42.3.0
  • proto kernel 路由协议是kernel,由kernel 生成。
  • scope link 该地址只在该link 上有效
  • src 10.0.1.12 源Ip为10.0.1.12, 这里的10.0.1.12 必须在本地的网卡地址上能找到
  • onlink 假装下一跳的网关在这个link上 。

一些网络相关的内核参数设置

OK,快到最后不得不提下linux 的内核参数设置,这些参数能在内核中可以设置,往往是提炼了又提炼的精华部分。那么问题来了?

  • Q : 我怎么知道哪些参数是我需要的呢?

    A : linux的内核文档中会对这些参数加以详细描述,因此我们可以阅读内核文档,比如和IP相关的参数,来寻找我们可能需要的参数,我的思路是通过自己觉得有可能的内核参数名去搜索互联网,然后看结果中别人使用这个参数具体解决了什么问题。

  • Q: 那里找到linux 内核文档?

    A: 以ubuntu 为例,linux-doc 是当前kernel的文档包,安装后的文件在/usr/share/doc/linux-doc/主目录下,可以 dpkg -L linux-doc查看寻找所需文档。比如zcat /usr/share/doc/linux-doc/networking/ip-sysctl.txt.gz可以阅读网络相关内核参数的文档。

几个重要的内核参数

  • rp_filter 设置为2时会针对所有网卡匹配包的src,如果匹配则路由,设置为1时,如果包经过的网卡发现返回路径不是最优,则丢弃包。

    net.ipv4.conf.default.rp_filter = 2

    net.ipv4.conf.all.rp_filter=2

  • log_martians Boolean 设置为enable 时,上面这种被内核认为是不可能的地址的时候,可以在内核日志中记录信息。

其他补充

  • 跨主机时,抓包工具是补充。
  • 知识需要扩散、深挖,欢迎补充。