日期:2014-05-16  浏览次数:20839 次

Linux2.6下ESP包解析流程
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
1. 前言

在Linux2.6中自带了ipsec的实现,可以不再使用freeswan及其变种了,freeswan通过建立ipsec*的
虚拟网卡来将发送和接收ipsec数据包,通过ipsec*网卡看到的数据是明文数据,而2.6中的ipsec实
现是不建立ipsec*虚拟网卡的,本文分析一下ESP包进入系统协议栈的处理流程。
以下Linux内核代码版本为2.6.19.2。

2. 流程分析

2.1 esp协议结构

esp协议结构定义,对于每个IPv4上层的协议,如TCP、UDP、ICMP、IGMP、ESP、AH等都需要定义这个
结构挂接到IPv4的协议链表中,当接收到IP数据包时,会根据包中定义的IP协议号找到该结构,然后
调用其成员handler函数进行处理。

/* net/ipv4/esp4.c */
static struct net_protocol esp4_protocol = {
 .handler = xfrm4_rcv,
 .err_handler = esp4_err,
 .no_policy = 1,
};

esp协议的handler函数是xfrm4_rcv()

2.2 xfrm4_rcv
/* net/ipv4/xfrm4_input.c */
int xfrm4_rcv(struct sk_buff *skb)
{
 return xfrm4_rcv_encap(skb, 0);
}

实际就是xfrm4_rcv_encap,封装类型参数设置为0,即没封装数据

2.3 xfrm4_rcv_encap
/* net/ipv4/xfrm4_input.c */
int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
{
 int err;
 __be32 spi, seq;
 struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH];
 struct xfrm_state *x;
 int xfrm_nr = 0;
 int decaps = 0;
// 获取skb中的spi和序列号信息
 if ((err = xfrm4_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) != 0)
  goto drop;
// 进入循环进行解包操作
 do {
  struct iphdr *iph = skb->nh.iph;
// 循环解包次数太深的话放弃
  if (xfrm_nr == XFRM_MAX_DEPTH)
   goto drop;
// 根据地址, SPI和协议查找SA
  x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, iph->protocol,
AF_INET);
  if (x == NULL)
   goto drop;
// 以下根据SA定义的操作对数据解码
  spin_lock(&x->lock);
  if (unlikely(x->km.state != XFRM_STATE_VALID))
   goto drop_unlock;
// 检查由SA指定的封装类型是否和函数指定的封装类型相同
  if ((x->encap ? x->encap->encap_type : 0) != encap_type)
   goto drop_unlock;
// SA重放窗口检查
  if (x->props.replay_window && xfrm_replay_check(x, seq))
   goto drop_unlock;
// SA生存期检查
  if (xfrm_state_check_expire(x))
   goto drop_unlock;
// type可为esp,ah,ipcomp, ipip等, 对输入数据解密
  if (x->type->input(x, skb))
   goto drop_unlock;
  /* only the first xfrm gets the encap type */
  encap_type = 0;
// 更新重放窗口
  if (x->props.replay_window)
   xfrm_replay_advance(x, seq);
// 包数,字节数统计
  x->curlft.bytes += skb->len;
  x->curlft.packets++;
  spin_unlock(&x->lock);
  xfrm_vec[xfrm_nr++] = x;
// mode可为通道,传输等模式, 对输入数据解封装
  if (x->mode->input(x, skb))
   goto drop;
// 如果是IPSEC通道模式,将decaps参数置1,否则表示是传输模式
  if (x->props.mode == XFRM_MODE_TUNNEL) {
   decaps = 1;
   break;
  }
// 看内层协议是否还要继续解包, 不需要解时返回1, 需要解时返回0, 错误返回负数
// 协议类型可以多层封装的,比如用AH封装ESP, 就得先解完AH再解ESP
  if ((err = xfrm_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) < 0)
   goto drop;
 } while (!err);
 /* Allocate new secpath or COW existing one. */
// 为skb包建立新的安全路径(struct sec_path)
 if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
  struct sec_path *sp;
  sp = secpath_dup(skb->sp);
  if (!sp)
   goto drop;
  if (skb->sp)
   secpath_put(skb->sp);
  skb->sp = sp;
 }
 if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
  goto drop;
// 将刚才循环解包用到的SA拷贝到安全路径
// 因此检查一个数据包是否是普通明文包还是解密后的明文包就看skb->sp参数是否为空
 memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec,
        xfrm_nr * sizeof(xfrm_vec[0]));
 skb->sp->len += xfrm_nr;
 nf_reset(skb);
 if (decaps) {
// 通道模式
  if (!(skb->dev->flags&IFF_LOOPBACK)) {
   dst_release(skb->dst);
   skb->dst = NULL;
  }
// 重新进入网卡接收函数
  netif_rx(skb);
  return 0;
 } else {
// 传输模式
#ifdef CONFIG_NETFILTER
// 如果定义NETFILTER, 进入PRE_ROUTING链处理,然后进入路由选择处理
// 其实现在已经处于INPUT点, 但解码后需要将该包作为一个新包看待
// 可能需要进行目的NAT操作, 这时候可能目的地址就会改变不是到自身
// 的了, 因此需要将其相当于是放回PRE_PROUTING点去操作, 重新找路由
// 这也说明可以制定针对解码后明文包的NAT规则,在还是加密包的时候不匹配
// 但解码后能匹配上
  __skb_push(skb, skb->data - skb->nh.raw);
  skb->nh.iph->tot_len = htons(skb->len);
  ip_send_check(skb->nh.iph);
  NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL,
          xfrm4_rcv_encap_finish);
  return 0;
#else
// 内核不支持NETFILTER, 该包肯定就是到自身的了
// 返回IP协议的负值, 表示重新进行IP层协议的处理
// 用解码后的内层