`
shaojiashuai123456
  • 浏览: 256818 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
社区版块
存档分类
最新评论

Linux内核中的IPSEC实现(5) ---转载

阅读更多

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

7. IPV4下的xfrm支持处理

在xfrm中各种和地址相关的操作是和协议族相关的, 因此这部分的具体实现就放在相关的协议族实现中, 然后通过状态和策略信息结构来指引到实际的操作中,完成对普通数据包的IPSEC包装或对IPSEC包的解封装。

7.1 IPV4下的xfrm策略

IPV4下的xfrm策略在net/ipv4/xfrm4_policy.c文件中定义, 主要是定义IPV4的策略信息结构:

static struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
 .family =   AF_INET,
 .dst_ops =  &xfrm4_dst_ops,
 .dst_lookup =  xfrm4_dst_lookup,
 .get_saddr =  xfrm4_get_saddr,
 .find_bundle =   __xfrm4_find_bundle,
 .bundle_create = __xfrm4_bundle_create,
 .decode_session = _decode_session4,
};

在xfrm_policy_register_afinfo()函数中, 还定义了struct xfrm_policy_afinfo结构的其他几个成员函数,因为这几个函数是和协议无关的, 所以在登记函数中定义了:
 afinfo->garbage_collect = __xfrm_garbage_collect;
该函数已经在本系列的第3篇中介绍过了.
 
以下是结构中几个函数的定义:
// IPV4的路由查找, 就是普通是路由查找方法
// 返回0成功
static int xfrm4_dst_lookup(struct xfrm_dst **dst, struct flowi *fl)
{
 return __ip_route_output_key((struct rtable**)dst, fl);
}
// 查找地址, 这个函数是在通道模式下, 源地址没明确指定时调用的,查找获取
// 外部头中的源地址
static int xfrm4_get_saddr(xfrm_address_t *saddr, xfrm_address_t *daddr)
{
 struct rtable *rt;
// 通道的流结构定义,用于查找路由
 struct flowi fl_tunnel = {
  .nl_u = {
   .ip4_u = {
    .daddr = daddr->a4,
   },
  },
 };
// 根据目的地址找路由
 if (!xfrm4_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel)) {
// 将找到的路由项中的源地址作为通道模式下的外部源地址
  saddr->a4 = rt->rt_src;
  dst_release(&rt->u.dst);
  return 0;
 }
 return -EHOSTUNREACH;
}

// 查找策略中的安全路由, 查找条件是流结构的定义的参数
static struct dst_entry *
__xfrm4_find_bundle(struct flowi *fl, struct xfrm_policy *policy)
{
 struct dst_entry *dst;
 read_lock_bh(&policy->lock);
// 遍历策略的安全路由链表
 for (dst = policy->bundles; dst; dst = dst->next) {
  struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
// 比较网卡位置, 目的地址, 源地址, TOS值是否匹配
// 同时检查该安全路由是否可用
  if (xdst->u.rt.fl.oif == fl->oif && /*XXX*/
      xdst->u.rt.fl.fl4_dst == fl->fl4_dst &&
          xdst->u.rt.fl.fl4_src == fl->fl4_src &&
          xdst->u.rt.fl.fl4_tos == fl->fl4_tos &&
      xfrm_bundle_ok(policy, xdst, fl, AF_INET, 0)) {
   dst_clone(dst);
   break;
  }
 }
 read_unlock_bh(&policy->lock);
 return dst;
}

// 解码skb数据, 填充流结构
static void
_decode_session4(struct sk_buff *skb, struct flowi *fl)
{
 struct iphdr *iph = skb->nh.iph;
// xprth是IP头后的上层协议头起始
 u8 *xprth = skb->nh.raw + iph->ihl*4;
// 先将流结构清零
 memset(fl, 0, sizeof(struct flowi));
// 数据包必须不是分片包
 if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) {
  switch (iph->protocol) {
// 对UDP(17), TCP(6), SCTP(132)和DCCP(33)协议, 要提取源端口和目的端口
// 头4字节是源端口和目的端口
  case IPPROTO_UDP:
  case IPPROTO_TCP:
  case IPPROTO_SCTP:
  case IPPROTO_DCCP:
// 要让skb预留出IP头长度加4字节的长度, 在IP层data应该指向最外面的IP头
   if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
    u16 *ports = (u16 *)xprth;
// 提取端口参数
    fl->fl_ip_sport = ports[0];
    fl->fl_ip_dport = ports[1];
   }
   break;
  case IPPROTO_ICMP:
// 对ICMP(1)协议要提取ICMP包的类型和编码, 2字节
   if (pskb_may_pull(skb, xprth + 2 - skb->data)) {
    u8 *icmp = xprth;
    fl->fl_icmp_type = icmp[0];
    fl->fl_icmp_code = icmp[1];
   }
   break;
  case IPPROTO_ESP:
// 对于ESP(50)协议要提取其中的SPI值, 4字节
   if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
    __be32 *ehdr = (__be32 *)xprth;
    fl->fl_ipsec_spi = ehdr[0];
   }
   break;
  case IPPROTO_AH:
// 对于AH(51)协议要提取其中的SPI值, 4字节
   if (pskb_may_pull(skb, xprth + 8 - skb->data)) {
    __be32 *ah_hdr = (__be32*)xprth;
    fl->fl_ipsec_spi = ah_hdr[1];
   }
   break;
  case IPPROTO_COMP:
// 对于COMP(108)协议要提取其中CPI值作为SPI值, 2字节
   if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
    __be16 *ipcomp_hdr = (__be16 *)xprth;
    fl->fl_ipsec_spi = htonl(ntohs(ipcomp_hdr[1]));
   }
   break;
  default:
   fl->fl_ipsec_spi = 0;
   break;
  };
 }
// 填充协议,源地址,目的地址, TOS参数
 fl->proto = iph->protocol;
 fl->fl4_dst = iph->daddr;
 fl->fl4_src = iph->saddr;
 fl->fl4_tos = iph->tos;
}
 

/* Allocate chain of dst_entry's, attach known xfrm's, calculate
 * all the metrics... Shortly, bundle a bundle.
 */
// 创建安全路由
static int
__xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
        struct flowi *fl, struct dst_entry **dst_p)
{
 struct dst_entry *dst, *dst_prev;
 struct rtable *rt0 = (struct rtable*)(*dst_p);
 struct rtable *rt = rt0;
 u32 remote = fl->fl4_dst;
 u32 local  = fl->fl4_src;
 struct flowi fl_tunnel = {
  .nl_u = {
   .ip4_u = {
    .saddr = local,
    .daddr = remote,
    .tos = fl->fl4_tos
   }
  }
 };
 int i;
 int err;
 int header_len = 0;
 int trailer_len = 0;
 dst = dst_prev = NULL;
 dst_hold(&rt->u.dst);
// 循环次数为策略中SA的数量, 每个SA对应一个安全路由, 一个安全路由对应对数据包的一个
// 操作: 如压缩, ESP封装, AH封装等
 for (i = 0; i < nx; i++) {
// 分配安全路由, 安全路由的操作结构是xfrm4_dst_ops
// 因为定义了很多不同类型的路由, 每种路由都有各自的操作结构, 这样在上层可用
// 统一的接口进行路由处理
  struct dst_entry *dst1 = dst_alloc(&xfrm4_dst_ops);
  struct xfrm_dst *xdst;
  int tunnel = 0;
  if (unlikely(dst1 == NULL)) {
   err = -ENOBUFS;
   dst_release(&rt->u.dst);
   goto error;
  }
  if (!dst)
// 第一次循环
   dst = dst1;
  else {
// 将新分配的安全路由作为前一个路由的child
   dst_prev->child = dst1;
   dst1->flags |= DST_NOHASH;
   dst_clone(dst1);
  }
  xdst = (struct xfrm_dst *)dst1;
// 安全路由中保留相应的普通路由
  xdst->route = &rt->u.dst;
  xdst->genid = xfrm[i]->genid;
// 新节点的next是老节点
  dst1->next = dst_prev;
// 现在prev节点位新节点
  dst_prev = dst1;
  if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
   remote = xfrm[i]->id.daddr.a4;
   local  = xfrm[i]->props.saddr.a4;
   tunnel = 1;
  }
  header_len += xfrm[i]->props.header_len;
  trailer_len += xfrm[i]->props.trailer_len;
// 如果是通道模式, 需要重新包裹外部IP头, 需要重新寻找外部IP头的路由
  if (tunnel) {
   fl_tunnel.fl4_src = local;
   fl_tunnel.fl4_dst = remote;
   err = xfrm_dst_lookup((struct xfrm_dst **)&rt,
           &fl_tunnel, AF_INET);
   if (err)
    goto error;
  } else
   dst_hold(&rt->u.dst);
 }
// 将最新节点的child指向最后的普通路由
 dst_prev->child = &rt->u.dst;
// 最老一个安全路由的path指向最后的普通路由
 dst->path = &rt->u.dst;
// 将最老安全路由点作为要返回的路由节点链表头
 *dst_p = dst;
// dst现在是最新节点
 dst = dst_prev;
// prev现在指向最老安全节点
 dst_prev = *dst_p;
 i = 0;
/*
 为更好理解上面的操作, 用图来表示. 以上循环形成了下图水平方向的一个链表, 链表中的最左边的路由项节点dst为最老的安全路由项, 新分配的安全路由项通过child链接成链表, child通过next指向老节点, 最后一项是数据包封装完后的最后普通路由项.
垂直方向的链表是在xfrm_lookup()中形成的, 是多个策略同时起作用的情况, 一般情况下就只有一个策略, 本文中可不考虑多策略的情况.
                  
                 rt0.u.dst        rt.u.dst            rt.u.dst
                     ^               ^                   ^
               route |         route |             route |           
                     |     child     |    child          |
          bundle  +-----+  -----> +-----+ ----->      +-----+ child
  policy -------> | dst |  <----- | dst | <----- ...  | dst | -----> rt.u.dst
                  +-----+   next  +-----+  next       +-----+
                     |
                     |next
                     |
                     V     child          child
                  +-----+  -----> +-----+ ----->      +-----+ child
                  | dst |  <----- | dst | <----- ...  | dst | -----> rt.u.dst
                  +-----+   next  +-----+  next       +-----+
                     |
                     |next
                     |
                     V
                    ....
*/
// 对新生成的每个安全路由项填充结构参数
 for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) {
  struct xfrm_dst *x = (struct xfrm_dst*)dst_prev;
  x->u.rt.fl = *fl;
  dst_prev->xfrm = xfrm[i++];
  dst_prev->dev = rt->u.dst.dev;
  if (rt->u.dst.dev)
   dev_hold(rt->u.dst.dev);
  dst_prev->obsolete = -1;
  dst_prev->flags        |= DST_HOST;
  dst_prev->lastuse = jiffies;
  dst_prev->header_len = header_len;
  dst_prev->nfheader_len = 0;
  dst_prev->trailer_len = trailer_len;
  memcpy(&dst_prev->metrics, &x->route->metrics, sizeof(dst_prev->metrics));
  /* Copy neighbout for reachability confirmation */
  dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour);
  dst_prev->input  = rt->u.dst.input;
// 注意安全路由的输出函数是xfrm4_output, 在以后分析路由过程时要用到
  dst_prev->output = xfrm4_output;
  if (rt->peer)
   atomic_inc(&rt->peer->refcnt);
  x->u.rt.peer = rt->peer;
  /* Sheit... I remember I did this right. Apparently,
   * it was magically lost, so this code needs audit */
  x->u.rt.rt_flags = rt0->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL);
  x->u.rt.rt_type = rt->rt_type;
  x->u.rt.rt_src = rt0->rt_src;
  x->u.rt.rt_dst = rt0->rt_dst;
  x->u.rt.rt_gateway = rt->rt_gateway;
  x->u.rt.rt_spec_dst = rt0->rt_spec_dst;
  x->u.rt.idev = rt0->idev;
  in_dev_hold(rt0->idev);
  header_len -= x->u.dst.xfrm->props.header_len;
  trailer_len -= x->u.dst.xfrm->props.trailer_len;
 }
// 初始化路由项的MTU值
 xfrm_init_pmtu(dst);
 return 0;
error:
 if (dst)
  dst_free(dst);
 return err;
}
 
7.1. 小结

IPV4的策略信息结构中的相关成员函数的被调用关系可如下简单表示:
xfrm_lookup: find xfrm_dst for the skb, create dst_list
  -> xfrm_find_bundle
    -> afinfo->find_bundle() == __xfrm4_find_bundle
  -> xfrm_tmpl_resolve
    -> xfrm_tmpl_resolve_one
      -> xfrm_get_saddr
        -> afinfo->get_saddr == xfrm4_get_saddr
          -> xfrm4_dst_lookup
  -> xfrm_bundle_create
    -> afinfo->bundle_create() == __xfrm4_bundle_create
      -> xfrm_dst_lookup()
        -> afinfo->dst_lookup() == xfrm4_dst_lookup

xfrm4_policy_check
  -> xfrm_policy_check
    -> __xfrm_policy_check
      -> xfrm_decode_session
        -> afinfo->decode_session() == _decode_session4

7.2 IPV4安全路由操作

路由操作是针对每种类型的路由定义的一个操作结构, 对上层隐藏了不同路由处理内部的处理方法, 对于IPSEC的IPV4安全路由(xfrm_dst)的操作结构定义如下:
/* net/ipv4/xfrm4_policy.c */
static struct dst_ops xfrm4_dst_ops = {
 .family =  AF_INET,
 .protocol =  __constant_htons(ETH_P_IP),
 .gc =   xfrm4_garbage_collect,
 .update_pmtu =  xfrm4_update_pmtu,
 .destroy =  xfrm4_dst_destroy,
 .ifdown =  xfrm4_dst_ifdown,
 .gc_thresh =  1024,
 .entry_size =  sizeof(struct xfrm_dst),
};

在xfrm_policy_register_afinfo()函数中, 还定义了安全路由操作结构的其他几个成员函数,因为这几个函数是和协议无关的, 所以在登记函数中定义了:
 dst_ops->kmem_cachep = xfrm_dst_cache;
 dst_ops->check = xfrm_dst_check;
 dst_ops->negative_advice = xfrm_negative_advice;
 dst_ops->link_failure = xfrm_link_failure;
 
// 安全路由垃圾搜集, 就是调用安全策略信息结构的垃圾搜集函数
static inline int xfrm4_garbage_collect(void)
{
 xfrm4_policy_afinfo.garbage_collect();
 return (atomic_read(&xfrm4_dst_ops.entries) > xfrm4_dst_ops.gc_thresh*2);
}

// 更新路由的MTU
static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu)
{
 struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
 struct dst_entry *path = xdst->route;
// 调用的是安全路由的原始普通路由的MTU更新操作
 path->ops->update_pmtu(path, mtu);
}

// 释放安全路由
static void xfrm4_dst_destroy(struct dst_entry *dst)
{
 struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
// 释放inet网卡引用
 if (likely(xdst->u.rt.idev))
  in_dev_put(xdst->u.rt.idev);
// 释放对方IP的引用
 if (likely(xdst->u.rt.peer))
  inet_putpeer(xdst->u.rt.peer);
// 释放安全路由
 xfrm_dst_destroy(xdst);
}

 static inline void xfrm_dst_destroy(struct xfrm_dst *xdst)
 {
 // 释放和安全路由相关的普通路由
  dst_release(xdst->route);
 // 释放SA
  if (likely(xdst->u.dst.xfrm))
   xfrm_state_put(xdst->u.dst.xfrm);
 }

// 网卡down时的回调操作
static void xfrm4_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
        int unregister)
{
 struct xfrm_dst *xdst;
 if (!unregister)
  return;
 xdst = (struct xfrm_dst *)dst;
// 该安全路由对应的网卡是当前停掉的网卡
 if (xdst->u.rt.idev->dev == dev) {
  struct in_device *loopback_idev = in_dev_get(&loopback_dev);
  BUG_ON(!loopback_idev);
  do {
// 释放安全路由网卡
   in_dev_put(xdst->u.rt.idev);
// 安全路由网卡采用自身的回环网卡
   xdst->u.rt.idev = loopback_idev;
   in_dev_hold(loopback_idev);
// 子路由
   xdst = (struct xfrm_dst *)xdst->u.dst.child;
  } while (xdst->u.dst.xfrm);
  __in_dev_put(loopback_idev);
 }
 xfrm_dst_ifdown(dst, dev);
}
 
7.3 IPV4下的xfrm状态

IPV4下的xfrm状态在net/ipv4/xfrm4_state.c文件中定义, 主要是定义IPV4的状态信息结构:
static struct xfrm_state_afinfo xfrm4_state_afinfo = {
 .family   = AF_INET,
 .init_flags  = xfrm4_init_flags,
 .init_tempsel  = __xfrm4_init_tempsel,
};

该结构中在IPV4下只定义了两个处理函数:

// 初始化状态标志
static int xfrm4_init_flags(struct xfrm_state *x)
{
 if (ipv4_config.no_pmtu_disc)
  x->props.flags |= XFRM_STATE_NOPMTUDISC;
 return 0;
}

// 初始化模板选择子
static void
__xfrm4_init_tempsel(struct xfrm_state *x, struct flowi *fl,
       struct xfrm_tmpl *tmpl,
       xfrm_address_t *daddr, xfrm_address_t *saddr)
{
// 填写选择子信息
// 源地址
 x->sel.daddr.a4 = fl->fl4_dst;
// 目的地址
 x->sel.saddr.a4 = fl->fl4_src;
// 目的端口, 掩码
 x->sel.dport = xfrm_flowi_dport(fl);
 x->sel.dport_mask = htons(0xffff);
// 源端口掩码
 x->sel.sport = xfrm_flowi_sport(fl);
 x->sel.sport_mask = htons(0xffff);
// 源目的地址长度
 x->sel.prefixlen_d = 32;
 x->sel.prefixlen_s = 32;
// 协议
 x->sel.proto = fl->proto;
// 网卡位置
 x->sel.ifindex = fl->oif;
// 状态ID值
 x->id = tmpl->id;
 if (x->id.daddr.a4 == 0)
  x->id.daddr.a4 = daddr->a4;
// 支持结构中的参数
// 源地址
 x->props.saddr = tmpl->saddr;
 if (x->props.saddr.a4 == 0)
  x->props.saddr.a4 = saddr->a4;
// 模式
 x->props.mode = tmpl->mode;
// 请求ID
 x->props.reqid = tmpl->reqid;
// 协议族
 x->props.family = AF_INET;
}

7.3小结

IPV4的状态信息结构中的相关成员函数的被调用关系可如下简单表示:
xfrm_init_state()
  -> afinfo->init_flags() == xfrm4_init_flags

xfrm_state_find()
  -> xfrm_init_tempsel()
    -> afinfo->init_tempsel() == __xfrm4_init_tempsel

7.4 模式

xfrm4支持3种模式: 通道, 传输和BEET模式, 分别在xfrm4_mode_tunnel.c, xfrm4_mode_transport.c和xfrm4_mode_beet.c中定义.
每个模式都通过结构struct xfrm_mode定义:
struct xfrm_mode {
 int (*input)(struct xfrm_state *x, struct sk_buff *skb);
 int (*output)(struct xfrm_state *x,struct sk_buff *skb);
 struct module *owner;
 unsigned int encap;
};
其中input函数在数据接收时调用, output函数数据发出时调用, encap参数表示是否封装.

7.4.1 通道
通道模式通过以下结构定义:
/* net/ipv4/xfrm4_mode_transport.c */
static struct xfrm_mode xfrm4_tunnel_mode = {
 .input = xfrm4_tunnel_input,
 .output = xfrm4_tunnel_output,
 .owner = THIS_MODULE,
 .encap = XFRM_MODE_TUNNEL,
};

// 通道模式下的接收函数, 解封装
static int xfrm4_tunnel_input(struct xfrm_state *x, struct sk_buff *skb)
{
 struct iphdr *iph = skb->nh.iph;
 int err = -EINVAL;
// IP协议为IPPROTO_IPIP(4)
 if (iph->protocol != IPPROTO_IPIP)
  goto out;
// 需要在skb头留出IP头的长度(20字节)
 if (!pskb_may_pull(skb, sizeof(struct iphdr)))
  goto out;
// 如果是clone包,重新拷贝一个
 if (skb_cloned(skb) &&
     (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
  goto out;
// 复制dscp字段
 if (x->props.flags & XFRM_STATE_DECAP_DSCP)
  ipv4_copy_dscp(iph, skb->h.ipiph);
// 非XFRM_STATE_NOECN时进行ECN解封装
 if (!(x->props.flags & XFRM_STATE_NOECN))
  ipip_ecn_decapsulate(skb);
// 将硬件地址挪到数据包缓冲区前
 skb->mac.raw = memmove(skb->data - skb->mac_len,
          skb->mac.raw, skb->mac_len);
// 网络部分数据头
 skb->nh.raw = skb->data;
 err = 0;
out:
 return err;
}

// 通道模式下的数据发出函数, 进行封装
static int xfrm4_tunnel_output(struct xfrm_state *x, struct sk_buff *skb)
{
 struct dst_entry *dst = skb->dst;
 struct iphdr *iph, *top_iph;
 int flags;
 iph = skb->nh.iph;
 skb->h.ipiph = iph;
// 数据头部增加外部IP头的长度
 skb->nh.raw = skb_push(skb, x->props.header_len);
 top_iph = skb->nh.iph;
// 填写外部IP头参数
 top_iph->ihl = 5;
 top_iph->version = 4;
 /* DS disclosed */
// 重新计算TOS
 top_iph->tos = INET_ECN_encapsulate(iph->tos, iph->tos);
 flags = x->props.flags;
 if (flags & XFRM_STATE_NOECN)
  IP_ECN_clear(top_iph);
// 处理分片包情况
 top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) ?
  0 : (iph->frag_off & htons(IP_DF));
 if (!top_iph->frag_off)
  __ip_select_ident(top_iph, dst->child, 0);
// TTL
 top_iph->ttl = dst_metric(dst->child, RTAX_HOPLIMIT);
// 外部源地址用proposal中的源地址
 top_iph->saddr = x->props.saddr.a4;
// 外部目的地址是SA中的目的地址
 top_iph->daddr = x->id.daddr.a4;
// 外部IP头内的协议号为IPIP(4)
 top_iph->protocol = IPPROTO_IPIP;
// IP选项部分设置为0
 memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
 return 0;
}

7.4.2 传输

传输模式下不添加新的IP头, 其实几乎什么都不用做, 老点的2.6内核中就没有专门为传输模式定义.
传输模式结构定义为:
/* net/ipv4/xfrm4_mode_transport.c */
static struct xfrm_mode xfrm4_transport_mode = {
 .input = xfrm4_transport_input,
 .output = xfrm4_transport_output,
 .owner = THIS_MODULE,
 .encap = XFRM_MODE_TRANSPORT,
};

/* Remove encapsulation header.
 *
 * The IP header will be moved over the top of the encapsulation header.
 *
 * On entry, skb->h shall point to where the IP header should be and skb->nh
 * shall be set to where the IP header currently is.  skb->data shall point
 * to the start of the payload.
 */
// 传输模式下的数据输入函数
static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb)
{
// data指向负载头, h指向IP头, 但很多情况下两者相同
 int ihl = skb->data - skb->h.raw;
// 如果h和nh不同, 将nh所指向IP头部分移动到h处
 if (skb->h.raw != skb->nh.raw)
  skb->nh.raw = memmove(skb->h.raw, skb->nh.raw, ihl);
// 增加数据包长度, 重新对数据包长度赋值
 skb->nh.iph->tot_len = htons(skb->len + ihl);
 skb->h.raw = skb->data;
 return 0;
}
 
/* Add encapsulation header.
 *
 * The IP header will be moved forward to make space for the encapsulation
 * header.
 *
 * On exit, skb->h will be set to the start of the payload to be processed
 * by x->type->output and skb->nh will be set to the top IP header.
 */
// 传输模式下的数据发出函数
static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
{
 struct iphdr *iph;
 int ihl;
// nh和赋值给h
 iph = skb->nh.iph;
 skb->h.ipiph = iph;
// ip头长度
 ihl = iph->ihl * 4;
// 重新计算h位置
 skb->h.raw += ihl;
// 重新计算新的nh位置,增加proposal中的头长度, 拷贝原来的IP头数据
 skb->nh.raw = memmove(skb_push(skb, x->props.header_len), iph, ihl);
 return 0;
}

7.4.3 BEET

封装成BEETPH(94)包, 非标准IPSEC, 略.

7.4.4 小结
和xfrm_mode相关的xfrm函数有:
登记: int xfrm_register_mode(struct xfrm_mode *mode, int family);
撤销: int xfrm_unregister_mode(struct xfrm_mode *mode, int family)
获取: struct xfrm_mode *xfrm_get_mode(unsigned int encap, int family)
释放: void xfrm_put_mode(struct xfrm_mode *mode)

xfrm_mode的输入输出函数调用:
xfrm4_rcv_encap()
  -> x->mode->input
xfrm4_output_one()
  -> x->mode->output
 
7.5 数据接收

IPV4的IPSEC数据接收处理在net/ipv4/xfrm4_input.c中定义, 作为AH和ESP协议数据接收处理函数.

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

实际就是xfrm4_rcv_encap,封装类型参数设置为0,在NAT-T时IPSEC数据被封装在UDP包中时, 该参数才非0.
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);
// 保存数据解封用的SA, 增加SA数量计数
  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;
}
// 解析AH,ESP数据包中的SPI和序号
static int xfrm4_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
{
 switch (nexthdr) {
// 如果只是普通的IPIP包, SPI为源地址, 序号位0
 case IPPROTO_IPIP:
  *spi = skb->nh.iph->saddr;
  *seq = 0;
  return 0;
 }
// 否则解析AH/ESP/COMP协议头中的SPI和序号
 return xfrm_parse_spi(skb, nexthdr, spi, seq);
}
// 接收封装完成处理函数
static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb)
{
 struct iphdr *iph = skb->nh.iph;
// 如果没有路由, 重新查找路由
 if (skb->dst == NULL) {
  if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
                     skb->dev))
   goto drop;
 }
// 调用相关的路由输入函数
 return dst_input(skb);
drop:
 kfree_skb(skb);
 return NET_RX_DROP;
}
 
调用关系:
ip_rcv
  -> (AH/ESP) net_protocol->handler == xfrm4_rcv
    -> xfrm4_rcv_encap
      -> xfrm4_parse_spi
         -> xfrm_parse_spi
      -> xfrm4_rcv_encap_finish
 
7.6 数据发送

IPV4的IPSEC数据发送处理在net/ipv4/xfrm4_output.c中定义,作为安全路由的输出函数:

int xfrm4_output(struct sk_buff *skb)
{
// 就是一个条件HOOK, 当skb包不带IPSKB_REROUTED标志时进入POSTROUTING点的NAT操作
// 这是数据在xfrm策略中多个bundle时会多次调用, 也就是数据在封装完成前可以进行
// 源NAT操作
// HOOK出口函数为xfrm4_output_finish
 return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev,
       xfrm4_output_finish,
       !(IPCB(skb)->flags & IPSKB_REROUTED));
}

// 发送结束处理
static int xfrm4_output_finish(struct sk_buff *skb)
{
 struct sk_buff *segs;
#ifdef CONFIG_NETFILTER
// 如果内核定义了NETFILTER, 当到达最后一个路由(普通路由)时, 设置IPSKB_REROUTED
// 标志, 进行普通路由发出函数(ip_output), 设置该标志后不进行源NAT操作
 if (!skb->dst->xfrm) {
  IPCB(skb)->flags |= IPSKB_REROUTED;
  return dst_output(skb);
 }
#endif
// 如果skb包不是是gso, 转xfrm4_output_finish2
// gso是什么意思现在还不知道, 以后再仔细分析
 if (!skb_is_gso(skb))
  return xfrm4_output_finish2(skb);
// 处理gso数据包, 最终也是使用xfrm4_output_finish2处理数据包
 skb->protocol = htons(ETH_P_IP);
 segs = skb_gso_segment(skb, 0);
 kfree_skb(skb);
 if (unlikely(IS_ERR(segs)))
  return PTR_ERR(segs);
 do {
  struct sk_buff *nskb = segs->next;
  int err;
  segs->next = NULL;
  err = xfrm4_output_finish2(segs);
  if (unlikely(err)) {
   while ((segs = nskb)) {
    nskb = segs->next;
    segs->next = NULL;
    kfree_skb(segs);
   }
   return err;
  }
  segs = nskb;
 } while (segs);
 return 0;
}
 
// 第2级发送结束处理
static int xfrm4_output_finish2(struct sk_buff *skb)
{
 int err;
// 根据安全路由包装要发送数据
 while (likely((err = xfrm4_output_one(skb)) == 0)) {
// 处理成功
// 释放skb中的netfilter信息
  nf_reset(skb);
// 重新将该包作为初始发送包, 进入OUTPUT点处理, 注意这是个函数而不是宏
// 如果内核没定义NETFILTER, 该函数只是个空函数
// 返回1表示NF_ACCEPT
  err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, &skb, NULL,
         skb->dst->dev, dst_output);
  if (unlikely(err != 1))
   break;
// 如果已经没有SA, 就只是个普通包了, 路由发送(ip_output)返回, 退出循环
  if (!skb->dst->xfrm)
   return dst_output(skb);
// 如果还有SA, 目前还只是中间状态, 还可以进行SNAT操作, 进入POSTROUTING点处理
  err = nf_hook(PF_INET, NF_IP_POST_ROUTING, &skb, NULL,
         skb->dst->dev, xfrm4_output_finish2);
  if (unlikely(err != 1))
   break;
 }
 return err;
}

// 按安全路由链表的安全路由处理数据, 该链表反映了多个SA对数据包进行处理
// 链表是在__xfrm4_bundle_create函数中建立的
static int xfrm4_output_one(struct sk_buff *skb)
{
// 安全路由
 struct dst_entry *dst = skb->dst;
// 相关SA
 struct xfrm_state *x = dst->xfrm;
 int err;
// skb包校验和 检查
 if (skb->ip_summed == CHECKSUM_PARTIAL) {
  err = skb_checksum_help(skb);
  if (err)
   goto error_nolock;
 }
// 如果是通道模式, 检查skb数据长度, 并进行相关处理, 通道模式下封装后的数据包长度可能
// 会超过1500字节的
 if (x->props.mode == XFRM_MODE_TUNNEL) {
  err = xfrm4_tunnel_check_size(skb);
  if (err)
   goto error_nolock;
 }
 do {
  spin_lock_bh(&x->lock);
// SA合法性检查
  err = xfrm_state_check(x, skb);
  if (err)
   goto error;
// 调用模式输出函数, 如通道封装, 此时外部IP头协议为IPIP
  err = x->mode->output(x, skb);
  if (err)
   goto error;
// 调用协议输出, 如对应ESP协议来说是esp4_output, 此时外部IP头协议会改为ESP
  err = x->type->output(x, skb);
  if (err)
   goto error;
// 更新SA中的当前生命期结构中的包和字节计数
  x->curlft.bytes += skb->len;
  x->curlft.packets++;
  spin_unlock_bh(&x->lock);
// 转移到下一个子路由 
  if (!(skb->dst = dst_pop(dst))) {
   err = -EHOSTUNREACH;
   goto error_nolock;
  }
// dst和x参数更新为子路由中的安全路由和SA
  dst = skb->dst;
  x = dst->xfrm;
// 循环条件是SA非空, 而且SA提议模式不是通道模式
 } while (x && (x->props.mode != XFRM_MODE_TUNNEL));
// skb中设置IPSKB_XFRM_TRANSFORMED标志
// 有该标志的数据包将NAT操作后将不进行一些特殊检查
 IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED;
 err = 0;
out_exit:
 return err;
error:
 spin_unlock_bh(&x->lock);
error_nolock:
 kfree_skb(skb);
 goto out_exit;
}

IPSEC输出函数调用关系:
dst_output
  -> xfrm_dst->output == xfrm4_output
     -> NF_HOOK(POSTROUTING)
       -> xfrm4_output_finish
         -> xfrm4_output_finish2
           -> xfrm4_output_one

7.7 NAT-T支持

在支持NAT穿越的IPSEC处理中,是通过UDP数据包来封装IPSEC数据(ESP数据包),因此在对UDP处理时需要进行特殊处理。由于IKE同样是用UDP处理的, 区分是IKE包还是封装的ESP包就看数据头部头4字节表示的SPI值, SPI为0表示是IKE包, 由IKE用户空间程序接收进行处理, SPI非0表示是UDP封装的ESP包, 需进行ESP解封。

7.7.1 接收数据

被UDP封装的IPSEC包在接收时会先按普通UDP包接收,在UDP处理中再解开该包后进行IPSEC处理
/* net/ipv4/udp.c */
// 正常接收的UDP包都将进入该函数
static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{
 struct udp_sock *up = udp_sk(sk);
 int rc;
 /*
  * Charge it to the socket, dropping if the queue is full.
  */
// 检查针对该sock,skb包的输入方法上的是否有安全策略
 if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
  kfree_skb(skb);
  return -1;
 }
 nf_reset(skb);
// 检查该SOCK是否是IPSEC封装的,该参数通过setsockopt系统调用的UDP_ENCAP选项设置
// 一般是IKE程序在打开UDP4500端口时设置的
 if (up->encap_type) {
  /*
   * This is an encapsulation socket, so let's see if this is
   * an encapsulated packet.
   * If it's a keepalive packet, then just eat it.
   * If it's an encapsulateed packet, then pass it to the
   * IPsec xfrm input and return the response
   * appropriately.  Otherwise, just fall through and
   * pass this up the UDP socket.
   */
  int ret;
// 进入UDP封装接收, 判断是否是ESP包
// 返回值小于0表示是IPSEC包, 大于0表示是普通UDP包, 等于0表示是错误包
  ret = udp_encap_rcv(sk, skb);
  if (ret == 0) {
   /* Eat the packet .. */
   kfree_skb(skb);
   return 0;
  }
  if (ret < 0) {
// 进行IPSEC接收处理
   /* process the ESP packet */
   ret = xfrm4_rcv_encap(skb, up->encap_type);
   UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
   return -ret;
  }
  /* FALLTHROUGH -- it's a UDP Packet */
 }
// 以下按普通UDP包接收处理, 分析略
 if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
  if (__udp_checksum_complete(skb)) {
   UDP_INC_STATS_BH(UDP_MIB_INERRORS);
   kfree_skb(skb);
   return -1;
  }
  skb->ip_summed = CHECKSUM_UNNECESSARY;
 }
 if ((rc = sock_queue_rcv_skb(sk,skb)) < 0) {
  /* Note that an ENOMEM error is charged twice */
  if (rc == -ENOMEM)
   UDP_INC_STATS_BH(UDP_MIB_RCVBUFERRORS);
  UDP_INC_STATS_BH(UDP_MIB_INERRORS);
  kfree_skb(skb);
  return -1;
 }
 UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
 return 0;
}

/* return:
 *  1  if the the UDP system should process it
 * 0  if we should drop this packet
 *  -1 if it should get processed by xfrm4_rcv_encap
 */
static int udp_encap_rcv(struct sock * sk, struct sk_buff *skb)
{
#ifndef CONFIG_XFRM
// 在内核不支持IPSEC情况下直接返回1
 return 1;
#else
 struct udp_sock *up = udp_sk(sk);
   struct udphdr *uh;
 struct iphdr *iph;
 int iphlen, len;
 
 __u8 *udpdata;
 __be32 *udpdata32;
// sock的封装标志值
 __u16 encap_type = up->encap_type;
 /* if we're overly short, let UDP handle it */
// UDP数据包中数据部分的长度
 len = skb->len - sizeof(struct udphdr);
 if (len <= 0)
  return 1;
 /* if this is not encapsulated socket, then just return now */
// 没定义封装处理, 返回1, 普通处理
 if (!encap_type)
  return 1;
 /* If this is a paged skb, make sure we pull up
  * whatever data we need to look at. */
 if (!pskb_may_pull(skb, sizeof(struct udphdr) + min(len, 8)))
  return 1;
 /* Now we can get the pointers */
 uh = skb->h.uh;
 udpdata = (__u8 *)uh + sizeof(struct udphdr);
 udpdata32 = (__be32 *)udpdata;
 switch (encap_type) {
 default:
// 在UDP中封装ESP
 case UDP_ENCAP_ESPINUDP:
  /* Check if this is a keepalive packet.  If so, eat it. */
  if (len == 1 && udpdata[0] == 0xff) {
// 只是普通UDP的IPSEC通道保活包, 直接丢弃
   return 0;
  } else if (len > sizeof(struct ip_esp_hdr) && udpdata32[0] != 0 ) {
// 头4字节非零, ESP包,需要下一步解析
   /* ESP Packet without Non-ESP header */
   len = sizeof(struct udphdr);
  } else
// 这是IKE包,按普通UDP接收处理
   /* Must be an IKE packet.. pass it through */
   return 1;
  break;
 case UDP_ENCAP_ESPINUDP_NON_IKE:
  /* Check if this is a keepalive packet.  If so, eat it. */
  if (len == 1 && udpdata[0] == 0xff) {
// IPSEC通道保活包, 丢弃
   return 0;
  } else if (len > 2 * sizeof(u32) + sizeof(struct ip_esp_hdr) &&
      udpdata32[0] == 0 && udpdata32[1] == 0) {
// 头4字节非零, ESP包,需要下一步解析
   
   /* ESP Packet with Non-IKE marker */
   len = sizeof(struct udphdr) + 2 * sizeof(u32);
  } else
// 这是IKE数据包,由
   /* Must be an IKE packet.. pass it through */
   return 1;
  break;
 }
 /* At this point we are sure that this is an ESPinUDP packet,
  * so we need to remove 'len' bytes from the packet (the UDP
  * header and optional ESP marker bytes) and then modify the
  * protocol to ESP, and then call into the transform receiver.
  */
// 如果是clone包需要复制成独立包
 if (skb_cloned(skb) && pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
  return 0;
// 检查数据长度
 /* Now we can update and verify the packet length... */
 iph = skb->nh.iph;
 iphlen = iph->ihl << 2;
 iph->tot_len = htons(ntohs(iph->tot_len) - len);
 if (skb->len < iphlen + len) {
  /* packet is too small!?! */
  return 0;
 }
 /* pull the data buffer up to the ESP header and set the
  * transport header to point to ESP.  Keep UDP on the stack
  * for later.
  */
// 修改IP上层头位置
 skb->h.raw = skb_pull(skb, len);
// 更改IP头协议类型为ESP包, 返回-1
 /* modify the protocol (it's ESP!) */
 iph->protocol = IPPROTO_ESP;
 /* and let the caller know to send this into the ESP processor... */
 return -1;
#endif
}

函数调用关系:
udp_rcv
  ->udp_queue_rcv_skb
    -> udp_encap_rcv
    -> xfrm4_policy_check
      -> xfrm_policy_check
        -> __xfrm_policy_check
        
7.7.2 ESP包的UDP封装

对于ESP包的UDP封装处理, 在下一节ESP协议数据包的输出处理中介绍.

...... 待续 ......
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics