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

9. IPSEC封装流程
IPSEC
数据包的封装过程是在数据包发出前完成的, 是和路由选择密切相关的, 根据前面的发出分析可知封装是通过对数据设置安全路由链表来实现的, 因此对数据包的IPSEC封装流程可以简单描述如下:
1) 对于进入的数据包, 进行路由选择, 如果是转发的, 进入路由输入, 然后查找安全策略检查是否需要IPSEC封装, 如果需要封装, 就查找和创建相关的安全路由, 进入路由输出处理, 在路由输出时即按照安全路由一层层地封装数据包最后得到IPSEC包发出;
2) 对于自身发出的数据包, 需要进行路由选择, 选定路由后进入路由输入, 查找安全策略进行处理, 以后和转发的数据包IPSEC封装就是完全相同了。

9.1 转发包的封装

数据的转发入口点函数是ip_forward, 进入该函数的数据包还是普通数据包,数据包的路由也是普通路由:

/* net/ipv4/ip_forward.c */
int ip_forward(struct sk_buff *skb)
{
 struct iphdr *iph; /* Our header */
 struct rtable *rt; /* Route we use */
 struct ip_options * opt = &(IPCB(skb)->opt);
// 对转发的数据包进行安全策略检查, 检查失败的话丢包
 if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
  goto drop;
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
  return NET_RX_SUCCESS;
// 转发包也是到自身的包, 不是的话丢包
 if (skb->pkt_type != PACKET_HOST)
  goto drop;
skb->ip_summed = CHECKSUM_NONE;
 
 /*
  * According to the RFC, we must first decrease the TTL field. If
  * that reaches zero, we must reply an ICMP control message telling
  * that the packet's lifetime expired.
  */
// TTL到头了, 丢包
 if (skb->nh.iph->ttl <= 1)
                goto too_many_hops;
// 进入安全路由选路和转发处理, 在此函数中构造数据包的安全路由
 if (!xfrm4_route_forward(skb))
  goto drop;
// 以下是一些常规的路由和TTL处理
 rt = (struct rtable*)skb->dst;
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
  goto sr_failed;
/* We are about to mangle packet. Copy it! */
 if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
  goto drop;
 iph = skb->nh.iph;
/* Decrease ttl after skb cow done */
 ip_decrease_ttl(iph);
/*
  * We now generate an ICMP HOST REDIRECT giving the route
  * we calculated.
  */
 if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr)
  ip_rt_send_redirect(skb);
skb->priority = rt_tos2priority(iph->tos);
// 进行FORWARD点过滤, 过滤后进入ip_forward_finish函数
 return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,
         ip_forward_finish);
sr_failed:
        /*
  * Strict routing permits no gatewaying
  */
         icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
         goto drop;
too_many_hops:
        /* Tell the sender its packet died... */
        IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
        icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
 kfree_skb(skb);
 return NET_RX_DROP;
}
// ip_forward_finish函数主要就是调用dst_output函数
static inline int ip_forward_finish(struct sk_buff *skb)
{
 struct ip_options * opt = &(IPCB(skb)->opt);
IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS);
if (unlikely(opt->optlen))
  ip_forward_options(skb);
 
 return dst_output(skb);
}

核心函数是xfrm4_route_forward函数
/* include/net/xfrm.h */
static inline int xfrm4_route_forward(struct sk_buff *skb)
{
 return xfrm_route_forward(skb, AF_INET);
}

static inline int xfrm_route_forward(struct sk_buff *skb, unsigned short family)
{
// 如果没有发出方向的安全策略的话返回
 return !xfrm_policy_count[XFRM_POLICY_OUT] ||
// 如果路由标志专门设置不进行IPSEC封装的话也返回
  (skb->dst->flags & DST_NOXFRM) ||
  __xfrm_route_forward(skb, family);
}
/* net/xfrm/xfrm_policy.c */
int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
{
 struct flowi fl;
// 路由解码, 填充流结构参数,
// 对IPV4实际调用的是_decode_session4(net/ipv4/xfrm4_policy.c)函数
 if (xfrm_decode_session(skb, &fl, family) < 0)
  return 0;
// 根据流结构查找安全路由, 没找到的话创建新的安全路由, 最后形成安全路由链表
// 见前几节中的分析
 return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
}

因此数据进行转发处理后, 最终进入dst_output函数处理
  

转发函数流程小结:
  
ip_forward
  -> xfrm4_route_forward (net/xfrm.h, get xfrm_dst)
    -> xfrm_route_forward
      -> __xfrm_route_forward
        -> xfrm_lookup
          -> xfrm_find_bundle
            -> afinfo->find_bundle == __xfrm4_find_bundle
          -> xfrm_bundle_create
            -> afinfo->bundle_create == __xfrm4_bundle_create
              tunnel mode
              -> xfrm_dst_lookup
                -> afinfo->dst_lookup == xfrm4_dst_lookup
                  -> __ip_route_output_key
          -> dst_list: dst->list=policy_bundles, policy->bundles = dst
  -> NF_HOOK(NF_FORWARD)
  -> ip_forward_finish
  -> dst_output
9.2 自身数据发出

对于IPv4包的发出, 通常出口函数是ip_queue_xmit或ip_push_pending_frames, 如果是后者, 数据包是已经经过了路由选择的, 而前者还没有进行路由选择, 两者最后都会调用dst_output()函数进行数据的发出.

/* net/ipv4/ip_output.c */
int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
 struct sock *sk = skb->sk;
 struct inet_sock *inet = inet_sk(sk);
 struct ip_options *opt = inet->opt;
 struct rtable *rt;
 struct iphdr *iph;
/* Skip all of this if the packet is already routed,
  * f.e. by something like SCTP.
  */
// 已经路由过的数据跳过路由查找过程
 rt = (struct rtable *) skb->dst;
 if (rt != NULL)
  goto packet_routed;
/* Make sure we can route this packet. */
 rt = (struct rtable *)__sk_dst_check(sk, 0);
 if (rt == NULL) {
  __be32 daddr;
/* Use correct destination address if we have options. */
  daddr = inet->daddr;
  if(opt && opt->srr)
   daddr = opt->faddr;
{
   struct flowi fl = { .oif = sk->sk_bound_dev_if,
         .nl_u = { .ip4_u =
            { .daddr = daddr,
       .saddr = inet->saddr,
       .tos = RT_CONN_FLAGS(sk) } },
         .proto = sk->sk_protocol,
         .uli_u = { .ports =
             { .sport = inet->sport,
        .dport = inet->dport } } };
/* If this fails, retransmit mechanism of transport layer will
    * keep trying until route appears or the connection times
    * itself out.
    */
   security_sk_classify_flow(sk, &fl);
   if (ip_route_output_flow(&rt, &fl, sk, 0))
    goto no_route;
  }
  sk_setup_caps(sk, &rt->u.dst);
 }
 skb->dst = dst_clone(&rt->u.dst);
packet_routed:
 if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
  goto no_route;
/* OK, we know where to send it, allocate and build IP header. */
 iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
 *((__u16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
 iph->tot_len = htons(skb->len);
 if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
  iph->frag_off = htons(IP_DF);
 else
  iph->frag_off = 0;
 iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
 iph->protocol = sk->sk_protocol;
 iph->saddr    = rt->rt_src;
 iph->daddr    = rt->rt_dst;
 skb->nh.iph   = iph;
 /* Transport layer set skb->h.foo itself. */
if (opt && opt->optlen) {
  iph->ihl += opt->optlen >> 2;
  ip_options_build(skb, opt, inet->daddr, rt, 0);
 }
ip_select_ident_more(iph, &rt->u.dst, sk,
        (skb_shinfo(skb)->gso_segs ?: 1) - 1);
/* Add an IP checksum. */
 ip_send_check(iph);
skb->priority = sk->sk_priority;
// 进入OUTPUT点进行过滤, 过滤完成后进入dst_output()函数
 return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
         dst_output);
no_route:
 IP_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
 kfree_skb(skb);
 return -EHOSTUNREACH;
}

// 路由查找函数
int ip_route_output_flow(struct rtable **rp, struct flowi *flp, struct sock *sk, int flags)
{
 int err;
// 普通的路由查找过程, 此过程不是本文重点, 分析略
 if ((err = __ip_route_output_key(rp, flp)) != 0)
  return err;
// 如果流结构协议非0(基本是肯定的)进行xfrm路由查找
 if (flp->proto) {
// 指定流结构的源地址和目的地址
  if (!flp->fl4_src)
   flp->fl4_src = (*rp)->rt_src;
  if (!flp->fl4_dst)
   flp->fl4_dst = (*rp)->rt_dst;
// 根据流结构查找安全路由, 没找到的话创建新的安全路由, 最后形成安全路由链表
// 见前几节中的分析
  return xfrm_lookup((struct dst_entry **)rp, flp, sk, flags);
 }
return 0;
}

对于不是进入ip_queue_xmit()发送的数据包, 在发送前必然也是经过ip_route_output_flow()函数的路由选择处理, 因此如果需要IPSEC封装的话, 也就设置了相关的安全路由链表.

这样, 对于自身发出的数据包, 最终也是进入dst_output()函数进行发送, 转发和自身发出的数据殊途同归了, 以后的处理过程就都是相同的了

函数流程小结:
ip_queue_xmit
  -> ip_route_output_flow
    -> xfrm_lookup
      -> xfrm_find_bundle
        -> bundle_create
          -> afinfo->bundle_create == __xfrm4_bundle_create
            -> xfrm_dst_lookup
              -> afinfo->dst_lookup == xfrm4_dst_lookup
                -> __ip_route_output_key
        -> dst_list
        -> dst->list=policy_bundles, policy->bundles = dst
-> NF_HOOK(NF_OUTPUT)
  -> dst_output
    -> dst->output

9.3 dst_output

/* include/net/dst.h */
/* Output packet to network from transport.  */
static inline int dst_output(struct sk_buff *skb)
{
 return skb->dst->output(skb);
}

dst_output()函数就是调用路由项的输出函数, 对于安全路由, 该函数是xfrm4_output()函数, 对于普通路由, 是ip_output()函数

对于xfrm4_output()函数的分析见7.6, 执行完所有安全路由的输出函数, 每执行一个安全路由输出函数就是一次IPSEC封装处理过程, 封装结束后的数据包会设置IPSKB_REROUTED标志, 到路由链表的最后一项是普通路由, 进入普通路由的输出函数ip_output:

int ip_output(struct sk_buff *skb)
{
 struct net_device *dev = skb->dst->dev;
IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
skb->dev = dev;
 skb->protocol = htons(ETH_P_IP);
// 如果是带IPSKB_REROUTED标志的数据包, 不进入POSTROUTING的SNAT处理, 直接执行
// ip_finish_output函数
 return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
              ip_finish_output,
       !(IPCB(skb)->flags & IPSKB_REROUTED));
}

因此对于封装的数据包而言, 在封装过程中可以进行OUTPUT点的过滤和POSTROUTING点的SNAT处理, 但一旦封装完成, 就不会再进行SNAT操作了.

函数调用小结:
xfrm_lookup: find xfrm_dst for the skb, create dst_list
  -> xfrm_sk_policy_lookup
  -> flow_cache_lookup
  -> xfrm_find_bundle
  -> xfrm_policy_lookup_bytype
  -> xfrm_tmpl_resolve
    -> xfrm_tmpl_resolve_one
      -> xfrm_get_saddr
        -> afinfo->get_saddr == xfrm4_get_saddr
          -> xfrm4_dst_lookup
      -> xfrm_state_find
        -> __xfrm_state_lookup
        -> xfrm_state_alloc
        -> km_query
          -> km->acquire (pfkey_acquire, xfrm_send_acquire)
    -> xfrm_state_sort
      -> afinfo->state_sort == NULL
  -> km_wait_queue
  -> xfrm_bundle_create
dst_output: loop dst_list
  -> dst->output == xfrm_dst->output == xfrm4_output == xfrm4_state_afinfo->output
    -> NF_HOOK(POSTROUTING)
      -> xfrm4_output_finish
        -> gso ?
        -> xfrm4_output_finish2
          -> xfrm4_output_one
            -> mode->output
            -> type->output
            -> skb->dst=dst_pop(skb->dst)
          -> nf_hook(NF_OUTPUT)
            -> !dst->xfrm
              -> dst_output
          -> nf_hook(POSTROUTING)
  -> dst->output == ip_output
    -> NF_HOOK(POSTROUTING)
      -> ip_finish_output
        -> ip_finish_output2
          -> hh_output == dev_queue_xmit

10. 总结

Linux自带的native ipsec实现xfrm是通过路由来实现IPSEC封装处理的, 这和freeswan是类似的, 只不过freeswan构造了虚拟的ipsec*网卡设备, 这样就可以通过标准的网络工具如iproute2等通过配置路由和ip rule等实现安全策略, 进入该虚拟网卡的数据包就进行IPSEC解封, 从虚拟网卡发出的包就是进行IPSEC封装,因此实现比较独立,除了NAT-T需要修改udp.c源码外,其他基本不需要修改内核源码,对于进入的IPSEC包,在物理网卡上可以抓到原始的IPSEC包,而从虚拟网卡上可以抓到解密后的数据包。而xfrm没有定义虚拟网卡,都是在路由查找过程中自动查找安全策略实现ipsec的解封或封装,因此该实现是必须和内核网络代码耦合在一起的,对于进入的IPSEC包,能在物理网卡抓到两次包,一次是IPSEC原始包,一次是解密后的包。由于还是需要根据路由来进行封装,所以本质还不是基于策略的IPSEC,不过可以通过定义策略路由方式来实现基于策略IPSEC,要是能把IPSEC封装作为一个netfilter的target就好了,这样就可以进行标准的基于策略的IPSEC了。

xfrm和网络代码耦合,这样进行路由或netfilter过滤时都可以通过相关标志进行处理或旁路,如经过IPSEC处理后的数据包是自动不会进行SNAT操作的,而freeswan的实现就不能保证,如果设置SNAT规则不对,是有可能对封装好的包进行SNAT操作而造成错误。但两个实现对于封装前的数据包都是可以进行SNAT操作的,因此那种实现同网段×××的特殊NAT可以在xfrm下实现。

在RFC2367中只定义了SA相关操作的消息类型,而没有定义SP的操作类型,也没有定义其他扩展的IPSEC功能的相关消息类型,如NAT-T相关的类型,那些SADB_X_*的消息类型就是非标准的,这就造成各种IPSEC实现只能自己定义这些消息类型,因此可能会造成不兼容的现象,应该尽快出新的RFC来更新2367了。

转载于:https://blog.51cto.com/enchen/158002

Linux内核中的IPSEC实现(7)相关推荐

  1. Linux内核中的IPSEC实现(1)

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

  2. Linux内核中的IPSEC实现2

    http://blog.chinaunix.net/uid-127037-id-2919563.html 5. 安全策略(xfrm_policy)处理 本节所介绍的函数都在net/xfrm/xfrm_ ...

  3. Linux内核中的IPSEC实现(3)

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

  4. Linux内核中的IPSEC实现(3) ---转载

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

  5. linux内核中TCP接收的实现

    linux内核中TCP接收的实现入口函数是tcp_v4_rcv 1. 数据包检查处理 一开始做一些数据包详细检查处理,一旦出错,可能导致内核挂掉 int tcp_v4_rcv(struct sk_bu ...

  6. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  7. 简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定

    众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对于TCP ...

  8. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  9. 如何放出Linux内核中的链表大招

    前言 上回,我们说到Linux内核中max()宏的终极奥义,Linux内核链表也不甘示弱,那么接下来,让我们看看Linux内核中的链表大招. 如何放出Linux内核中的链表大招 前言 一.链表简介 ( ...

最新文章

  1. Spring Boot 服务监控,健康检查,线程信息,JVM堆信息,指标收集,运行情况监控...
  2. iOS - 数据的归档和反归档
  3. html未点击背景 点击背景,在AngularJs中点击状态如何改变背景色
  4. Android 系统(271)---进程、守护进程的实现及进程拉活
  5. Blazor UI事件和渲染
  6. matlab2c使用c++实现matlab函数开发配置全解
  7. Julia语言:让高性能科学计算人人可用(转)
  8. android 极光IM集成及使用
  9. FPGA 38译码器
  10. win10修改dns服务器命令,win10怎么修改DNS服务器?win10修改DNS服务器的方法
  11. 清空SQL数据库日志|数据库开发|SQL|web开发|快速开发|中间件平台|方正飞鸿|ES2007|
  12. C#打印正三角、倒三角、平行四边形
  13. 爱奇艺自主研发的动态化框架!
  14. redis 五大数据类型
  15. 第1章 计算机网络和因特网-计算机网络
  16. Markdown语法详细整理
  17. 10个最佳价格行动交易模式
  18. iOS开发之仿微博视频边下边播之自定义AVPlayer播放器, 边下边播解剖。视频处理流程,建立连接-请求数据-统筹数据-解码数据-视频呈现
  19. matlab的meadian函数_24 第二十四章 时间序列模型_W
  20. nes模拟器java怎么用_PC版FC模拟器怎么用 VirtuaNES使用设置教程

热门文章

  1. MySQL行(记录)的详细操作
  2. [No000018C]Vim清除上次的搜索高亮结果-Vim使用技巧(1)
  3. 十天学Linux内核之第二天---进程
  4. [转]计算机读研的取向
  5. TP3.2.3 页面跳转后 Cookie 失效 —— 参考解决方案
  6. wangEditor 菜单栏随页面滚动位置改变(吸顶)问题解决
  7. 解决js跨域调用WebApi的问题
  8. ES使用org.elasticsearch.client.transport.NoNodeAvailableException: No node available 错误解决方法
  9. 解决minikube启动时若干问题
  10. Requirejs加载超时问题的一个解决方法:设置waitSeconds=0