Trace ESP Packet
一. 简介
本文详细分析一个ESP Packet在协议栈的input方向和output方向是如何处理的。先讲述相对简单的output方向,再讲述复杂一点的input方向。
本文仅仅涉及重要的函数名,用以显示调用关系与注册时机,不会陷入到具体的实现细节之中。
1.1 网络拓扑
本节中将使用传统的站点到站点的业务拓扑:
moon站点保护子网10.1.0.0/16,其中Alice是一个主机,IP地址为10.1.0.10/16;
sun站点保护子网10.2.0.0/16,其中Bob是一个主机,IP地址为10.2.0.10/16;
1 | 10.1.0.0/16 -- | 192.168.0.1 | === | 192.168.0.2 | -- 10.2.0.0/16 |
使用Alice ping Bob,我们在moon站点的192.168.0.1接口抓包。
1 | moon:~# tcpdump -ni eth0 esp or icmp |
我们也将按照这三个分组的时间顺序,来分析协议栈是如何处理数据包的。
1.2 先觉条件
了解IP、ESP的报文格式,可以去参考链接中熟悉一下。
1.3 参考链接
二. OUTPUT方向
OUTPUT方向是针对moon站点来说的,moon站点将向sun站点发送ESP分组。
我们是使用Alice ping Bob来产生ESP分组的。意味着moon站点的10.1.0.1接口将首先受到一个来自Alice设备的ICMP请求。源地址为10.1.0.10,目的地址为10.2.0.10。
1 | moon:~# tcpdump -ni eth1 icmp |
2.1 查路由
moon站点在执行ip_rcv_finish_core()
,在查路由的时候将ip_forward()注册到了input
中
1 | ip_rcv_finish() { |
2.2 ip_forward
OUTPUT方向上ESP的所有处理都将从ip_forward()
开始。
1 | ip_forward() { |
1. xfrm4_route_forward
在转发时,会在xfrm policy中查询分组是否需要进入XFRM流程。
此时,我们需要处理的分组是这样子的:
1 | 09:46:28.524684 IP 10.1.0.10 > 10.2.0.10: ICMP echo request, id 43193, seq 1, length 64 |
我们可以使用ip xfrm policy get src 10.1.0.0/16 dst 10.2.0.0/16 dir out
来查看当前XFRM策略
1 | moon:~# ip xfrm policy get src 10.1.0.0/16 dst 10.2.0.0/16 dir out |
可以发现,在OUT方向上,这个分组与xfrm policy是匹配的。
1 | ip_forward() { |
2. xfrm4_output
匹配到XFRM策略后,将进入XFRM框架。
我们可以使用ip xfrm policy get src 10.1.0.0/16 dst 10.2.0.0/16 dir out
来查看当前XFRM策略的templates:
1 | moon:~# ip xfrm policy get src 10.1.0.0/16 dst 10.2.0.0/16 dir out |
可以看到,这个策略的templates是tmpl src 192.168.0.1 dst 192.168.0.2
。
XFRM框架会将我们的Packet信息与tmpl进行匹配,最终找到或创建一个XFRM State实例。
通过这个XFRM State就可以找到afinfo注册的回调函数afinfo->output
。
这个output函数是在xfrm4_state_init()
的时候,通过xfrm_state_register_afinfo()
把xfrm4_state_afinfo
注册到了xfrm_state_afinfo
中。
而afinfo->output就是xfrm4_output
。
1 | xfrm_resolve_and_create_bundle() { |
3. xfrm_output
接下来进入xfrm4_output()
流程。
1 | xfrm4_output() { |
4. xfrm_output_one
在xfrm_output_one中,会根据xfrm state来对ICMP Packet作封装。
我们可以查看与这个Packet匹配的XFRM State,可以发现,mode为tunnel,意味着XFRM框架会对这个PACKET作一层外侧封装。
1 | 外层IP 内层IP |
1 | moon:~# ip xfrm state list src 192.168.0.1 dst 192.168.0.2 proto esp |
1 | xfrm_output() { |
5. xfrm_register_type
此时,我们已经为PACKET作了tunnel分装,接下来,将会进入ESP模块,对Packet进行加密处理。
1 | xfrm_output_one() { |
这个x->type->output是在ESP模块初始化时,通过xfrm_register_type()
注册到xfrm_state_get_afinfo(family)
中去的。
在创建XFRM State实例的时候,注册到了x->type字段。
1 | __xfrm_init_state() { |
那么,此时x->type->output(x, skb)
其实就是在调用esp_output()
6. esp_output
在此处,会在内层IP和外层IP之间插入ESP Header,并添加ESP tail,并对ESP有效载荷作加密处理。
7. __ip_local_out
至此,网络层的所有处理已经完成,将会调用协议栈的__ip_local_out()
将这个SKB发往下一层,这里就不是我们关注的重点了。
ESP在OUTPUT方向上经过的所有关键函数已经全部罗列了出来。
2.3 总结
moon站点在eth0接口收到一个ICMP报文,经过了XFRM协议栈,向sun站点发送一个ESP报文。至此,第一个ESP报文的生命流程已经解释完毕。
1 | moon:~# tcpdump -ni eth0 esp or icmp |
三. INPUT方向
moon站点向sun站点发送了ESP Packet,紧接着,收到了来自sun站点的回复。
1 | 09:30:24.460150 IP 192.168.0.2 > 192.168.0.1: ESP(spi=0xcbda26bb,seq=0x3), length 120 |
接下来,我们将分析这个Packet是如何进入moon站点的XFRM协议栈的。
3.1 ip_local_deliver_finish
同样,当Packet进入网络层,查询路由,发现目的地址为本地地址,于是进入ip_local_deliver函数继续将数据包发送至上一层。
1 | netif_receive_skb_list_internal() { |
3.2 xfrm4_esp_rcv
ip_protocol_deliver_rcu
根据ip_hdr(skb)->protocol来选择应该调用哪个处理函数来继续解析Packet。
由于IP层内是ESP Header,那么ip_hdr(skb)->protocol就是IPPROTO_ESP(50)。所以就会调用IPPROTO_ESP的hander处理函数xfrm4_esp_rcv。
xfrm4_esp_rcv通过xfrm4_protocol_register注册到inet_protos[IPPROTO_ESP]。
1 | ip_local_deliver_finish() { |
3.3 xfrm4_rcv
xfrm4_rcv通过xfrm4_protocol_register()
将esp4_protocol
注册到struct xfrm4_protocol
handler链表中。xfrm4_esp_rcv将遍历hander链表,执行处理函数。
1 | xfrm4_esp_rcv() { |
3.4 xfrm_input
xfrm_input函数是INPUT方向最核心的处理函数。
1 | xfrm4_rcv() { |
1. xfrm_state_lookup
此时,协议栈正在处理报文格式是这样的:
1 | 09:30:24.460150 IP 192.168.0.2 > 192.168.0.1: ESP(spi=0xcbda26bb,seq=0x3), length 120 |
XFRM框架将会把这个Packet和XFRM State进行对比,查找到匹配的XFRM State。
1 | moon:~# ip x s list src 192.168.0.2 dst 192.168.0.1 proto esp |
可以发现这个Packet匹配到了存储解密ESP方法的XFRM State。
至此,我们就可以通过这个XFRM State进入ESP模块,解密ESP消息。
1 | xfrm_input() { |
2. esp_input
所有解密ESP Packet的线索都存在了XFRM State中,包括了解密方法。
解密方法存储来type->input中。而type是在创建XFRM state时初始化的。
1 | __xfrm_init_state() { |
1 | xfrm_input() { |
在这个函数中,将解密ESP消息,并且剥离ESP header
和ESP tail
。
3. xfrm_inner_mode_input
至此,Packet的状态是这样的:
1 | 外层IP 内层IP |
xfrm_inner_mode_input()
将会脱去外层IP,我们就得到了一个从10.2.0.10发往10.1.0.10的ICMP Packet。这也就是第三个数据包:
1 | 09:30:24.462169 IP 10.2.0.10 > 10.1.0.10: ICMP echo reply, id 58139, seq 1, length 64 |
4. xfrm_rcv_cb
剥离ESP封装后,将进入xfrm_rcv_cb流程。
xfrm_rcv_cb通过xfrm_input_register_afinfo(&xfrm4_input_afinfo)
注册到afinfo中。
1 | xfrm_input() { |
接下来,分析xfrm4_rcv_cb
作了什么:
1 | xfrm4_rcv_cb() { |
其实esp4_rcv_cb中什么都没有作,仅仅直接返回了0。
5. gro_cells_receive
至此,我们已经完整地从ESP Packet中,解析到了ICMP Packet。接下来,需要让这个ICMP Packet重走协议栈即可。
1 | xfrm_input() { |
在这里,把新的ICMP Packet加入到cell->napi_skbs的队尾。并唤起NET_RX_SOFTIRQ中断。
3.5 总结
moon站点在eth0接口收到一个ESP报文,首先通过ip_local_deliver发现是ESP Packet,接着进入了XFRM协议栈,进行ESP解密、ESP解封装、tunnel解封装,把内层的IP Packet追加的napi_skbs的队尾,唤起软中断,让ICMP Packet重走协议栈。
1 | moon:~# tcpdump -ni eth0 esp or icmp |