通过一个云平台故障案例了解数据包在iptables中的流向

以下案例是基于一个cloudstack虚拟化云平台为例子,阅读者无须有相关知识背景,只需要记住这个概念;动手分一分,抓包析一析。一起来了解下数据包在iptables中是如何流淌的吧。

1.故障现象:

云虚拟机情况简单描述:

  • 云平台一般会提供一个虚拟机ssh 登录方式为: ssh 公网IP:大端口
  • 大客户会有一个私网网段和一个VR(虚拟路由器)

故障现象:客户通过客服保障说:无法通过ssh方式登录。

ssh 6.6.6.120 10174 转发到内部 10.1.1.69 22

作为运维人员:我做了以下的故障检测

  1. 尝试通过电信联通移动等不同运营商进行ssh登录,果然无法登录
  2. 询问客户该机器提供的服务是否正常(正常)
  3. 让客户使用同网段(私网)的其他虚拟机 ssh登录:结果正常
  4. 询问客户做了那些变更操作(关键点)

客户绑定了一个公网IP 10.1.1.69---6.6.6.82,并且只是放行了tcp端口300--65535

2.故障分析

云平台现在使用的是SDN(软件定义网络),简单的说,就是拿linux服务器作为这个路由网关设备,然后通过iptables规则,来做数据包的转发。

故障分析,无非就是分析数据包的走向,结合iptable数据包转发顺序和路由表这两块内容来分析,最好要注意这个转发的接口问题。

动手分一分,抓包析一析。回头一看,如此简单粗暴。

2.1iptables4表5链的知识普及

iptables 4表五链的数据包流向顺序,这里借助鸟哥的图,多谢鸟哥。 four_table_five_chain

从上图可以很清晰的看到数据包在规则链中的流动路径: `数据包先经过mangle表的PREROUTING链,然后进入nat表的PREROUTING链,然后路由进行判定:

  1. 如果目标是本机则进入mangle表的INPUT链,在进入filter表的INPUT链到达本机(应用层接受数据), 最后数据经由mangle表的OUTPUT链,nat表OUTPUT链,filter表OUTPUT链
  2. 如果目标不是本机且开启了forward则数据包进入mangle表FORWARD链,然后到filter表FORWARD链 最终都到达mangle表POSTROUTING链,经过nat表POSTROUTING链传出`

根据上图的数据包的流动情况现在我们可以很容易的添加规则到对应的链路来满足我们的需求,下面是我们常用的一些场景:

  • 修改数据包的来源ip地址及端口可以在nat表的PREROUTING链添加规则 - 过滤未授权及非法ip来源及端口的数据包到达本机可以在filter表的INPUT链添加规则
  • 过滤未授权及非法ip来源及端口的数据包forward可以在filter表的FORWARD链添加规则
  • 修改出口数据包的ip地址及端口可以在nat表的POSTROUTING表添加规则(充当代理服务器)

上面的图示很复杂喔!不过基本上你依旧可以看出来,我们的 iptables 可以控制三种封包的流向:

  • 封包进入 Linux 主机使用资源 (路径 A): 在路由判断后确定是向 Linux 主机要求数据的封包,主要就会透过 filter 的 INPUT 链来进行控管;
  • 封包经由 Linux 主机的转递,没有使用主机资源,而是向后端主机流动 (路径 B): 在路由判断之前进行封包表头的修订作业后,发现到封包主要是要透过防火墙而去后端,此时封包就会透过路径 B 来跑动。 也就是说,该封包的目标并非我们的 Linux 本机。主要经过的链是 filter 的 FORWARD 以及 nat 的 POSTROUTING, PREROUTING。
  • 封包由 Linux 本机发送出去 (路径 C): 例如响应客户端的要求,或者是 Linux 本机主动送出的封包,都是透过路径 C 来跑的。先是透过路由判断, 决定了输出的路径后,再透过 filter 的 OUTPUT 链来传送的!当然,最终还是会经过 nat 的 POSTROUTING 链。

由于 mangle 这个表格很少被使用,如果将上图 的 mangle 拿掉的话,那就容易看的多了: three_table_five_chain

一般来说我们接触并使用最多的是

  1. filter表的INPUT链
  2. nat表的PREROUTING,POSTROUTING链

2.2测试使用到的iptables命令和tcpdump知识

iptables -t nat -L -n --line-numbers -v -x
-v:显示接口名字,包数量和包总量 人性化显示
-x:详细数据包信息,K显示
-L: 列出规则
-n:不做域名解析
--line-numbers:行号
tcpdump  -nn -i dev  dst port
-nn :不做ip:port的域名解析
-i dev:抓取指定的接口
dst port:抓取目标端口为
常用的组合为:
dst host
src host
src port
dst port
可以使用 and 和or 组合

特别注意:tcpdump默认抓取的eth0接口的数据包,如果是其他接口的数据包,一定要手动指定接口

2.3SDN网络架构

简单文字描述 外网—>一级VR—>二级VR–>虚拟机

这里面使用到了共二层转发,因此中间有一个过渡IP 图解: sdn-network

2.4数据包流向分析(通过iptables和tcpdump)

注意:这套环境仅仅没有使用raw表,所以使用mangle,filter和nat表分析。

实战分析:

本机ip为8.8.8.118

目标虚拟机的公网ip和端口为 6.6.6.120 10174

IP 8.8.8.118. 20147> 6.6.6.120 10174

使用tcpdump抓包的目的:

  1. 验证分析iptables规则的正确性
  2. 强化练习使用tcpdump命令

一举两得,何乐而不为呢?

2.4.1分析数据包在1级VR中的走向

抓包分析的时候不断ssh测试

大概如下:

Xshell:\> ssh 6.6.6.120 10174


Connecting to 6.6.6.120:10174...
Could not connect to '6.6.6.120' (port 10174): Connection failed.

Type `help' to learn how to use Xshell prompt.

查看1级VR的网络接口配置

eth0:172.17.21.2/24 
	172.17.21.1/24 secondary
eth2:6.6.6.120/27

抓包的规则:

  1. 进入VR之后,抓包
  2. 在数据包做了nat转换之后再抓取数据包

在一级VR抓包(连续尝试发送3个数据包)

root@InternetGateway:~# tcpdump -nn  -i eth2 host 8.8.8.118
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes
17:22:14.030955 IP 8.8.8.118.22449 > 6.6.6.120.10174: Flags [S], seq 1921748684, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
17:22:17.036260 IP 8.8.8.118.22449 > 6.6.6.120.10174: Flags [S], seq 1921748684, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
17:22:22.971443 IP 8.8.8.118.22449 > 6.6.6.120.10174: Flags [S], seq 1921748684, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length

分析数据包:单向的,而且已经在一级VR做了目标IP的nat转换,很明显,包是有来无回。

下面分析在一级VR的防火墙链条上面如何转换 1)mangle表PREROUTING

分析:

  1. 主链上有2条自定义链匹配到了规则,跳转到自定义链,结果是全放行
  2. 回到主链,匹配到了做打标记的规则,mangle链其实主要用途就是打标记
  3. PREROUTING默认是accept
  4. 数据包放行,仅仅打了个标记
root@InternetGateway:~# iptables -t mangle -L -n
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
VPN_6.6.6.120  all  --  0.0.0.0/0            6.6.6.120  #首先匹配到了自定义链    
FIREWALL_6.6.6.120  all  --  0.0.0.0/0            6.6.6.120   
MARK       tcp  --  0.0.0.0/0            6.6.6.120       tcp dpt:10174 MARK set 0x2
CONNMARK   tcp  --  0.0.0.0/0            6.6.6.120       tcp dpt:10174 state NEW CONNMARK save


Chain FIREWALL_6.6.6.120 (1 references)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0      


Chain VPN_6.6.6.120 (1 references)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

2)nat表PREROUTING 分析:

  1. 匹配到了DNAT规则,做DNAT的转换
  2. 8.8.8.118. 20147> 6.6.6.120 10174 转化为了如下 8.8.8.118. 20147> 172.17.21.105.22
  3. 正如上面抓包所示,目标地址在nat的prerouting链上做了DNAT转换
root@InternetGateway:~# iptables -t nat -L -n -v
下面列出前后两次,明显有数据包通过
Chain PREROUTING (policy ACCEPT 61147 packets, 3881K bytes)
 pkts bytes target     prot opt in     out     source               destination
131  6938 DNAT       tcp  --  eth2   *       0.0.0.0/0            6.6.6.120       tcp dpt:10174 to:172.17.21.105:22
  132  6990 DNAT       tcp  --  eth2   *       0.0.0.0/0            6.6.6.120       tcp dpt:10174 to:172.17.21.105:22

目标地址变化了,目标地址是本机eth0接口相同网段,因此抓取eth0接口

root@InternetGateway:~# tcpdump -nn -i eth0 host 8.8.8.118
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
16:45:12.260383 IP 8.8.8.118.20147 > 172.17.21.105.22: Flags [S], seq 4195582156, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
16:45:15.365081 IP 8.8.8.118.20147 > 172.17.21.105.22: Flags [S], seq 4195582156, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
16:45:21.501045 IP 8.8.8.118.20147 > 172.17.21.105.22: Flags [S], seq 4195582156, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0

3)根据路由表转发

目前数据包为8.8.8.118. 20147> 172.17.21.105.22

root@InternetGateway:~# route -n
172.17.21.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

根据数据包和路由表,数据包不是发往本机的,但是和本机eth0接口IP是同个网段,因此从eth0接口发送出去(forwad方向)

4)mangle表forward 空

5)filter表forward 分析:

  1. 数据包事实上是从eth2接口进来的,从eth0口出去
  2. 这里的规则非常精髓,好好研究
  3. 数据包的state是属于NEW,匹配到了源地址0.0.0.0/0,目标地址为172.17.21.105:22
  4. 可以转发出去
root@InternetGateway:~# iptables  -L -n -v -x
Chain FORWARD (policy DROP 43 packets, 2851 bytes)
    pkts      bytes target     prot opt in     out     source               destination         
       0        0 ACCEPT     all  --  eth0   eth1    0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    9251   551124 ACCEPT     all  --  eth0   eth0    0.0.0.0/0            0.0.0.0/0            state NEW
 3656216 7131393506 ACCEPT     all  --  eth0   eth0    0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
2575287622 1255341360189 FW_OUTBOUND  all  --  eth0   eth2    0.0.0.0/0            0.0.0.0/0           
171494620 46905892260 ACCEPT     all  --  eth2   eth0    0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
       0        0 ACCEPT     all  --  eth0   eth2    0.0.0.0/0            0.0.0.0/0           
21807930 2178350475 ACCEPT     all  --  eth3   eth0    0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
20889774 57146716281 ACCEPT     all  --  eth0   eth3    0.0.0.0/0            0.0.0.0/0           
       0        0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        state RELATED,ESTABLISHED /* 6.6.6.120:10174:10174 */
  ` 21428  1209090 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        tcp dpt:22 state NEW /* 6.6.6.120:10174:10174 */`

这里开始我是也有点懵圈的,后面通过ssh去连接,发送的包的数量其实就是只有三个包得出结论。

抓包每次都是3个包

root@InternetGateway:~# tcpdump -nn  -i eth0 host 8.8.8.118
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
19:15:47.766651 IP 8.8.8.118.25546 > 172.17.21.105.22: Flags [S], seq 3657881821, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
19:15:50.722647 IP 8.8.8.118.25546 > 172.17.21.105.22: Flags [S], seq 3657881821, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
19:15:56.657900 IP 8.8.8.118.25546 > 172.17.21.105.22: Flags [S], seq 3657881821, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0

数据包数量3个

root@InternetGateway:~# iptables  -L -n -v -x | grep 10174
       0        0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        state RELATED,ESTABLISHED /* 6.6.6.120:10174:10174 */
   21435  1209442 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        tcp dpt:22 state NEW /* 6.6.6.120:10174:10174 */
root@InternetGateway:~# iptables  -L -n -v -x | grep 10174
       0        0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        state RELATED,ESTABLISHED /* 6.6.6.120:10174:10174 */
   21438  1209598 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.17.21.105        tcp dpt:22 state NEW /* 6.6.6.120:10174:10174 */

6)mangle表postrouting

root@InternetGateway:~# iptables  -t mangle -L -n -v -x
Chain POSTROUTING (policy ACCEPT 2829042 packets, 2712854044 bytes)
    pkts      bytes target     prot opt in     out     source               destination         
       1       66 CHECKSUM   udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:68 CHECKSUM fill

默认规则放行

数据包依然是:

IP 8.8.8.118.12604 > 172.17.21.105.22

7)nat表postrouting

root@InternetGateway:~# iptables  -t nat -L -n -v -x
Chain POSTROUTING (policy ACCEPT 66701 packets, 4451479 bytes)

走默认规则,accept

数据包依然是:

IP 8.8.8.118.12604 > 172.17.21.105.22

到这里为止:数据包已经走完了在1级VR的最后一段旅途,再也回不来了。 下面进行2级VR的操作。

2.4.2分析数据包在2级VR中的走向

ip地址

eth1:10.1.1.1/24
eth2:172.17.21.105/24

路由表

root@r-588-VM:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.21.1     0.0.0.0         UG    0      0        0 eth2
10.1.1.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     0      0        0 eth1
172.17.21.0     0.0.0.0         255.255.255.0   U     0      0        0 eth2

抓包测试 数据包到二级VR之后,就没有回包,问题可能就出在这里。

root@r-588-VM:~# tcpdump -i eth2 -nn  host 8.8.8.118 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes
11:28:43.771665 IP 8.8.8.118.25706 > 172.17.21.105.22: Flags [S], seq 1049927226, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
11:28:46.730812 IP 8.8.8.118.25706 > 172.17.21.105.22: Flags [S], seq 1049927226, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
11:28:52.752525 IP 8.8.8.118.25706 > 172.17.21.105.22: Flags [S], seq 1049927226, win 65535, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0

1)mangle表 prerouting 分析:

  1. 数据包进来,匹配到了prerouting链上面的自定义链
  2. 注意是NEW包,自定义链的第一条规则不会匹配
  3. 最终匹配到了DROP的规则,数据包在这里丢弃,数据包在这里就丢弃了
root@r-588-VM:~# iptables -t mangle -L -n -v -x
Chain PREROUTING (policy ACCEPT 5161795 packets, 2771007577 bytes)
pkts      bytes target     prot opt in     out     source               destination
68331 27713097 FIREWALL_172.17.21.105  all  --  *      *       0.0.0.0/0            172.17.21.105


Chain FIREWALL_172.17.21.105 (1 references)
    pkts      bytes target     prot opt in     out     source               destination         
   34652 25511785 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    6766   285688 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpts:300:65535
    2364   524301 RETURN     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpts:300:65535
     792    38817 RETURN     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 code 0
   23757  1352506 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0  

既然发现了问题,自然要尝试去解决问题了。下面继续分析

2.4.3分析ssh 6.6.6.120 10174 如何转发到内部 10.1.1.69 22

这里要继续2.4.2分析数据包在2级VR的走向的章节。 1)nat表 prerouting 分析:

  1. 数据包是从eth2口进来的,自然匹配到eth2
  2. 而且这里的DNAT是全映射(由于在1级VR做的是22端口DNAT,其实也就相当端口DNAT)
  3. c)可惜啊,并没有啥用,因为前面mangle表的prerouting链规则已经把你抛弃了
root@r-588-VM:~# iptables -t nat -L -n -v -x 
Chain PREROUTING (policy ACCEPT 391687 packets, 16175211 bytes)
pkts      bytes target     prot opt in     out     source               destination
  11008   935157 DNAT       all  --  eth2   *       0.0.0.0/0            172.17.21.105        to:10.1.1.69
       0        0 DNAT       all  --  eth0   *       0.0.0.0/0            172.17.21.105        to:10.1.1.69

上面分析的其实是虚拟机默认分配的管理ip:端口,下面分析下判定公网ip的主要路由规则

2.5分析判定绑定公网IP的主要iptables规则

注意:这里不会那么详细,但是会列出所有需要的规则信息

2.5.1分析一级VR的主要规则

1)nat表的prerouting链

root@InternetGateway:~# iptables -t nat -L -n -x -v
Chain PREROUTING (policy ACCEPT 182136 packets, 11548087 bytes)
    pkts      bytes target     prot opt in     out     source               destination
   69847  4341308 DNAT       all  --  eth2   *       0.0.0.0/0            6.6.6.82        to:172.17.21.105
       0        0 DNAT       all  --  eth0   *       0.0.0.0/0            6.6.6.82        to:172.17.21.105

问题出现在这了

上面的管理ssh连接的转换时DNAT:port

源地址;端口 目标地址:端口 源地址:端口 转换后目标地址:端口
0.0.0.0/0 6.6.6.120:10174 0.0.0.0/0 172.17.21.105:22

绑定公网IP的做了DNAT的full nat

源地址 目标地址 源地址 转换后目标地址
0.0.0.0/0 6.6.6.82 0.0.0.0/0 172.17.21.105

由于两种iptables规则都转换到了同个系统IP172.17.21.105

这就造成了在二级VR的mangle链把目标是172.17.21.105的数据包不是tcp300-65535端口的全部drop掉。

6分析如何解决上面的故障

既然发现了这个问题,明显是一个大的bug问题,研发同志居然没有发现出来……

从一个运维的角度来思考如何解决这个问题,但是由于涉及到代码,具体怎么解决,还是要和研发沟通这个问题。

  主要用途 研发使用其用途 默认规则
mangle 打标记 公网IP绑定(放行端口策略) DROP
nat nat转换 管理端口nat转换(ssh rdp) ACCEPT

毕竟云架构比较复杂,牵一发动全身,建议使用最小改动的原则。

建议的解决方案1:

  1. 假如虚拟机绑定了公网IP,则管理端口的nat转换移动到mangle表的prerouting链上处理(在公网IP过滤规则之前处理)。
  2. 假如虚拟机没有绑定公网IP,则还是按照目前的方案处理。

这部分对代码的改动是比较少的,研发只需要在用户要绑定公网IP的时候做一个处理,把在nat表上管理端口的nat转换移动到mangle表上处理即可。

建议解决方案2:最合理的方式

对绑定的公网IP的虚拟机,如6.6.6.82这个IP,在一级VR上面做iptables过滤规则,根据上面客户的需求:把访问目标地址是6.6.6.82 tcp 300-65535的端口给过滤掉;二级VR的mangle表prerouting链的过滤规则删除掉

建议解决方案3:

  1. 管理用的ssh端口的映射分配 系统ip172.17.21.105
  2. 绑定公网IP的分配一个新的系统ip 如 172.17.21.212

参考

鸟哥的资料