Linux内核中的IPSEC实现(7)

146 篇文章 11 订阅
81 篇文章 0 订阅
Java代码   收藏代码  
  1. 9. IPSEC封装流程  
  2.    
  3. IPSEC  
  4. 数据包的封装过程是在数据包发出前完成的, 是和路由选择密切相关的, 根据前面的发出分析可知封装是通过对数据设置安全路由链表来实现的, 因此对数据包的IPSEC封装流程可以简单描述如下:  
  5. 1) 对于进入的数据包, 进行路由选择, 如果是转发的, 进入路由输入, 然后查找安全策略检查是否需要IPSEC封装, 如果需要封装, 就查找和创建相关的安全路由, 进入路由输出处理, 在路由输出时即按照安全路由一层层地封装数据包最后得到IPSEC包发出;  
  6. 2) 对于自身发出的数据包, 需要进行路由选择, 选定路由后进入路由输入, 查找安全策略进行处理, 以后和转发的数据包IPSEC封装就是完全相同了。  
  7.   
  8. 9.1 转发包的封装  
  9.   
  10. 数据的转发入口点函数是ip_forward, 进入该函数的数据包还是普通数据包,数据包的路由也是普通路由:  
  11.   
  12. /* net/ipv4/ip_forward.c */  
  13. int ip_forward(struct sk_buff *skb)  
  14. {  
  15.  struct iphdr *iph; /* Our header */  
  16.  struct rtable *rt; /* Route we use */  
  17.  struct ip_options * opt = &(IPCB(skb)->opt);  
  18. // 对转发的数据包进行安全策略检查, 检查失败的话丢包  
  19.  if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))  
  20.   goto drop;  
  21.  if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))  
  22.   return NET_RX_SUCCESS;  
  23. // 转发包也是到自身的包, 不是的话丢包  
  24.  if (skb->pkt_type != PACKET_HOST)  
  25.   goto drop;  
  26.  skb->ip_summed = CHECKSUM_NONE;  
  27.    
  28.  /* 
  29.   * According to the RFC, we must first decrease the TTL field. If 
  30.   * that reaches zero, we must reply an ICMP control message telling 
  31.   * that the packet's lifetime expired. 
  32.   */  
  33. // TTL到头了, 丢包  
  34.  if (skb->nh.iph->ttl <= 1)  
  35.                 goto too_many_hops;  
  36. // 进入安全路由选路和转发处理, 在此函数中构造数据包的安全路由  
  37.  if (!xfrm4_route_forward(skb))  
  38.   goto drop;  
  39. // 以下是一些常规的路由和TTL处理  
  40.  rt = (struct rtable*)skb->dst;  
  41.  if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)  
  42.   goto sr_failed;  
  43.  /* We are about to mangle packet. Copy it! */  
  44.  if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))  
  45.   goto drop;  
  46.  iph = skb->nh.iph;  
  47.  /* Decrease ttl after skb cow done */  
  48.  ip_decrease_ttl(iph);  
  49.  /* 
  50.   * We now generate an ICMP HOST REDIRECT giving the route 
  51.   * we calculated. 
  52.   */  
  53.  if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr)  
  54.   ip_rt_send_redirect(skb);  
  55.  skb->priority = rt_tos2priority(iph->tos);  
  56. // 进行FORWARD点过滤, 过滤后进入ip_forward_finish函数  
  57.  return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,  
  58.          ip_forward_finish);  
  59. sr_failed:  
  60.         /* 
  61.   * Strict routing permits no gatewaying 
  62.   */  
  63.          icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);  
  64.          goto drop;  
  65. too_many_hops:  
  66.         /* Tell the sender its packet died... */  
  67.         IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);  
  68.         icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);  
  69. drop:  
  70.  kfree_skb(skb);  
  71.  return NET_RX_DROP;  
  72. }  
  73. // ip_forward_finish函数主要就是调用dst_output函数  
  74. static inline int ip_forward_finish(struct sk_buff *skb)  
  75. {  
  76.  struct ip_options * opt = &(IPCB(skb)->opt);  
  77.  IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS);  
  78.  if (unlikely(opt->optlen))  
  79.   ip_forward_options(skb);  
  80.    
  81.  return dst_output(skb);  
  82. }  
  83.   
  84. 核心函数是xfrm4_route_forward函数  
  85. /* include/net/xfrm.h */  
  86. static inline int xfrm4_route_forward(struct sk_buff *skb)  
  87. {  
  88.  return xfrm_route_forward(skb, AF_INET);  
  89. }  
  90.   
  91. static inline int xfrm_route_forward(struct sk_buff *skb, unsigned short family)  
  92. {  
  93. // 如果没有发出方向的安全策略的话返回  
  94.  return !xfrm_policy_count[XFRM_POLICY_OUT] ||  
  95. // 如果路由标志专门设置不进行IPSEC封装的话也返回  
  96.   (skb->dst->flags & DST_NOXFRM) ||  
  97.   __xfrm_route_forward(skb, family);  
  98. }  
  99. /* net/xfrm/xfrm_policy.c */  
  100. int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)  
  101. {  
  102.  struct flowi fl;  
  103. // 路由解码, 填充流结构参数,  
  104. // 对IPV4实际调用的是_decode_session4(net/ipv4/xfrm4_policy.c)函数  
  105.  if (xfrm_decode_session(skb, &fl, family) < 0)  
  106.   return 0;  
  107. // 根据流结构查找安全路由, 没找到的话创建新的安全路由, 最后形成安全路由链表  
  108. // 见前几节中的分析  
  109.  return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;  
  110. }  
  111.   
  112. 因此数据进行转发处理后, 最终进入dst_output函数处理  
  113.     
  114.   
  115. 转发函数流程小结:  
  116.     
  117. ip_forward  
  118.   -> xfrm4_route_forward (net/xfrm.h, get xfrm_dst)  
  119.     -> xfrm_route_forward  
  120.       -> __xfrm_route_forward  
  121.         -> xfrm_lookup  
  122.           -> xfrm_find_bundle  
  123.             -> afinfo->find_bundle == __xfrm4_find_bundle  
  124.           -> xfrm_bundle_create  
  125.             -> afinfo->bundle_create == __xfrm4_bundle_create  
  126.               tunnel mode  
  127.               -> xfrm_dst_lookup  
  128.                 -> afinfo->dst_lookup == xfrm4_dst_lookup  
  129.                   -> __ip_route_output_key  
  130.           -> dst_list: dst->list=policy_bundles, policy->bundles = dst  
  131.   -> NF_HOOK(NF_FORWARD)  
  132.   -> ip_forward_finish  
  133.   -> dst_output  
  134.    
  135. 9.2 自身数据发出  
  136.   
  137. 对于IPv4包的发出, 通常出口函数是ip_queue_xmit或ip_push_pending_frames, 如果是后者, 数据包是已经经过了路由选择的, 而前者还没有进行路由选择, 两者最后都会调用dst_output()函数进行数据的发出.  
  138.   
  139. /* net/ipv4/ip_output.c */  
  140. int ip_queue_xmit(struct sk_buff *skb, int ipfragok)  
  141. {  
  142.  struct sock *sk = skb->sk;  
  143.  struct inet_sock *inet = inet_sk(sk);  
  144.  struct ip_options *opt = inet->opt;  
  145.  struct rtable *rt;  
  146.  struct iphdr *iph;  
  147.  /* Skip all of this if the packet is already routed, 
  148.   * f.e. by something like SCTP. 
  149.   */  
  150. // 已经路由过的数据跳过路由查找过程  
  151.  rt = (struct rtable *) skb->dst;  
  152.  if (rt != NULL)  
  153.   goto packet_routed;  
  154.  /* Make sure we can route this packet. */  
  155.  rt = (struct rtable *)__sk_dst_check(sk, 0);  
  156.  if (rt == NULL) {  
  157.   __be32 daddr;  
  158.   /* Use correct destination address if we have options. */  
  159.   daddr = inet->daddr;  
  160.   if(opt && opt->srr)  
  161.    daddr = opt->faddr;  
  162.   {  
  163.    struct flowi fl = { .oif = sk->sk_bound_dev_if,  
  164.          .nl_u = { .ip4_u =  
  165.             { .daddr = daddr,  
  166.        .saddr = inet->saddr,  
  167.        .tos = RT_CONN_FLAGS(sk) } },  
  168.          .proto = sk->sk_protocol,  
  169.          .uli_u = { .ports =  
  170.              { .sport = inet->sport,  
  171.         .dport = inet->dport } } };  
  172.    /* If this fails, retransmit mechanism of transport layer will 
  173.     * keep trying until route appears or the connection times 
  174.     * itself out. 
  175.     */  
  176.    security_sk_classify_flow(sk, &fl);  
  177.    if (ip_route_output_flow(&rt, &fl, sk, 0))  
  178.     goto no_route;  
  179.   }  
  180.   sk_setup_caps(sk, &rt->u.dst);  
  181.  }  
  182.  skb->dst = dst_clone(&rt->u.dst);  
  183. packet_routed:  
  184.  if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)  
  185.   goto no_route;  
  186.  /* OK, we know where to send it, allocate and build IP header. */  
  187.  iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));  
  188.  *((__u16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));  
  189.  iph->tot_len = htons(skb->len);  
  190.  if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)  
  191.   iph->frag_off = htons(IP_DF);  
  192.  else  
  193.   iph->frag_off = 0;  
  194.  iph->ttl      = ip_select_ttl(inet, &rt->u.dst);  
  195.  iph->protocol = sk->sk_protocol;  
  196.  iph->saddr    = rt->rt_src;  
  197.  iph->daddr    = rt->rt_dst;  
  198.  skb->nh.iph   = iph;  
  199.  /* Transport layer set skb->h.foo itself. */  
  200.  if (opt && opt->optlen) {  
  201.   iph->ihl += opt->optlen >> 2;  
  202.   ip_options_build(skb, opt, inet->daddr, rt, 0);  
  203.  }  
  204.  ip_select_ident_more(iph, &rt->u.dst, sk,  
  205.         (skb_shinfo(skb)->gso_segs ?: 1) - 1);  
  206.  /* Add an IP checksum. */  
  207.  ip_send_check(iph);  
  208.  skb->priority = sk->sk_priority;  
  209. // 进入OUTPUT点进行过滤, 过滤完成后进入dst_output()函数  
  210.  return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,  
  211.          dst_output);  
  212. no_route:  
  213.  IP_INC_STATS(IPSTATS_MIB_OUTNOROUTES);  
  214.  kfree_skb(skb);  
  215.  return -EHOSTUNREACH;  
  216. }  
  217.   
  218. // 路由查找函数  
  219. int ip_route_output_flow(struct rtable **rp, struct flowi *flp, struct sock *sk, int flags)  
  220. {  
  221.  int err;  
  222. // 普通的路由查找过程, 此过程不是本文重点, 分析略  
  223.  if ((err = __ip_route_output_key(rp, flp)) != 0)  
  224.   return err;  
  225. // 如果流结构协议非0(基本是肯定的)进行xfrm路由查找  
  226.  if (flp->proto) {  
  227. // 指定流结构的源地址和目的地址  
  228.   if (!flp->fl4_src)  
  229.    flp->fl4_src = (*rp)->rt_src;  
  230.   if (!flp->fl4_dst)  
  231.    flp->fl4_dst = (*rp)->rt_dst;  
  232. // 根据流结构查找安全路由, 没找到的话创建新的安全路由, 最后形成安全路由链表  
  233. // 见前几节中的分析  
  234.   return xfrm_lookup((struct dst_entry **)rp, flp, sk, flags);  
  235.  }  
  236.  return 0;  
  237. }  
  238.   
  239. 对于不是进入ip_queue_xmit()发送的数据包, 在发送前必然也是经过ip_route_output_flow()函数的路由选择处理, 因此如果需要IPSEC封装的话, 也就设置了相关的安全路由链表.  
  240.   
  241. 这样, 对于自身发出的数据包, 最终也是进入dst_output()函数进行发送, 转发和自身发出的数据殊途同归了, 以后的处理过程就都是相同的了  
  242.   
  243. 函数流程小结:  
  244. ip_queue_xmit  
  245.   -> ip_route_output_flow  
  246.     -> xfrm_lookup  
  247.       -> xfrm_find_bundle  
  248.         -> bundle_create  
  249.           -> afinfo->bundle_create == __xfrm4_bundle_create  
  250.             -> xfrm_dst_lookup  
  251.               -> afinfo->dst_lookup == xfrm4_dst_lookup  
  252.                 -> __ip_route_output_key  
  253.         -> dst_list  
  254.         -> dst->list=policy_bundles, policy->bundles = dst  
  255.   -> NF_HOOK(NF_OUTPUT)  
  256.   -> dst_output  
  257.     -> dst->output  
  258.   
  259. 9.3 dst_output  
  260.   
  261. /* include/net/dst.h */  
  262. /* Output packet to network from transport.  */  
  263. static inline int dst_output(struct sk_buff *skb)  
  264. {  
  265.  return skb->dst->output(skb);  
  266. }  
  267.   
  268. dst_output()函数就是调用路由项的输出函数, 对于安全路由, 该函数是xfrm4_output()函数, 对于普通路由, 是ip_output()函数  
  269.   
  270. 对于xfrm4_output()函数的分析见7.6, 执行完所有安全路由的输出函数, 每执行一个安全路由输出函数就是一次IPSEC封装处理过程, 封装结束后的数据包会设置IPSKB_REROUTED标志, 到路由链表的最后一项是普通路由, 进入普通路由的输出函数ip_output:  
  271.   
  272. int ip_output(struct sk_buff *skb)  
  273. {  
  274.  struct net_device *dev = skb->dst->dev;  
  275.  IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);  
  276.  skb->dev = dev;  
  277.  skb->protocol = htons(ETH_P_IP);  
  278. // 如果是带IPSKB_REROUTED标志的数据包, 不进入POSTROUTING的SNAT处理, 直接执行  
  279. // ip_finish_output函数  
  280.  return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,  
  281.               ip_finish_output,  
  282.        !(IPCB(skb)->flags & IPSKB_REROUTED));  
  283. }  
  284.   
  285. 因此对于封装的数据包而言, 在封装过程中可以进行OUTPUT点的过滤和POSTROUTING点的SNAT处理, 但一旦封装完成, 就不会再进行SNAT操作了.  
  286.   
  287. 函数调用小结:  
  288. xfrm_lookup: find xfrm_dst for the skb, create dst_list  
  289.   -> xfrm_sk_policy_lookup  
  290.   -> flow_cache_lookup  
  291.   -> xfrm_find_bundle  
  292.   -> xfrm_policy_lookup_bytype  
  293.   -> xfrm_tmpl_resolve  
  294.     -> xfrm_tmpl_resolve_one  
  295.       -> xfrm_get_saddr  
  296.         -> afinfo->get_saddr == xfrm4_get_saddr  
  297.           -> xfrm4_dst_lookup  
  298.       -> xfrm_state_find  
  299.         -> __xfrm_state_lookup  
  300.         -> xfrm_state_alloc  
  301.         -> km_query  
  302.           -> km->acquire (pfkey_acquire, xfrm_send_acquire)  
  303.     -> xfrm_state_sort  
  304.       -> afinfo->state_sort == NULL  
  305.   -> km_wait_queue  
  306.   -> xfrm_bundle_create  
  307.    
  308. dst_output: loop dst_list  
  309.   -> dst->output == xfrm_dst->output == xfrm4_output == xfrm4_state_afinfo->output  
  310.     -> NF_HOOK(POSTROUTING)  
  311.       -> xfrm4_output_finish  
  312.         -> gso ?  
  313.         -> xfrm4_output_finish2  
  314.           -> xfrm4_output_one  
  315.             -> mode->output  
  316.             -> type->output  
  317.             -> skb->dst=dst_pop(skb->dst)  
  318.           -> nf_hook(NF_OUTPUT)  
  319.             -> !dst->xfrm  
  320.               -> dst_output  
  321.           -> nf_hook(POSTROUTING)  
  322.   -> dst->output == ip_output  
  323.     -> NF_HOOK(POSTROUTING)  
  324.       -> ip_finish_output  
  325.         -> ip_finish_output2  
  326.           -> hh_output == dev_queue_xmit  
  327.   
  328. 10. 总结  
  329.   
  330. 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了。  
  331.   
  332. xfrm和网络代码耦合,这样进行路由或netfilter过滤时都可以通过相关标志进行处理或旁路,如经过IPSEC处理后的数据包是自动不会进行SNAT操作的,而freeswan的实现就不能保证,如果设置SNAT规则不对,是有可能对封装好的包进行SNAT操作而造成错误。但两个实现对于封装前的数据包都是可以进行SNAT操作的,因此那种实现同网段VPN的特殊NAT可以在xfrm下实现。  
  333.   
  334. 在RFC2367中只定义了SA相关操作的消息类型,而没有定义SP的操作类型,也没有定义其他扩展的IPSEC功能的相关消息类型,如NAT-T相关的类型,那些SADB_X_*的消息类型就是非标准的,这就造成各种IPSEC实现只能自己定义这些消息类型,因此可能会造成不兼容的现象,应该尽快出新的RFC来更新2367了。  
  335.    
  336.   
  337. 发表于: 2007-06-21,修改于: 2007-06-21 21:19,已浏览4125次,有评论4条 推荐 投诉  
  338.     网友: Evan    时间:2008-04-21 10:52:26 IP地址:58.34.238.★  
  339.       
  340.   
  341. 发送数据的时候, 如果发现没有sa bundle,或者说没有SA,那么XFRM 如何启动IKE过程呢? 需要额外添加code吗?   
  342.   
  343.   
  344.   
  345. 我看xfrm没有 sa的时候就返回err。 奇怪哈?  
  346.   
  347.   
  348.     网友: yfydz   时间:2008-04-21 21:05:13 IP地址:58.31.248.★  
  349.       
  350.   

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值