Linux2.6下ESP包解析流程

Linux2.6下ESP包解析流程
 
 
Linux2.6下ESP包解析流程
 
本文檔的Copyleft歸yfydz所有,使用GPL發佈,可以自由拷貝,轉載,轉載時請保持文檔的完整性,
嚴禁用於任何商業用途。
msn: [email protected]
來源: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層協議的處理
// 用解碼後的內層協議來處理數據
  return -skb->nh.iph->protocol;
#endif
 }
drop_unlock:
 spin_unlock(&x->lock);
 xfrm_state_put(x);
drop:
 while (--xfrm_nr >= 0)
  xfrm_state_put(xfrm_vec[xfrm_nr]);
 kfree_skb(skb);
 return 0;
}

最後說一下返回負協議值的處理, IP上層協議的handler是在ip_local_deliver_finish()函數中調用
的:

/* net/ipv4/ip_input.c */
static inline int ip_local_deliver_finish(struct sk_buff *skb)
{
 int ihl = skb->nh.iph->ihl*4;
 __skb_pull(skb, ihl);
        /* Point into the IP datagram, just past the header. */
        skb->h.raw = skb->data;
 rcu_read_lock();
 {
  /* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
  int protocol = skb->nh.iph->protocol;
  int hash;
  struct sock *raw_sk;
  struct net_protocol *ipprot;
 resubmit:
// 協議hash值, IPv4最大支持255種協議
  hash = protocol & (MAX_INET_PROTOS - 1);
  raw_sk = sk_head(&raw_v4_htable[hash]);
  /* If there maybe a raw socket we must check - if not we
   * don't care less
   */
  if (raw_sk && !raw_v4_input(skb, skb->nh.iph, hash))
   raw_sk = NULL;
// 直接在協議數組中獲取協議指針
  if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
   int ret;
   if (!ipprot->no_policy) {
    if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
     kfree_skb(skb);
     goto out;
    }
    nf_reset(skb);
   }
// 調用協議handler
   ret = ipprot->handler(skb);
   if (ret < 0) {
// 如果返回值爲負, 取反後重新跳回resubmit點進行選協議處理
    protocol = -ret;
    goto resubmit;
   }
   IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
  } else {
   if (!raw_sk) {
    if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
     IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
     icmp_send(skb, ICMP_DEST_UNREACH,
        ICMP_PROT_UNREACH, 0);
    }
   } else
    IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
   kfree_skb(skb);
  }
 }
 out:
 rcu_read_unlock();
 return 0;
}

3. 結論
雖然在2.6的native ipsec中沒支持虛擬網卡,但在通道模式下也用到了netif_rx函數將解碼後的數
據包重新接收處理的過程,並沒有改變skb包的dev網卡參數,因此如果在該網卡上抓包,就會同時抓
到最初的加密包和解碼後的明文包;而用freeswan的實現,在普通網卡上抓包抓到的是加密包,由於
freeswan在解碼後將skb包的dev參數改爲了ipsec*,因此是通過在ipsec*網卡上能抓到解密包。對於
傳輸模式,由於沒有調用netif_rx函數,因此在實際網卡抓包只能抓到加密包,解密包只能在
netfilter架構中看到了。
 
另外,在此情況下NAT規則仍然是有效的,制定NAT規則時根據解密後的地址端口等信息來處理就可以
了。
 
識別一個明文包是否是解密過的就看skb的sp參數即可,該指針爲空表示是普通明文包,非空表示是
解密後的明文包。
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章