ipsec 加密流程(四):封装状态机和发送流程_Ipsec加密流程

  • Author       : 叨陪鲤
  • Email         : vip_13031075266@163.com
  • Date          : 2020.11.30
  • Copyright : 未经同意不得转载!!!
  • Version    : openswan-2.6.51.5
  • Referencehttps://download.openswan.org/openswan/

目录

1. ipsec封装状态机:

2. ipsec发送流程:


1. ipsec封装状态机:

ipsec封装状态机是一个很经典有限状态机实现:(当前状态,执行动作,下一个状态),同时它的核心调度流程也是比较简单:如果当前状态执行成功,则跳转至下一状态。此外在ipsec状态机中,可以通过实际参数配置(如ESP封装、IP隧道封装、AH封装等)来自动选择下一状态,这个是在ipsec_xmit_cont()函数中实现的。

我们直接来看状态机和核心调度流程:

struct {

enum ipsec_xmit_value (*action)(struct ipsec_xmit_state *ixs);

int next_state;

} xmit_state_table[] = {

[IPSEC_XSM_INIT1]                     =         {ipsec_xmit_init1,               IPSEC_XSM_INIT2 },

[IPSEC_XSM_INIT2]                     =         {ipsec_xmit_init2,               IPSEC_XSM_ENCAP_INIT },

[IPSEC_XSM_ENCAP_INIT]         =         {ipsec_xmit_encap_init,          IPSEC_XSM_ENCAP_SELECT },

[IPSEC_XSM_ENCAP_SELECT]  =         {ipsec_xmit_encap_select,  IPSEC_XSM_DONE },

[IPSEC_XSM_ESP]                       =         {ipsec_xmit_esp,                 IPSEC_XSM_ESP_AH },

[IPSEC_XSM_ESP_AH]                =         {ipsec_xmit_esp_ah,              IPSEC_XSM_CONT },

[IPSEC_XSM_AH]                         =         {ipsec_xmit_ah,                  IPSEC_XSM_CONT },

[IPSEC_XSM_IPIP]                       =         {ipsec_xmit_ipip,                IPSEC_XSM_CONT },

[IPSEC_XSM_IPCOMP]               =         {ipsec_xmit_ipcomp,              IPSEC_XSM_CONT },

[IPSEC_XSM_CONT]                   =         {ipsec_xmit_cont,                IPSEC_XSM_DONE },

[IPSEC_XSM_DONE]                   =         {NULL,                           IPSEC_XSM_DONE},

};

ipsec_xsm(struct ipsec_xmit_state *ixs)

{

enum ipsec_xmit_value stat = IPSEC_XMIT_ENCAPFAIL;

unsigned more_allowed;

 

more_allowed = 1000;

while (ixs->state != IPSEC_XSM_DONE && --more_allowed) {

         ixs->next_state = xmit_state_table[ixs->state].next_state;

         stat = xmit_state_table[ixs->state].action(ixs);

 

        if (stat == IPSEC_XMIT_OK) {

       ixs->state = ixs->next_state;

        } else if (stat == IPSEC_XMIT_PENDING) {

         return;

        } else {

         /* bad result, force state change to done */

         ixs->state = IPSEC_XSM_DONE;

        }

}

/*

 * let the caller continue with their processing

 */

ixs->xsm_complete(ixs, stat);

}

IP报文在经过ipsec_xsm状态机完成报文封装后,进入到xsm_complete函数进行后续收尾处理,之后发送此ipsec报文。

2. ipsec发送流程:

此部分函数入口为:

ipsec_tunnel_xsm_complete(ixs, stat);

此函数的函数调用关系基本如下所示:

  • ipsec_tunnel_xsm_complete
  • ipsec_extract_ports
  • ipsec_findroute
    • 存在隧道嵌套的情况,则再次进入ipsec_xsm进行封装
  • ipsec_nat_encap
  • ipsec_tunnel_restore_hard_header
  • ipsec_tunnel_send
    • ipsec_xmit_send
      • ipsec_set_dst
      • ipsec_xmit_send2_mast
      • ipsec_xmit_send2
  • 判断封装完毕后的状态是否成功:

If (stat != IPSEC_XMIT_OK)

如果不正确,则说明封装有错误,则将释放相应的资源。

  • 获取新封装完毕的报文五元组信息,重新组成ixs->matcher

ixs->matcher.sen_type = SENT_IP4

协议族类型

ixs->matcher.sen_ip_src.s_addr = osw_ip4_hdr(ixs)->saddr;

源IP

ixs->matcher.sen_ip_dst.s_addr = osw_ip4_hdr(ixs)->daddr;

目的IP

nexthdr = osw_ip4_hdr(ixs)->protocol;

四层协议

ixs->matcher.sen_proto = nexthdr;

-

  • 获取新封装完毕的报文端口信息,存储在ixs->matcher

udp = skb_header_pointer(skb, nexthdroff, sizeof(*udp), &_udp);

ixs->matcher.sen_sport = udp->source;

ixs->matcher.sen_dport = udp->dest;

  • 再次根据ixs->matcher查询eroute路由表:

ixs->eroute = ipsec_findroute(&ixs->matcher);

查询eroute路由表

ixs->outgoing_said = ixs->eroute->er_said;

查到后记录said

ixs->eroute_pid = ixs->eroute->er_pid;

 

ixs->eroute->er_count++;

 

  • 如果封装后的报文仍然处于其他隧道的保护子网中,则再次进行封装:

ip_address_cmp(&ixs->orgedst, &ixs->outgoing_said.dst) != 0 &&

!ip_address_isany(&ixs->outgoing_said.dst) &&

ixs->eroute

 上述三个条件同时满足的情况下,则进入封装状态机ipsec_xsm(ixs)继续新的隧道封装。 

  • 如果隧道中间经过NAT设备,则进行NAT-T封装

NAT-T封装报文基本格式如下:

ipsec 加密流程(四):封装状态机和发送流程_openswan源码分析_02

 

在此基础上,需要区分IKE协商报文和业务流量报文(一般是ESP)。当ipsec隧道经过NAT(NATP)设备时,IKE通过端口浮动(port floating)将端口由500浮动到4500;而业务流量报文为了适应NAT环境,也需要进行UDP格式封装,两者仅仅通过端口是无法区分的(目的端口都是4500,源端口可由NAT修改)。因此采用了一个特殊标记来分区:

IKE协商过程中(IKEv1或者IKEv2),最初的两个字段分别为Initiator SPI和Responder SPI, 它们各占用8Bytes。 在IKE协商过程中有一条规则:Initiator SPI不能为0, 而Responder SPI在第一包中可以为0。基于上述规则,IPsec业务流量报文封装时,在UDP头部之后添加8字节的额外空间,全部填充0,NAT-T便是通过这8个字节是否全部为0来区分协商报文和业务报文的。规则如下:

UDP后面的8个字节如果不为0, 则是IKE协商报文(IKE协议要求的)

UDP后面的8个字节全为0, 则是IPSec业务流量报文.

ipsec 加密流程(四):封装状态机和发送流程_openswan源码分析_03

因此,在Ipsec_xmit_init2()函数中,预留头部和尾部空间时,NAT-T的相关操作为:

switch (ixs->natt_type) {

     case ESPINUDP_WITH_NON_IKE:

                ixs->natt_head = sizeof(struct udphdr)+(2*sizeof(__u32));

                break;

     case ESPINUDP_WITH_NON_ESP:

               ixs->natt_head = sizeof(struct udphdr);

               break;

     default:

               goto cleanup;

               break;

}

 

后续操作便是将报文内容后移,填充UDP头部(UDP头部中的校验和未填充):

源端口

udp->source = htons(ixs->natt_sport);

目的端口

udp->dest   = htons(ixs->natt_dport);

udp长度

udp->len    = htons(ntohs(ipp->tot_len) - ixs->iphlen);

IP头部协议字段

ipp->protocol = IPPROTO_UDP;

IP头部校验和

ipp->check  = 0;

ipp->check  = ip_fast_csum((unsigned char *)ipp, ipp->ihl);

至此, NAT-T场景下,UDP封装IPsec报文操作完毕。

  • 填充封装后报文的二层头

因为我们在ipsec_xmit_init2中已经将原先IP报文的二层头存储在ixs->saved_header中,在此,我们再将其添加到封装完毕的IP报文前。

if(ixs->saved_header) {

          skb_push(ixs->skb, ixs->hard_header_len);/*skb_push:添加头部*/

          {

                   int i;

                   for (i = 0; i < ixs->hard_header_len; i++) {

                         ixs->skb->data[i] = ixs->saved_header[i];

                   }

          }

}

  • 最后交由ipsec_tunnel_send()函数来发送封装完毕的报文。