热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ipsec加密流程(二):ipsec初始化操作

《openswan》专栏系列文章主要是记录openswan源码学习过程中的笔记。Author:叨陪鲤Email:vip_13031075266163.comDate:2020.1

《openswan》专栏系列文章主要是记录openswan源码学习过程中的笔记。

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


目录

1. 加密流程状态机实现:

2. 各状态函数基本功能总览:

4. 各状态功能概要

4.1 ipsec_xmit_init1():

4.2 ipsec_xmit_init2():

4.3 ipsec_xmit_encap_init():



1. 加密流程状态机实现:

如果还不知道状态机是什么东东的,可以参考文章《C语言实现有限状态机》,该博客里简单的说明了状态机的实现原理和步骤。这里跳过状态机的初级讲解,而是直接进入高阶实现。

下面是IPSec封装流程中的核心:加密状态机

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},
};

状态机核心调度处理流程代码:

void
ipsec_xsm(struct ipsec_xmit_state *ixs)
{enum ipsec_xmit_value stat = IPSEC_XMIT_ENCAPFAIL;unsigned more_allowed;if (ixs == NULL) {return;}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;}}ixs->xsm_complete(ixs, stat);
}

 

2. 各状态函数基本功能总览:


状态对应的动作(函数)作用备注
IPSEC_XSM_INIT1

ipsec_xmit_init1

根据outgoing_id查找对应的IPsec_sa 
IPSEC_XSM_INIT2

ipsec_xmit_init2

根据IPsec_sa封装策略确定统计封装需要空间大小 
IPSEC_XSM_ENCAP_INIT

ipsec_xmit_encap_init

开始封装,开辟报文头部、尾部空间真正开始修改报文内容
IPSEC_XSM_ENCAP_SELECT

ipsec_xmit_encap_select

t
根据IPsecsa封装协议选择封装套件 
IPSEC_XSM_ESP

ipsec_xmit_esp

ESP封装套件 
IPSEC_XSM_ESP_AH

ipsec_xmit_esp_ah

ESP+AH封装套件 
IPSEC_XSM_AH

ipsec_xmit_ah

AH封装套件 
IPSEC_XSM_IPIP

ipsec_xmit_ipip

隧道模式封装套件 
IPSEC_XSM_IPCOMP

ipsec_xmit_ipcomp

IPcom封装套件 
IPSEC_XSM_CONT

ipsec_xmit_cont

根据IPsecsa封装策略确定是否需要继续封装是否需要继续封装
IPSEC_XSM_DONE

NULL

IPsec封装完毕 

 

4. 各状态功能概要


4.1 ipsec_xmit_init1():

此函数最关键的功能便是根据outgoing_said获取到对应的IPSec SA。 outgoing_said是在IPSec匹配流程根据报文五元组找到对应的eroute,其中eroute结构中包含said指针。

  • 根据outgoing_said查询ipsec sa
    • ixs->ipsp = ipsec_sa_getbyid(&ixs->outgoing_said, IPSEC_REFTX);
    • 查询失败,返回:IPSEC_XMIT_SAIDNOTFOUND
  • 构建ixs->ips

4.2 ipsec_xmit_init2():

在IKE协商流程成功时,会构建IPSec SA时用来加密业务流量信息。IPSec SA的是一组用来描述封装信息的结构。一个IPSec 隧道可能包含多个IPSec SA节点(视封装协议决定),这几个节点共同构成此隧道的IPSecSA链,在进行报文封装时,依据IPSecSA链进行依次封装,封装包含严格的顺序:通常情况下按下图顺序进行封装:

下面介绍ipsec_xmit_init2()函数的主要功能:

①保存IPSec SA链的头部:

save_ipsp=ixs->ipsp;

②统计IPsec封装需要的headroomtailroom

  • AH协议

ixs->headroom += sizeof(struct ahhdr);


  • ESP协议

ESP协议实现较为复杂。在openswan源码实现中提到了一个OCF技术,这是一种开源加密加速框架,OCF是一种开源加速框架,支持DES, 3DES, AES, MD5, SHA1等算法。详情可参考:http://ocf-linux.sourceforge.net/

下面考虑系统不支持OCF加密框架:

             1) 获取加密算法:

ixs->ixt_e  =  ixs->ipsp->ips_alg_enc

ixs->blocksize  =  ixs->ixt_e->xxx

ixs->headroom  =  ixs->ixt_e->ooo

             2) 获取认证算法:

ixs->ixt_a=ixs->ipsp->ips_alg_auth

ixs->tailroom += AHHMAC_HASHLEN

【note】:加密、认证算法都会至少要求4字节对齐,因此openswan中使用了一套组合拳顺利将我打蒙:

ixs->tailroom += ixs->blocksize != 1 ?((ixs->blocksize - ((ixs->pyldsz + 2) % ixs->blocksize)) % ixs->blocksize) + 2 :((4 - ((ixs->pyldsz + 2) % 4)) % 4) + 2;

这里我做了一个简单计算:

Pyldsz

((ixs->pyldsz + 2) % 4)

(4 - ((ixs->pyldsz + 2) % 4))

((4 - ((ixs->pyldsz + 2) % 4)) % 4)

((4 - ((ixs->pyldsz + 2) % 4)) % 4) + 2

0

2

2

2

4

1

3

1

1

3

2

0

4

0

2

3

1

3

3

5

4

2

2

2

4

5

3

1

1

3

6

0

4

0

2

7

1

3

3

5

8

2

2

2

4

9

3

1

1

3

     

从上表中可以看出:第一列值+最后一列值都是4的整数倍。因此上述那个计算表达式应该就是这个意思:【tailroom+pyldsz 要保证4字节对齐】

             3) NAT-T封装空间预留:

如果IPSec SA上表明需要使用NAT-T,则需要根据报文类型确定采用封装方式:

                   3.1)IKE协商报文

使用ESPINUDP_WITH_NON_IKE格式封装:

UPD + 8字节

                   3.2)业务流量报文

使用ESPINUDP_WITH_NON_ESP格式封装

UPD

  【特别说明】:

ESPINUDP_WITH_NON_IKE封装包,UDP头部后面的8字节全部为0,而ESPINUDP_WITH_NON_ESP报文UDP头部之后紧跟SPI值,此参数不可能全部为0(8个字节全部为0)。从而区分开来是IKE协商报文or业务流量报文。

最后:计算NAT-T需要预留的空间大小:

ixs->tailroom += ixs->natt_head;


  • IPIP协议

       1)添加一个IP头部空间(IPv4 or IPv6):

 ixs->headroom += sizeof(struct ipv6hdr);

 ixs->headroom += sizeof(struct iphdr);

       2)在ixs上记录下一个头部协议:

ixs->ipip_proto =  IPPROTO_IPV6    or  IPPROTO_IPIP


  • IPcom协议

         略。(openswan此版本未实现...)


 

headroom, tailroom更新到ixs上,遍历此链上后续的IPSec SA协议(重新从②开始);

ixs->ipsp = ixs->ipsp->ips_next;

③IPSec封装链上的多个IPSec SA协议的headroomtailroom统计完毕后,将ixs->ipsp指向此链表头部:

ixs->ipsp = saved_ipsp;

④计算MTU相关

⑤传输模式时:根据mtu调整TCP协议SYN包中的MSS

tcph->syn && !tcph->ack

 ipsec_adjust_mss(ixs->skb, tcph, ixs->cur_mtu)

⑥保存二层头,并将报文中的二层头去除

If(!ixs->hard_header_stripped)

 

 

memcpy(&ixs->saved_header[0], &ixs->skb->data[0], ixs->hard_header_len)

 

skb_pull(ixs->skb, ixs->hard_header_len);

 

ixs->hard_header_stripped = 1;

【追踪溯源】:

IPSec_xmit_init2()函数中会根据算法信息(encalg, authalg, keylen, iv,etc.)计算需要的headroom tailroom。其中算法的来源如下:

 

4.3 ipsec_xmit_encap_init():

①   获取IP头部长度和报文负载长度

ixs->iphlen &#61; osw_ip4_hdr(ixs)->ihl <<2;

ixs->pyldsz &#61; ntohs(osw_ip4_hdr(ixs)->tot_len) - ixs->iphlen;

②   根据ipsecsa链上的封装协议移动报文数据(head, tail)

       1&#xff09; AH协议

ixs->headroom &#43;&#61; sizeof(struct ahhdr);

       2&#xff09; ESP协议

          2.1&#xff09; 加密算法

  • OCF加速框架?

    &#xff08;i&#xff09;DES/3DES

ixs->blocksize &#61; 8;

ixs->headroom &#43;&#61; ESP_HEADER_LEN &#43; 8/*IV size*

&#xff08;ii&#xff09;AES

ixs->blocksize &#61; 16;

ixs->headroom &#43;&#61; ESP_HEADER_LEN &#43; 16/*IV size*/;


  • 从加密策略上获取blocksize和headroom


ixs->blocksize &#61; ixs->ixt_e->xxx;

ixs->headroom &#43;&#61; ESP_HEADER_LEN &#43; ixs->ixt_e->ooo;

          2.2&#xff09; 认证算法

  • OCF加速框架?

    &#xff08;i&#xff09;MD5/SHA1

ixs->authlen &#61; AHHMAC_HASHLEN;


  • 从加密策略上获取authlen:MD5/SHA1


ixs->authlen &#61; AHHMAC_HASHLEN;

          2.3&#xff09; tailroom

                      &#xff08;i&#xff09;填充到四字节整数倍

                      &#xff08;ii&#xff09;加上认证长度(12字节)

       3&#xff09;IPIP协议

  • 外层为IPv6头部

ixs->headroom &#43;&#61; sizeof(struct ipv6hdr);

ixs->iphlen &#61; sizeof(struct ipv6hdr);

new_version &#61; 6;


  • 外层为IPv4头部

ixs->headroom &#43;&#61; sizeof(struct iphdr);

ixs->iphlen &#61; sizeof(struct iphdr);

new_version &#61; 4;

       3&#xff09;IPIP协议    

目前不支持

③   根据headroomtailroom修改报文headtail

ixs->dat &#61; skb_push(ixs->skb, ixs->headroom);

            skb_put(ixs->skb, ixs->tailroom);

ixs->ilen &#61; ixs->skb->len - ixs->tailroom;

ixs->len &#61; ixs->skb->len;

④   填充新头部信息

memmove((void *)ixs->dat, (void *)(ixs->dat &#43; ixs->headroom), ixs->iphlen);

ixs->iph &#61; (void *)ixs->dat;

osw_ip4_hdr(ixs)->tot_len &#61; htons(ixs->skb->len);

这里需要注意的是&#xff1a;新头和旧IP头类型相同时&#xff0c;直接上旧IP头部移动(memmove)到新头位置&#xff0c;此时旧头中的ip->id是有效的。此外新封装的IP报文却是沿用了原先的ip->id

 

 

 

 

 





推荐阅读
  • Hybrid 应用的后台接口与管理界面优化
    本文探讨了如何通过优化 Hybrid 应用的后台接口和管理界面,提升用户体验。特别是在首次加载 H5 页面时,为了减少用户等待时间和流量消耗,介绍了离线资源包的管理和分发机制。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • CMake跨平台开发实践
    本文介绍如何使用CMake支持不同平台的代码编译。通过一个简单的示例,我们将展示如何编写CMakeLists.txt以适应Linux和Windows平台,并实现跨平台的函数调用。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • PostgreSQL 10 离线安装指南
    本文详细介绍了如何在无法联网的服务器上进行 PostgreSQL 10 的离线安装,并涵盖了从下载安装包到配置远程访问的完整步骤。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • 本文详细解释了华为ENSP模拟器中常用的命令,涵盖用户模式、系统模式、接口模式和地址池视图模式下的操作。这些命令对于进行计算机网络实验至关重要,帮助用户更好地理解和配置路由器及PC机的通信。 ... [详细]
  • 深入解析TCP/IP五层协议
    本文详细介绍了TCP/IP五层协议模型,包括物理层、数据链路层、网络层、传输层和应用层。每层的功能及其相互关系将被逐一解释,帮助读者理解互联网通信的原理。此外,还特别讨论了UDP和TCP协议的特点以及三次握手、四次挥手的过程。 ... [详细]
  • 本文详细介绍如何使用 Python 集成微信支付的三种主要方式:Native 支付、APP 支付和 JSAPI 支付。每种方式适用于不同的应用场景,如 PC 网站、移动端应用和公众号内支付等。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本文介绍如何在Linux Mint系统上搭建Rust开发环境,包括安装IntelliJ IDEA、Rust工具链及必要的插件。通过详细步骤,帮助开发者快速上手。 ... [详细]
  • 本文探讨了如何使用自增和自减运算符遍历二维数组中的元素。通过实例详细解释了指针与二维数组结合使用的正确方法,并解答了常见的错误用法。 ... [详细]
  • 本文介绍如何利用栈数据结构在C++中判断字符串中的括号是否匹配。通过顺序栈和链栈两种方式实现,并详细解释了算法的核心思想和具体实现步骤。 ... [详细]
author-avatar
邓世璇_664_425
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有