《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链的头部:
②统计IPsec封装需要的headroom和tailroom
ixs->headroom += sizeof(struct ahhdr); |
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; |
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 |
略。(openswan此版本未实现...)
将headroom, tailroom更新到ixs上,遍历此链上后续的IPSec SA协议(重新从②开始); | ixs->ipsp = ixs->ipsp->ips_next; |
③IPSec封装链上的多个IPSec SA协议的headroom、tailroom统计完毕后,将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*/; |
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; |
ixs->authlen &#61; AHHMAC_HASHLEN; |
2.3&#xff09; tailroom
&#xff08;i&#xff09;填充到四字节整数倍
&#xff08;ii&#xff09;加上认证长度(12字节)
3&#xff09;IPIP协议
ixs->headroom &#43;&#61; sizeof(struct ipv6hdr); |
ixs->iphlen &#61; sizeof(struct ipv6hdr); |
new_version &#61; 6; |
ixs->headroom &#43;&#61; sizeof(struct iphdr); |
ixs->iphlen &#61; sizeof(struct iphdr); |
new_version &#61; 4; |
3&#xff09;IPIP协议
目前不支持
③ 根据headroom和tailroom修改报文head、tail
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。