从一个问题开始,业务上需要使用Tcpreplay – Pcap editing and replaying utilities (appneta.com)或者TRex (cisco.com)这类工具来回放抓到的pcap包,并让流量通过DUT(被测设备),测试当流量经过DUT时,功能是否正常。
有几种模式,我们先讲最简单的情况
loopback(自环模式)
在此场景下,我们发现通过使用trex工具回放pcap,同时模拟client/server,网络是正常联通的,可以正常收发包。
这里的意思是
vm有两个网口
eth0: ip为 177.0.0.1 gw为178.0.0.1
eth1: ip为 178.0.0.1 gw为177.0.0.1
从eth0发出的包为client,ip段为173.0.0.2 – 173.0.0.254
从eth1发出的包为server, ip段为173.0.0.2 – 173.0.0.254
因为是两条线直连,所以网络是通的
vrouter
Case1:为啥需要静态路由
vrouter下的拓扑配置如下
这里的意思是
vrouter,有两个网口
eth0: ip为 177.0.0.1 掩码是8位,包含了整个177的子网段
eth1: ip为 178.0.0.1 掩码是8位,包含了整个178的子网段
虚拟机vm,含两张网卡
eth0:ip为177.0.0.10,网关是和路由器相连的ip
eth1:ip为178.0.0.10,网关是和路由器相连的ip
使用trex回放包时(回放包中的mac地址为原始数据包mac地址未改变,且和vrouter网卡的mac不一样,也非广播地址)
从eth0发出的包为client,ip段为173.0.0.2 – 173.0.0.254
从eth1发出的包为server, ip段为173.0.0.2 – 173.0.0.254
现在出现的问题是,网络不通,包没有被vrouter转发,
在vrouter的eth0抓包,可以看到client的包,但没有server。从eth1抓包,只有server,没有client
架构师看了后说,这个拓扑图网络不通,琢磨了下,然后在路由器上配了如下两条静态路由。
Router# configure terminal
Router(config)# ip route 174.0.0.0 255.0.0.0 178.0.0.10
Router(config)# ip route 173.0.0.0 255.0.0.0 177.0.0.10
然后,网络就通了….
架构师给我大概解释了下,我似懂非懂…,记下了这个问题。
Case2: 向VPC内的虚拟机回放pcap,目的ip、mac地址应该填啥
VM1是一个VPC子网下的一台虚拟机
VM2是另一个VPC子网下得一台虚拟机,内网ip是192.168.0.2,公网ip是7.7.7.7,公网IP由Router2通过NAT实现
使用tcpreplay工具,VM1向VM2回放pcap流量,该工具要求我们填写源、目ip及mac地址
源ip、mac填VM1对应的值(这一点大家应该没有疑惑)
那么目的方向呢?
目的ip填vm2上的公网ip(即DNAT前的7.7.7.7),目的mac填Router1上eth0的mac地址
目的ip如果填错,packet无法正确送达vm2
目的mac填错,packet会在Router1上被丢包
其他情况:
- 目的端填Router1的ip和mac,会connection failed(因为没有对应端口)
- 目的端填成VM2的内网ip(192.168.0.2),packet会在Router1丢包,因为不知道该怎么转发
这两个案例的问题类似,涉及包的转发流程是怎样的,都在这上面踩过坑,花了些时间大概弄明白了,参见下述。
为什么
我们似乎得了解路由器的工作原理,什么情况下该包会被转发,什么情况下会丢包。
基于此,我们才能做出正确的解释。
先讲Case2,这个简单一些
为什么需要保证mac地址正确
我们先看vm1—-sw1—Router1是怎么转发的
假设目的mac地址是填的vm2的mac地址
- vm1先向sw1转发packet
-
sw1收包后查看mac地址,发现sw1上本地的arp中未找到该mac地址,因此不知道该转给谁,于是sw1向和它相连的除发包端所有设备发送该packet
这种行为叫做Unkown Unicast,看起来和Broadcast类似,但是和Broadcast最主要的区别是Broadcast中packet的目的mac为广播地址,即
FF:FF:FF:FF:FF:FF
,而Unknown Unicast中目的mac地址非广播地址/多播地址。 -
路由器收到该包
路由器对于这两种”广播“packet的行为是不同的
- 对于”Unknown Unicast“这类packet的处理,路由器/接收端拆包后发现mac地址不对会将包给drop掉。
具体的逻辑在这里,以e1000网卡为例,linux5.10
e1000_clean_rx_irq
|-e1000_rx_checksum
|-e1000_rx_hash
|-e1000_receive_skb
|-eth_type_trans #根据mac地址设置路由的方向,本地/多播/广播/other
|-napi_gro_receive (net/core/gro.c)
|-napi_skb_finish
|-dev_gro_receive
|-inet_gro_receive
|-tcp/udp_gro_revice 协议层的一些预处理
|-napi_gro_complete
|-gro_normal_one
|-gro_normal_list
|-netif_receive_skb_list_internal---(RPS)-->
|-__netif_receive_skb_list
|-__netif_receive_skb_list_core
|-__netif_receive_skb_core
|-__netif_receive_skb_list_ptype-
|-ip_list_rcv/ip_rcv
|-ip_rcv_core
|-if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
- 对于multicast/broadcast这类包,路由器的处理待我研究下…(TODO)
为什么同样是回放包,但是需要配置静态路由?
这里有一个问题: Case1中回放包时vm(eth0)—vr(eth0)这条路径上,trex回放pcap时目的mac地址是填的什么呢,
通过抓包发现填的是vr中eth0的mac地址,这一点和tcpreplay还略有不同,tcpreplay中填的是我们自己覆写的mac地址,所以会导致case2中的丢包。(可能tcpreplay也支持这个模式,等我琢磨下,TODO)
好,现在我们看为什么需要手动配置一条路由表
路由的工作原理可以参见RFC1812,这里我引一些关键信息和大家一起解读下
路由的大概流程是
- 路由器从链路层上收到包
-
路由器校验ip头
-
对于IP option中字段的处理
-
根据目的ip地址判断是转发还是自己处理
- 目的地址是自己,需自己处理,完成ip重组(如果需要的话)
- 目的地址不是自己,转发给其他(如何知道转发给谁呢?)
-
目的地址不是自己,需要转发,但同时将包拷贝一份留给自己处理
转发流程
-
查找路由表,检查是否有符合要求的下一跳地址(具体检查过程见下述)
-
校验包是否有效
-
修改TTL
-
处理IP Option(哪些不能在路由前完成的操作)
-
如果需要的话,对包进行分片(可能是因为interface对于MTU的要求不同)
-
找到下一跳的mac地址
-
重新封包(将目的mac地址改为下一跳的mac地址)
-
如果有必要的话,发送ICMP Redirect消息
检查下一跳的路由算法
参见5.2.4,核心流程
- 检查目的地址是否是该Router上相连接口的IP地址
-
若不是,则检查路由表,寻找下一跳的路由器
查询路由表的算法一般可以用最初前缀匹配
#destination ip: (1) 192.168.1.2 (2) 191.168.1.2 # route a 192.168.1.1/24 b 192.168.0.1/16 c 192.0.0.1/8
以上面这个例子为例,
- 匹配192,168.1.2时,最长前缀算法会讲a,匹配出来,选a为路由
- 匹配191,168.1.2时,没有匹配的路由,就会被丢包
-
如果由多条匹配的路由,会按照下列路由优先级排序选择
(1) Host Route: This is a route to a specific end system. (2) Hierarchical Network Prefix Routes: This is a route to a particular network prefix. Note that the FIB may contain several routes to network prefixes that subsume each other (one prefix is the other prefix with additional bits). These are selected in order of decreasing prefix length. (3) Default Route: This is a route to all networks for which there are no explicit routes. It is by definition the route whose prefix length is zero.
我们回看Case1
以Client 173.0.0.10 Server 174.0.0.10为例进行分析
在vrouter上进入后台,我们应该可以看到只有两条路由
Router# configure terminal
Router(config)# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
I - ISIS, B - BGP, H - host, > - selected route, * - FIB route
C>* 177.0.0.0/8 is directly connected, 50191abb-0bf7-4437-81ca-0d47ca7c4ffb, weight 1
H>* 177.0.0.1/32 [0/0] is directly connected, 50191abb-0bf7-4437-81ca-0d47ca7c4ffb, weight 1
C>* 178.0.0.0/8 is directly connected, 601af182-6864-4540-a26d-b84cb7d2ad56, weight 1
H>* 178.0.0.1/32 [0/0] is directly connected, 601af182-6864-4540-a26d-b84cb7d2ad56, weight 1
Request方向,目的地址为174.0.0.10,进行前缀匹配,发现无法匹配,丢包。
Response方向同理
在知道是因为无法找到匹配的下一跳地址后,我们就手动指定静态路由即可
- 让目的地址174.0.0.1/8 下一跳都指定为178.0.0.10
- 让目的地址173.0.0.1/8 下一跳都指定为177.0.0.10
在指定路由后就能正确路由到下一跳地址了
其他问题
遇到了个奇怪的问题,接下来花时间研究下
1. vm(eth0) ping vr(eth0) 正常
2. vm(eth1) ping vr(eth1) 正常
3. vm(eth0) ping vr(eth1) 100%丢包,但是可以在vm(eth0)处抓包看到vr(eth1)的确已经回包,并且iptables也记录了manle的PREROUTING记录,似乎是在Route时丢了包,还不知道为啥
后记
写得不够清晰,或者说有些匆忙,没写出我所期望的效果,算了,先写出来吧,免得后面不写了
留了一些坑,后面补
- 这里留了三个TODO待确认
- 还想列一下正常的包转发是怎样,但是自己搭环境时遇到了其他问题
《数据包流转—包回放遇到的一些问题》有1个想法