quhr 发表于 2011-12-21 08:43

Linux内核中的IPSEC实现(6)

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。<br>msn: <a href="mailto:yfydz_no1@hotmail.com" target="_blank">yfydz_no1@hotmail.com</a><br>来源:<a href="http://yfydz.cublog.cn/" target="_blank">http://yfydz.cublog.cn</a>
<div><br>8. 安全协议</div>
<div><br>与IPSEC相关的安全协议是AH(51)和ESP(50), IPSEC使用这两个协议对普通数据包进行封装, AH只认证不加密,
ESP既加密又认证, 当ESP和AH同时使用时, 一般都是先进行ESP封装, 再进行AH封装, 因为AH是对整个IP包进行验证的,
而ESP只验证负载部分.</div>
<div><br>在IPV4下的AH和ESP的协议实现在net/ipv4/ah4.c和net/ipv4/esp4.c中,
每个协议实现实际是要完成两个结构: struct net_protocol和struct xfrm_type,
前者用于处理接收的该协议类型的IP包, 后者则是IPSEC协议处理.</div>
<div><br>8.1 AH</div>
<div><br>8.1.1 初始化</div>
<div><br>/* net/ipv4/ah4.c */<br>static int __init ah4_init(void)<br>{<br>// 登记AH协议的xfrm协议处理结构<br>&nbsp;if (xfrm_register_type(&amp;ah_type, AF_INET) &lt; 0) {<br>&nbsp;&nbsp;printk(KERN_INFO "ip ah init: can't add xfrm type\n");<br>&nbsp;&nbsp;return -EAGAIN;<br>&nbsp;}<br>// 登记AH协议到IP协议<br>&nbsp;if (inet_add_protocol(&amp;ah4_protocol, IPPROTO_AH) &lt; 0) {<br>&nbsp;&nbsp;printk(KERN_INFO "ip ah init: can't add protocol\n");<br>&nbsp;&nbsp;xfrm_unregister_type(&amp;ah_type, AF_INET);<br>&nbsp;&nbsp;return -EAGAIN;<br>&nbsp;}<br>&nbsp;return 0;<br>}</div>
<div><br>8.1.2 IPV4下的AH协议处理结构</div>
<div><br>// AH协议处理结构, 接收到IPV4包后, 系统根据IP头中的protocol字段选择相应的上层协议处理<br>// 函数, 当IP协议号是51时, 数据包将调用该结构的handler处理函数:<br>static struct net_protocol ah4_protocol = {<br>&nbsp;.handler&nbsp;=&nbsp;xfrm4_rcv,<br>&nbsp;.err_handler&nbsp;=&nbsp;ah4_err,<br>&nbsp;.no_policy&nbsp;=&nbsp;1,<br>};</div>
<div>AH协议结构的handler函数为xfrm4_rcv, 在net/ipv4/xfrm4_input.c 中定义, 在上一篇中进行了介绍.</div>
<div><br>// 错误处理, 收到ICMP错误包时的处理情况, 此时的skb包是ICMP包<br>static void ah4_err(struct sk_buff *skb, u32 info)<br>{<br>// 应用层, data指向ICMP错误包里的内部IP头<br>&nbsp;struct iphdr *iph = (struct iphdr*)skb-&gt;data;<br>// AH头<br>&nbsp;struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb-&gt;data+(iph-&gt;ihl&lt;&lt;2));<br>&nbsp;struct xfrm_state *x;<br>// ICMP错误类型检查, 本处理函数只处理"目的不可达"和"需要分片"两种错误<br>&nbsp;if (skb-&gt;h.icmph-&gt;type != ICMP_DEST_UNREACH ||<br>&nbsp;&nbsp;&nbsp;&nbsp; skb-&gt;h.icmph-&gt;code != ICMP_FRAG_NEEDED)<br>&nbsp;&nbsp;return;<br>// 重新查找SA<br>&nbsp;x = xfrm_state_lookup((xfrm_address_t *)&amp;iph-&gt;daddr, ah-&gt;spi, IPPROTO_AH, AF_INET);<br>&nbsp;if (!x)<br>&nbsp;&nbsp;return;<br>&nbsp;printk(KERN_DEBUG "pmtu discovery on SA AH/%08x/%08x\n",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ntohl(ah-&gt;spi), ntohl(iph-&gt;daddr));<br>&nbsp;xfrm_state_put(x);<br>}</div>
<div><br>8.1.3 AH4协议的IPSEC处理结构</div>
<div><br>// AH4的xfrm协议处理结构<br>static struct xfrm_type ah_type =<br>{<br>&nbsp;.description&nbsp;= "AH4",<br>&nbsp;.owner&nbsp;&nbsp;= THIS_MODULE,<br>&nbsp;.proto&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;= IPPROTO_AH,<br>// 状态初始化<br>&nbsp;.init_state&nbsp;= ah_init_state,<br>// 协议释放<br>&nbsp;.destructor&nbsp;= ah_destroy,<br>// 协议输入<br>&nbsp;.input&nbsp;&nbsp;= ah_input,<br>// 协议输出<br>&nbsp;.output&nbsp;&nbsp;= ah_output<br>};<br>结构的重点是input和ouput函数</div>
<div><br>8.1.3.1 状态初始化</div>
<div>ah_data数据结构:</div>
<div>/* include/net/ah.h */</div>
<div>struct ah_data<br>{<br>// 密钥指针<br>&nbsp;u8&nbsp;&nbsp;&nbsp;*key;<br>// 密钥长度<br>&nbsp;int&nbsp;&nbsp;&nbsp;key_len;<br>// 工作初始化向量<br>&nbsp;u8&nbsp;&nbsp;&nbsp;*work_icv;<br>// 初始化向量完整长度<br>&nbsp;int&nbsp;&nbsp;&nbsp;icv_full_len;<br>// 初始化向量截断长度<br>&nbsp;int&nbsp;&nbsp;&nbsp;icv_trunc_len;<br>// HASH算法<br>&nbsp;struct crypto_hash&nbsp;*tfm;<br>};</div>
<div><br>// 该函数被xfrm状态(SA)初始化函数xfrm_init_state调用<br>// 用来生成SA中所用的AH数据处理结构相关信息<br>static int ah_init_state(struct xfrm_state *x)<br>{<br>&nbsp;struct ah_data *ahp = NULL;<br>&nbsp;struct xfrm_algo_desc *aalg_desc;<br>&nbsp;struct crypto_hash *tfm;</div>
<div>// 对AH协议的SA, 认证算法是必须的, 否则就没法进行AH认证了<br>&nbsp;if (!x-&gt;aalg)<br>&nbsp;&nbsp;goto error;</div>
<div>&nbsp;/* null auth can use a zero length key */<br>// 认证算法密钥长度要大于512<br>&nbsp;if (x-&gt;aalg-&gt;alg_key_len &gt; 512)<br>&nbsp;&nbsp;goto error;</div>
<div>// 如果要进行UDP封装(进行NAT穿越), 错误, 因为AH是不支持NAT的<br>&nbsp;if (x-&gt;encap)<br>&nbsp;&nbsp;goto error;</div>
<div>// 分配ah_data数据结构空间<br>&nbsp;ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);<br>&nbsp;if (ahp == NULL)<br>&nbsp;&nbsp;return -ENOMEM;</div>
<div>// 设置AH数据结构的密钥和长度<br>&nbsp;ahp-&gt;key = x-&gt;aalg-&gt;alg_key;<br>&nbsp;ahp-&gt;key_len = (x-&gt;aalg-&gt;alg_key_len+7)/8;<br>// 分配认证算法HASH结构指针并赋值给AH数据结构<br>// 算法是固定相同的, 但在每个应用使用算法时的上下文是不同的, 该结构就是描述具体应用<br>// 时的相关处理的上下文数据的<br>&nbsp;tfm = crypto_alloc_hash(x-&gt;aalg-&gt;alg_name, 0, CRYPTO_ALG_ASYNC);<br>&nbsp;if (IS_ERR(tfm))<br>&nbsp;&nbsp;goto error;</div>
<div>&nbsp;ahp-&gt;tfm = tfm;<br>// 设置认证算法密钥<br>&nbsp;if (crypto_hash_setkey(tfm, ahp-&gt;key, ahp-&gt;key_len))<br>&nbsp;&nbsp;goto error;<br>&nbsp;<br>&nbsp;/*<br>&nbsp; * Lookup the algorithm description maintained by xfrm_algo,<br>&nbsp; * verify crypto transform properties, and store information<br>&nbsp; * we need for AH processing.&nbsp; This lookup cannot fail here<br>&nbsp; * after a successful crypto_alloc_hash().<br>&nbsp; */<br>// 分配算法描述结构<br>&nbsp;aalg_desc = xfrm_aalg_get_byname(x-&gt;aalg-&gt;alg_name, 0);<br>&nbsp;BUG_ON(!aalg_desc);</div>
<div>&nbsp;if (aalg_desc-&gt;uinfo.auth.icv_fullbits/8 !=<br>&nbsp;&nbsp;&nbsp;&nbsp; crypto_hash_digestsize(tfm)) {<br>&nbsp;&nbsp;printk(KERN_INFO "AH: %s digestsize %u != %hu\n",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x-&gt;aalg-&gt;alg_name, crypto_hash_digestsize(tfm),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; aalg_desc-&gt;uinfo.auth.icv_fullbits/8);<br>&nbsp;&nbsp;goto error;<br>&nbsp;}<br>// AH数据结构的初始化向量的总长和截断长度的赋值&nbsp;<br>&nbsp;ahp-&gt;icv_full_len = aalg_desc-&gt;uinfo.auth.icv_fullbits/8;<br>&nbsp;ahp-&gt;icv_trunc_len = aalg_desc-&gt;uinfo.auth.icv_truncbits/8;<br>&nbsp;<br>&nbsp;BUG_ON(ahp-&gt;icv_trunc_len &gt; MAX_AH_AUTH_LEN);</div>
<div>// 分配初始化向量空间, 没对其赋值, 其初始值就是随机值, 这也是初始化向量所需要的<br>&nbsp;ahp-&gt;work_icv = kmalloc(ahp-&gt;icv_full_len, GFP_KERNEL);<br>&nbsp;if (!ahp-&gt;work_icv)<br>&nbsp;&nbsp;goto error;<br>// AH类型SA中AH头长度: ip_auth_hdr结构和初始化向量长度, 按8字节对齐&nbsp;<br>// 反映在AH封装操作时要将数据包增加的长度<br>&nbsp;x-&gt;props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp-&gt;icv_trunc_len);<br>// 如果是通道模式, 增加IP头长度<br>&nbsp;if (x-&gt;props.mode == XFRM_MODE_TUNNEL)<br>&nbsp;&nbsp;x-&gt;props.header_len += sizeof(struct iphdr);<br>// SA数据指向AH数据结构<br>&nbsp;x-&gt;data = ahp;</div>
<div>&nbsp;return 0;</div>
<div>error:<br>&nbsp;if (ahp) {<br>&nbsp;&nbsp;kfree(ahp-&gt;work_icv);<br>&nbsp;&nbsp;crypto_free_hash(ahp-&gt;tfm);<br>&nbsp;&nbsp;kfree(ahp);<br>&nbsp;}<br>&nbsp;return -EINVAL;<br>}</div>
<div><br>8.1.3.2 协议释放</div>
<div>// 该函数被xfrm状态(SA)释放函数xfrm_state_gc_destroy()调用<br>static void ah_destroy(struct xfrm_state *x)<br>{<br>&nbsp;struct ah_data *ahp = x-&gt;data;</div>
<div>&nbsp;if (!ahp)<br>&nbsp;&nbsp;return;<br>// 释放初始化向量空间<br>&nbsp;kfree(ahp-&gt;work_icv);<br>&nbsp;ahp-&gt;work_icv = NULL;<br>// 算法描述释放<br>&nbsp;crypto_free_hash(ahp-&gt;tfm);<br>&nbsp;ahp-&gt;tfm = NULL;<br>// AH数据结构释放<br>&nbsp;kfree(ahp);<br>}</div>
<div><br>8.1.3.3 协议输入</div>
<div><br>// 接收数据处理, 在xfrm4_rcv_encap()函数中调用<br>// 进行AH认证, 剥离AH头<br>static int ah_input(struct xfrm_state *x, struct sk_buff *skb)<br>{<br>&nbsp;int ah_hlen;<br>&nbsp;int ihl;<br>&nbsp;int err = -EINVAL;<br>&nbsp;struct iphdr *iph;<br>&nbsp;struct ip_auth_hdr *ah;<br>&nbsp;struct ah_data *ahp;<br>// IP头备份空间<br>&nbsp;char work_buf;</div>
<div>// skb数据包要准备留出AH头空间<br>&nbsp;if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))<br>&nbsp;&nbsp;goto out;<br>// IP上层数据为AH数据<br>&nbsp;ah = (struct ip_auth_hdr*)skb-&gt;data;<br>// SA相关的AH处理数据<br>&nbsp;ahp = x-&gt;data;<br>&nbsp;ah_hlen = (ah-&gt;hdrlen + 2) &lt;&lt; 2;<br>// AH头部长度合法性检查<br>&nbsp;if (ah_hlen != XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp-&gt;icv_full_len) &amp;&amp;<br>&nbsp;&nbsp;&nbsp;&nbsp; ah_hlen != XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp-&gt;icv_trunc_len)) <br>&nbsp;&nbsp;goto out;</div>
<div>// skb数据包要准备留出实际AH头空间<br>&nbsp;if (!pskb_may_pull(skb, ah_hlen))<br>&nbsp;&nbsp;goto out;</div>
<div>&nbsp;/* We are going to _remove_ AH header to keep sockets happy,<br>&nbsp; * so... Later this can change. */<br>// 对于clone的包要复制成独立包<br>&nbsp;if (skb_cloned(skb) &amp;&amp;<br>&nbsp;&nbsp;&nbsp;&nbsp; pskb_expand_head(skb, 0, 0, GFP_ATOMIC))<br>&nbsp;&nbsp;goto out;</div>
<div>&nbsp;skb-&gt;ip_summed = CHECKSUM_NONE;<br>// 可能包已经进行了复制, 所以对ah重新赋值<br>&nbsp;ah = (struct ip_auth_hdr*)skb-&gt;data;<br>&nbsp;iph = skb-&gt;nh.iph;<br>// IP头长度<br>&nbsp;ihl = skb-&gt;data - skb-&gt;nh.raw;<br>// 备份外部IP头数据<br>&nbsp;memcpy(work_buf, iph, ihl);</div>
<div>// 将IP头中的一些参数清零, 这些参数不进行认证<br>&nbsp;iph-&gt;ttl = 0;<br>&nbsp;iph-&gt;tos = 0;<br>&nbsp;iph-&gt;frag_off = 0;<br>&nbsp;iph-&gt;check = 0;<br>// IP头长度超过20字节时,处理IP选项参数<br>&nbsp;if (ihl &gt; sizeof(*iph)) {<br>&nbsp;&nbsp;u32 dummy;<br>&nbsp;&nbsp;if (ip_clear_mutable_options(iph, &amp;dummy))<br>&nbsp;&nbsp;&nbsp;goto out;<br>&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>// 认证数据缓冲区<br>&nbsp;&nbsp;u8 auth_data;<br>// 拷贝数据包中的认证数据到缓冲区<br>&nbsp;&nbsp;memcpy(auth_data, ah-&gt;auth_data, ahp-&gt;icv_trunc_len);<br>// 包括IP头部分数据<br>&nbsp;&nbsp;skb_push(skb, ihl);<br>// 计算认证值是否匹配, 非0表示出错<br>&nbsp;&nbsp;err = ah_mac_digest(ahp, skb, ah-&gt;auth_data);<br>// 认证失败返回错误<br>&nbsp;&nbsp;if (err)<br>&nbsp;&nbsp;&nbsp;goto out;<br>&nbsp;&nbsp;err = -EINVAL;<br>// 复制一定长度的认证数据作为初始化向量<br>&nbsp;&nbsp;if (memcmp(ahp-&gt;work_icv, auth_data, ahp-&gt;icv_trunc_len)) {<br>&nbsp;&nbsp;&nbsp;x-&gt;stats.integrity_failed++;<br>&nbsp;&nbsp;&nbsp;goto out;<br>&nbsp;&nbsp;}<br>&nbsp;}<br>// 将备份的IP头缓冲区中的协议改为AH内部包裹的协议<br>&nbsp;((struct iphdr*)work_buf)-&gt;protocol = ah-&gt;nexthdr;<br>// 将原来IP头数据拷贝到原来AH头后面作为新IP头<br>&nbsp;skb-&gt;h.raw = memcpy(skb-&gt;nh.raw += ah_hlen, work_buf, ihl);<br>// skb包缩减原来的IP头和AH头, 以新IP头作为数据开始<br>&nbsp;__skb_pull(skb, ah_hlen + ihl);</div>
<div>&nbsp;return 0;</div>
<div>out:<br>&nbsp;return err;<br>}</div>
<div><br>8.1.3.4 协议输出</div>
<div><br>// 发送数据处理, 在xfrm4_output_one()中调用<br>// 计算AH认证值, 添加AH头<br>static int ah_output(struct xfrm_state *x, struct sk_buff *skb)<br>{<br>&nbsp;int err;<br>&nbsp;struct iphdr *iph, *top_iph;<br>&nbsp;struct ip_auth_hdr *ah;<br>&nbsp;struct ah_data *ahp;<br>// 临时IP头缓冲区, 最大IP头60字节<br>&nbsp;union {<br>&nbsp;&nbsp;struct iphdr&nbsp;iph;<br>&nbsp;&nbsp;char &nbsp;&nbsp;buf;<br>&nbsp;} tmp_iph;</div>
<div>// 当前的IP头将作为最外部IP头<br>&nbsp;top_iph = skb-&gt;nh.iph;<br>// 临时IP头,用于临时保存IP头内部分字段数据<br>&nbsp;iph = &amp;tmp_iph.iph;</div>
<div>// 将当前IP头中不进行认证的字段数据复制到临时IP头<br>&nbsp;iph-&gt;tos = top_iph-&gt;tos;<br>&nbsp;iph-&gt;ttl = top_iph-&gt;ttl;<br>&nbsp;iph-&gt;frag_off = top_iph-&gt;frag_off;<br>// 如果有IP选项, 处理IP选项<br>&nbsp;if (top_iph-&gt;ihl != 5) {<br>&nbsp;&nbsp;iph-&gt;daddr = top_iph-&gt;daddr;<br>&nbsp;&nbsp;memcpy(iph+1, top_iph+1, top_iph-&gt;ihl*4 - sizeof(struct iphdr));<br>&nbsp;&nbsp;err = ip_clear_mutable_options(top_iph, &amp;top_iph-&gt;daddr);<br>&nbsp;&nbsp;if (err)<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;}<br>// AH头定位在外部IP头后面, skb缓冲中已经预留出AH头的数据部分了, <br>// 这是通过mode-&gt;output函数预留的, 通常调用type-&gt;output前要调用mode-&gt;oputput<br>&nbsp;ah = (struct ip_auth_hdr *)((char *)top_iph+top_iph-&gt;ihl*4);<br>// AH中的下一个头用原来的外部IP头中的协议<br>&nbsp;ah-&gt;nexthdr = top_iph-&gt;protocol;<br>// 将外部IP头的不进行认证计算的部分字段清零<br>&nbsp;top_iph-&gt;tos = 0;<br>&nbsp;top_iph-&gt;tot_len = htons(skb-&gt;len);<br>&nbsp;top_iph-&gt;frag_off = 0;<br>&nbsp;top_iph-&gt;ttl = 0;<br>// IP协议改为AH<br>&nbsp;top_iph-&gt;protocol = IPPROTO_AH;<br>&nbsp;top_iph-&gt;check = 0;</div>
<div>// AH数据处理结构<br>&nbsp;ahp = x-&gt;data;<br>// AH头长度对齐<br>&nbsp;ah-&gt;hdrlen&nbsp; = (XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ahp-&gt;icv_trunc_len) &gt;&gt; 2) - 2;<br>// AH头参数赋值<br>&nbsp;ah-&gt;reserved = 0;<br>// SPI值<br>&nbsp;ah-&gt;spi = x-&gt;id.spi;<br>// 序列号<br>&nbsp;ah-&gt;seq_no = htonl(++x-&gt;replay.oseq);<br>// 通知防止重放攻击处理, 更新序列号<br>&nbsp;xfrm_aevent_doreplay(x);<br>// 对skb进行AH认证值的计算<br>&nbsp;err = ah_mac_digest(ahp, skb, ah-&gt;auth_data);<br>&nbsp;if (err)<br>&nbsp;&nbsp;goto error;<br>// 赋值初始化向量值到认证数据部分<br>&nbsp;memcpy(ah-&gt;auth_data, ahp-&gt;work_icv, ahp-&gt;icv_trunc_len);</div>
<div>// 恢复原来IP头的的不认证部分的值<br>&nbsp;top_iph-&gt;tos = iph-&gt;tos;<br>&nbsp;top_iph-&gt;ttl = iph-&gt;ttl;<br>&nbsp;top_iph-&gt;frag_off = iph-&gt;frag_off;<br>&nbsp;if (top_iph-&gt;ihl != 5) {<br>&nbsp;&nbsp;top_iph-&gt;daddr = iph-&gt;daddr;<br>&nbsp;&nbsp;memcpy(top_iph+1, iph+1, top_iph-&gt;ihl*4 - sizeof(struct iphdr));<br>&nbsp;}<br>// 重新计算IP头的认证值<br>&nbsp;ip_send_check(top_iph);</div>
<div>&nbsp;err = 0;</div>
<div>error:<br>&nbsp;return err;<br>}</div>
<div>&nbsp;</div>
<div>8.2 ESP</div>
<div><br>8.2.1 初始化</div>
<div><br>/* net/ipv4/esp4.c */</div>
<div>static int __init esp4_init(void)<br>{<br>// 登记ESP协议的xfrm协议处理结构<br>&nbsp;if (xfrm_register_type(&amp;esp_type, AF_INET) &lt; 0) {<br>&nbsp;&nbsp;printk(KERN_INFO "ip esp init: can't add xfrm type\n");<br>&nbsp;&nbsp;return -EAGAIN;<br>&nbsp;}<br>// 登记ESP协议到IP协议<br>&nbsp;if (inet_add_protocol(&amp;esp4_protocol, IPPROTO_ESP) &lt; 0) {<br>&nbsp;&nbsp;printk(KERN_INFO "ip esp init: can't add protocol\n");<br>&nbsp;&nbsp;xfrm_unregister_type(&amp;esp_type, AF_INET);<br>&nbsp;&nbsp;return -EAGAIN;<br>&nbsp;}<br>&nbsp;return 0;<br>}</div>
<div><br>8.2.2 IPV4下的ESP协议处理结构</div>
<div><br>// ESP协议处理结构, 接收到IPV4包后, 系统根据IP头中的protocol<br>// 字段选择相应的上层协议处理函数, 当IP协议号是50时, 数据包将<br>// 调用该结构的handler处理函数:<br>static struct net_protocol esp4_protocol = {<br>&nbsp;.handler&nbsp;=&nbsp;xfrm4_rcv,<br>&nbsp;.err_handler&nbsp;=&nbsp;esp4_err,<br>&nbsp;.no_policy&nbsp;=&nbsp;1,<br>};</div>
<div><br>ESP协议结构的handler函数也是xfrm4_rcv, 在net/ipv4/xfrm4_input.c 中定义, <br>在上一篇中进行了介绍.</div>
<div><br>// 错误处理, 收到ICMP错误包时的处理情况, 此时的skb包是ICMP包<br>static void esp4_err(struct sk_buff *skb, u32 info)<br>{<br>// 应用层, data指向ICMP错误包里的内部IP头<br>&nbsp;struct iphdr *iph = (struct iphdr*)skb-&gt;data;<br>// ESP头<br>&nbsp;struct ip_esp_hdr *esph = (struct ip_esp_hdr*)(skb-&gt;data+(iph-&gt;ihl&lt;&lt;2));<br>&nbsp;struct xfrm_state *x;</div>
<div>// ICMP错误类型检查, 本处理函数只处理"目的不可达"和"需要分片"两种错误<br>&nbsp;if (skb-&gt;h.icmph-&gt;type != ICMP_DEST_UNREACH ||<br>&nbsp;&nbsp;&nbsp;&nbsp; skb-&gt;h.icmph-&gt;code != ICMP_FRAG_NEEDED)<br>&nbsp;&nbsp;return;</div>
<div>// 重新查找SA<br>&nbsp;x = xfrm_state_lookup((xfrm_address_t *)&amp;iph-&gt;daddr, esph-&gt;spi, IPPROTO_ESP, AF_INET);<br>&nbsp;if (!x)<br>&nbsp;&nbsp;return;<br>&nbsp;NETDEBUG(KERN_DEBUG "pmtu discovery on SA ESP/%08x/%08x\n",<br>&nbsp;&nbsp; ntohl(esph-&gt;spi), ntohl(iph-&gt;daddr));<br>&nbsp;xfrm_state_put(x);<br>}</div>
<div><br>8.2.3 ESP4协议的IPSEC处理结构</div>
<div><br>static struct xfrm_type esp_type =<br>{<br>&nbsp;.description&nbsp;= "ESP4",<br>&nbsp;.owner&nbsp;&nbsp;= THIS_MODULE,<br>&nbsp;.proto&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;= IPPROTO_ESP,<br>// 状态初始化<br>&nbsp;.init_state&nbsp;= esp_init_state,<br>// 协议释放<br>&nbsp;.destructor&nbsp;= esp_destroy,<br>// 计算最大长度<br>&nbsp;.get_max_size&nbsp;= esp4_get_max_size,<br>// 协议输入<br>&nbsp;.input&nbsp;&nbsp;= esp_input,<br>// 协议输出<br>&nbsp;.output&nbsp;&nbsp;= esp_output<br>};</div>
<div><br>8.2.3.1 状态初始化</div>
<div>esp_data数据结构:</div>
<div>/* include/net/esp.h */</div>
<div>struct esp_data<br>{<br>&nbsp;struct scatterlist&nbsp;&nbsp;sgbuf;</div>
<div>&nbsp;/* Confidentiality */<br>// 加密使用的相关数据<br>&nbsp;struct {<br>// 密钥<br>&nbsp;&nbsp;u8&nbsp;&nbsp;&nbsp;*key;&nbsp;&nbsp;/* Key */<br>// 密钥长度<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;key_len;&nbsp;/* Key length */<br>// 填充长度<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;padlen;&nbsp;&nbsp;/* 0..255 */<br>&nbsp;&nbsp;/* ivlen is offset from enc_data, where encrypted data start.<br>&nbsp;&nbsp; * It is logically different of crypto_tfm_alg_ivsize(tfm).<br>&nbsp;&nbsp; * We assume that it is either zero (no ivec), or<br>&nbsp;&nbsp; * &gt;= crypto_tfm_alg_ivsize(tfm). */<br>// 初始化向量长度<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;ivlen;<br>// 初始化向量是否初始化标志<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;ivinitted;<br>// 初始化向量<br>&nbsp;&nbsp;u8&nbsp;&nbsp;&nbsp;*ivec;&nbsp;&nbsp;/* ivec buffer */<br>// 加密算法<br>&nbsp;&nbsp;struct crypto_blkcipher&nbsp;*tfm;&nbsp;&nbsp;/* crypto handle */<br>&nbsp;} conf;</div>
<div>&nbsp;/* Integrity. It is active when icv_full_len != 0 */<br>// 认证使用的相关数据<br>&nbsp;struct {<br>// 密钥<br>&nbsp;&nbsp;u8&nbsp;&nbsp;&nbsp;*key;&nbsp;&nbsp;/* Key */<br>// 密钥长度<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;key_len;&nbsp;/* Length of the key */<br>// 初始化向量<br>&nbsp;&nbsp;u8&nbsp;&nbsp;&nbsp;*work_icv;<br>// 初始化向量全长<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;icv_full_len;<br>// 初始化向量截断长度<br>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;icv_trunc_len;<br>// 初始化向量更新函数, 好象没用<br>&nbsp;&nbsp;void&nbsp;&nbsp;&nbsp;(*icv)(struct esp_data*,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct sk_buff *skb,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int offset, int len, u8 *icv);<br>// HASH算法<br>&nbsp;&nbsp;struct crypto_hash&nbsp;*tfm;<br>&nbsp;} auth;<br>};</div>
<div>// ESP的esp_data数据结构初始化<br>static int esp_init_state(struct xfrm_state *x)<br>{<br>&nbsp;struct esp_data *esp = NULL;<br>&nbsp;struct crypto_blkcipher *tfm;</div>
<div>&nbsp;/* null auth and encryption can have zero length keys */<br>// 如果有认证算法, 密钥至少512, ESP的认证处理是可选的, 但在实际中都会使用认证<br>&nbsp;if (x-&gt;aalg) {<br>&nbsp;&nbsp;if (x-&gt;aalg-&gt;alg_key_len &gt; 512)<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;}<br>// ESP加密算法是必须的<br>&nbsp;if (x-&gt;ealg == NULL)<br>&nbsp;&nbsp;goto error;</div>
<div>// 分配esp_data数据结构空间<br>&nbsp;esp = kzalloc(sizeof(*esp), GFP_KERNEL);<br>&nbsp;if (esp == NULL)<br>&nbsp;&nbsp;return -ENOMEM;<br>// 如果定义了认证算法, 初始化认证算法参数, 和AH类似<br>&nbsp;if (x-&gt;aalg) {<br>&nbsp;&nbsp;struct xfrm_algo_desc *aalg_desc;<br>&nbsp;&nbsp;struct crypto_hash *hash;<br>// 认证密钥和长度设置<br>&nbsp;&nbsp;esp-&gt;auth.key = x-&gt;aalg-&gt;alg_key;<br>&nbsp;&nbsp;esp-&gt;auth.key_len = (x-&gt;aalg-&gt;alg_key_len+7)/8;<br>// 分配HASH算法的实现<br>&nbsp;&nbsp;hash = crypto_alloc_hash(x-&gt;aalg-&gt;alg_name, 0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CRYPTO_ALG_ASYNC);<br>&nbsp;&nbsp;if (IS_ERR(hash))<br>&nbsp;&nbsp;&nbsp;goto error;</div>
<div>&nbsp;&nbsp;esp-&gt;auth.tfm = hash;<br>// 设置HASH算法密钥<br>&nbsp;&nbsp;if (crypto_hash_setkey(hash, esp-&gt;auth.key, esp-&gt;auth.key_len))<br>&nbsp;&nbsp;&nbsp;goto error;<br>// 找到算法描述<br>&nbsp;&nbsp;aalg_desc = xfrm_aalg_get_byname(x-&gt;aalg-&gt;alg_name, 0);<br>&nbsp;&nbsp;BUG_ON(!aalg_desc);<br>// 检查算法初始化向量长度合法性<br>&nbsp;&nbsp;if (aalg_desc-&gt;uinfo.auth.icv_fullbits/8 !=<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; crypto_hash_digestsize(hash)) {<br>&nbsp;&nbsp;&nbsp;NETDEBUG(KERN_INFO "ESP: %s digestsize %u != %hu\n",<br>&nbsp;&nbsp;&nbsp;&nbsp; x-&gt;aalg-&gt;alg_name,<br>&nbsp;&nbsp;&nbsp;&nbsp; crypto_hash_digestsize(hash),<br>&nbsp;&nbsp;&nbsp;&nbsp; aalg_desc-&gt;uinfo.auth.icv_fullbits/8);<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;&nbsp;}<br>// 初始化向量的全长和截断长度<br>&nbsp;&nbsp;esp-&gt;auth.icv_full_len = aalg_desc-&gt;uinfo.auth.icv_fullbits/8;<br>&nbsp;&nbsp;esp-&gt;auth.icv_trunc_len = aalg_desc-&gt;uinfo.auth.icv_truncbits/8;<br>// 分配全长度的初始化向量空间<br>&nbsp;&nbsp;esp-&gt;auth.work_icv = kmalloc(esp-&gt;auth.icv_full_len, GFP_KERNEL);<br>&nbsp;&nbsp;if (!esp-&gt;auth.work_icv)<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;}</div>
<div>// 初始化加密算法相关参数, ESP使用的加密算法都是对称块加密算法, 不可能用非对称算法的<br>// 加密密钥<br>&nbsp;esp-&gt;conf.key = x-&gt;ealg-&gt;alg_key;<br>// 加密密钥长度<br>&nbsp;esp-&gt;conf.key_len = (x-&gt;ealg-&gt;alg_key_len+7)/8;<br>// 分配加密算法的具体实现结构<br>&nbsp;tfm = crypto_alloc_blkcipher(x-&gt;ealg-&gt;alg_name, 0, CRYPTO_ALG_ASYNC);<br>&nbsp;if (IS_ERR(tfm))<br>&nbsp;&nbsp;goto error;<br>&nbsp;esp-&gt;conf.tfm = tfm;<br>// 初始化向量大小<br>&nbsp;esp-&gt;conf.ivlen = crypto_blkcipher_ivsize(tfm);<br>// 填充数据长度初始化为0<br>&nbsp;esp-&gt;conf.padlen = 0;<br>// 初始化向量长度非0, 分配具体的初始化向量空间<br>&nbsp;if (esp-&gt;conf.ivlen) {<br>&nbsp;&nbsp;esp-&gt;conf.ivec = kmalloc(esp-&gt;conf.ivlen, GFP_KERNEL);<br>&nbsp;&nbsp;if (unlikely(esp-&gt;conf.ivec == NULL))<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;&nbsp;esp-&gt;conf.ivinitted = 0;<br>&nbsp;}<br>// 设置加密算法密钥<br>&nbsp;if (crypto_blkcipher_setkey(tfm, esp-&gt;conf.key, esp-&gt;conf.key_len))<br>&nbsp;&nbsp;goto error;<br>// 定义SA中ESP头部长度: ESP头加初始化向量长度<br>// 反映在ESP封装操作时要将数据包增加的长度<br>&nbsp;x-&gt;props.header_len = sizeof(struct ip_esp_hdr) + esp-&gt;conf.ivlen;<br>// 如果是通道模式, 还需要增加IP头长度<br>&nbsp;if (x-&gt;props.mode == XFRM_MODE_TUNNEL)<br>&nbsp;&nbsp;x-&gt;props.header_len += sizeof(struct iphdr);<br>// 如果要进行UDP封装<br>&nbsp;if (x-&gt;encap) {<br>&nbsp;&nbsp;struct xfrm_encap_tmpl *encap = x-&gt;encap;</div>
<div>&nbsp;&nbsp;switch (encap-&gt;encap_type) {<br>&nbsp;&nbsp;default:<br>&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;&nbsp;case UDP_ENCAP_ESPINUDP:<br>// 该类型封装增加UDP头长度<br>&nbsp;&nbsp;&nbsp;x-&gt;props.header_len += sizeof(struct udphdr);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;case UDP_ENCAP_ESPINUDP_NON_IKE:<br>// 该类型封装增加UDP头长度外加加8字节<br>&nbsp;&nbsp;&nbsp;x-&gt;props.header_len += sizeof(struct udphdr) + 2 * sizeof(u32);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;}<br>&nbsp;}<br>// 将esp_data作为SA的data指针<br>&nbsp;x-&gt;data = esp;<br>// 追踪长度, 最大增加长度和当前的计算的增加长度的差值,在路由时会用到<br>// 对于AH, 由于没有定义get_max_size(), 该值位0<br>&nbsp;x-&gt;props.trailer_len = esp4_get_max_size(x, 0) - x-&gt;props.header_len;<br>&nbsp;return 0;</div>
<div>error:<br>&nbsp;x-&gt;data = esp;<br>&nbsp;esp_destroy(x);<br>&nbsp;x-&gt;data = NULL;<br>&nbsp;return -EINVAL;<br>}</div>
<div><br>8.2.3.2 协议释放</div>
<div><br>// 该函数被xfrm状态(SA)释放函数xfrm_state_gc_destroy()调用<br>static void esp_destroy(struct xfrm_state *x)<br>{<br>&nbsp;struct esp_data *esp = x-&gt;data;</div>
<div>&nbsp;if (!esp)<br>&nbsp;&nbsp;return;<br>// 释放加密算法<br>&nbsp;crypto_free_blkcipher(esp-&gt;conf.tfm);<br>&nbsp;esp-&gt;conf.tfm = NULL;<br>// 释放加密初始化向量<br>&nbsp;kfree(esp-&gt;conf.ivec);<br>&nbsp;esp-&gt;conf.ivec = NULL;<br>// 释放认证算法<br>&nbsp;crypto_free_hash(esp-&gt;auth.tfm);<br>&nbsp;esp-&gt;auth.tfm = NULL;<br>// 释放认证初始化向量<br>&nbsp;kfree(esp-&gt;auth.work_icv);<br>&nbsp;esp-&gt;auth.work_icv = NULL;<br>// 释放esp_data<br>&nbsp;kfree(esp);<br>}</div>
<div><br>8.2.3.3 计算最大长度</div>
<div><br>// 在xfrm_state_mtu()函数中调用, 计算最大增加的数据长度<br>// AH中没有该函数, 增加的长度使用x-&gt;props.header_len<br>static u32 esp4_get_max_size(struct xfrm_state *x, int mtu)<br>{<br>&nbsp;struct esp_data *esp = x-&gt;data;<br>// 加密块长度, 按4字节对齐<br>&nbsp;u32 blksize = ALIGN(crypto_blkcipher_blocksize(esp-&gt;conf.tfm), 4);<br>&nbsp;int enclen = 0;</div>
<div>&nbsp;switch (x-&gt;props.mode) {<br>&nbsp;case XFRM_MODE_TUNNEL:<br>// 通道模式下的MTU, 按加密块大小对齐, +2是要包括2字节数据长度<br>&nbsp;&nbsp;mtu = ALIGN(mtu +2, blksize);<br>&nbsp;&nbsp;break;<br>&nbsp;default:<br>&nbsp;case XFRM_MODE_TRANSPORT:<br>&nbsp;&nbsp;/* The worst case */<br>// 传输模式下, MTU先按4字节对齐, 再加块长度减4<br>&nbsp;&nbsp;mtu = ALIGN(mtu + 2, 4) + blksize - 4;<br>&nbsp;&nbsp;break;<br>&nbsp;case XFRM_MODE_BEET:<br>&nbsp;&nbsp;&nbsp;/* The worst case. */<br>&nbsp;&nbsp;enclen = IPV4_BEET_PHMAXLEN;<br>&nbsp;&nbsp;mtu = ALIGN(mtu + enclen + 2, blksize);<br>&nbsp;&nbsp;break;<br>&nbsp;}<br>// 如果加密算法中定义了填充长度, MTU也要按填充长度对齐<br>&nbsp;if (esp-&gt;conf.padlen)<br>&nbsp;&nbsp;mtu = ALIGN(mtu, esp-&gt;conf.padlen);<br>// 返回MTU加提议中需要增加的头部长度和认证初始化向量的截断长度<br>// enclen只在BEET模式下非0, 在通道和传输模式下都是0<br>&nbsp;return mtu + x-&gt;props.header_len + esp-&gt;auth.icv_trunc_len - enclen;<br>}</div>
<div>&nbsp;</div>
<div>8.2.3.4 协议输入</div>
<div><br>struct scatterlist结构说明:</div>
<div>/* include/asm-i386/scatterlist.h */</div>
<div>struct scatterlist {<br>&nbsp;&nbsp;&nbsp; struct page&nbsp;&nbsp;*page;<br>&nbsp;&nbsp;&nbsp; unsigned int&nbsp;offset;<br>&nbsp;&nbsp;&nbsp; dma_addr_t&nbsp;&nbsp;dma_address;<br>&nbsp;&nbsp;&nbsp; unsigned int&nbsp;length;<br>};</div>
<div><br>/*<br>&nbsp;* Note: detecting truncated vs. non-truncated authentication data is very<br>&nbsp;* expensive, so we only support truncated data, which is the recommended<br>&nbsp;* and common case.<br>&nbsp;*/<br>// 接收数据处理, 在xfrm4_rcv_encap()函数中调用<br>// 进行ESP认证解密, 剥离ESP头, 解密成普通数据包, 数据包长度减少<br>// 输入的数据包是ESP包<br>static int esp_input(struct xfrm_state *x, struct sk_buff *skb)<br>{<br>&nbsp;struct iphdr *iph;<br>&nbsp;struct ip_esp_hdr *esph;<br>&nbsp;struct esp_data *esp = x-&gt;data;<br>&nbsp;struct crypto_blkcipher *tfm = esp-&gt;conf.tfm;<br>&nbsp;struct blkcipher_desc desc = { .tfm = tfm };<br>&nbsp;struct sk_buff *trailer;<br>&nbsp;int blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4);<br>// 认证初始化向量截断长度<br>&nbsp;int alen = esp-&gt;auth.icv_trunc_len;<br>// 需要加密的数据长度: 总长减ESP头, 加密初始化向量长度, 认证初始化向量长度<br>&nbsp;int elen = skb-&gt;len - sizeof(struct ip_esp_hdr) - esp-&gt;conf.ivlen - alen;<br>&nbsp;int nfrags;<br>&nbsp;int ihl;<br>&nbsp;u8 nexthdr;<br>&nbsp;struct scatterlist *sg;<br>&nbsp;int padlen;<br>&nbsp;int err;</div>
<div>// 在skb头留出ESP头的空间<br>&nbsp;if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr)))<br>&nbsp;&nbsp;goto out;</div>
<div>// 检查需要加密的数据长度, 必须大于0而且按块大小对齐的<br>&nbsp;if (elen &lt;= 0 || (elen &amp; (blksize-1)))<br>&nbsp;&nbsp;goto out;</div>
<div>&nbsp;/* If integrity check is required, do this. */<br>// 认证计算处理<br>&nbsp;if (esp-&gt;auth.icv_full_len) {<br>&nbsp;&nbsp;u8 sum;<br>// 计算认证值, 认证值保存在esp_data结构中<br>&nbsp;&nbsp;err = esp_mac_digest(esp, skb, 0, skb-&gt;len - alen);<br>&nbsp;&nbsp;if (err)<br>&nbsp;&nbsp;&nbsp;goto out;</div>
<div>// 将skb中的认证初始化向量部分数据拷贝到缓冲区sum中<br>&nbsp;&nbsp;if (skb_copy_bits(skb, skb-&gt;len - alen, sum, alen))<br>&nbsp;&nbsp;&nbsp;BUG();<br>// 比较sum中的向量值和认证算法结构中的向量值是否匹配, 数据包正常情况下应该是相同的<br>&nbsp;&nbsp;if (unlikely(memcmp(esp-&gt;auth.work_icv, sum, alen))) {<br>&nbsp;&nbsp;&nbsp;x-&gt;stats.integrity_failed++;<br>&nbsp;&nbsp;&nbsp;goto out;<br>&nbsp;&nbsp;}<br>&nbsp;}<br>// 使数据包是可写的<br>&nbsp;if ((nfrags = skb_cow_data(skb, 0, &amp;trailer)) &lt; 0)<br>&nbsp;&nbsp;goto out;</div>
<div>&nbsp;skb-&gt;ip_summed = CHECKSUM_NONE;<br>// 定位在数据包中的ESP头位置, 为当前的data位置<br>&nbsp;esph = (struct ip_esp_hdr*)skb-&gt;data;</div>
<div>&nbsp;/* Get ivec. This can be wrong, check against another impls. */<br>// 设置加密算法的初始化向量<br>&nbsp;if (esp-&gt;conf.ivlen)<br>&nbsp;&nbsp;crypto_blkcipher_set_iv(tfm, esph-&gt;enc_data, esp-&gt;conf.ivlen);</div>
<div>&nbsp;sg = &amp;esp-&gt;sgbuf;</div>
<div>&nbsp;if (unlikely(nfrags &gt; ESP_NUM_FAST_SG)) {<br>&nbsp;&nbsp;sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);<br>&nbsp;&nbsp;if (!sg)<br>&nbsp;&nbsp;&nbsp;goto out;<br>&nbsp;}<br>&nbsp;skb_to_sgvec(skb, sg, sizeof(struct ip_esp_hdr) + esp-&gt;conf.ivlen, elen);<br>// 解密操作, 返回非0表示失败<br>&nbsp;err = crypto_blkcipher_decrypt(&amp;desc, sg, sg, elen);<br>&nbsp;if (unlikely(sg != &amp;esp-&gt;sgbuf))<br>&nbsp;&nbsp;kfree(sg);<br>// 解密失败返回<br>&nbsp;if (unlikely(err))<br>&nbsp;&nbsp;return err;<br>// 拷贝两字节数据<br>&nbsp;if (skb_copy_bits(skb, skb-&gt;len-alen-2, nexthdr, 2))<br>&nbsp;&nbsp;BUG();</div>
<div>&nbsp;padlen = nexthdr;<br>&nbsp;if (padlen+2 &gt;= elen)<br>&nbsp;&nbsp;goto out;</div>
<div>&nbsp;/* ... check padding bits here. Silly. :-) */ <br>// 新的IP头<br>&nbsp;iph = skb-&gt;nh.iph;<br>&nbsp;ihl = iph-&gt;ihl * 4;<br>// 如果是NAT穿越情况, 进行一些处理<br>&nbsp;if (x-&gt;encap) {<br>// xfrm封装模板<br>&nbsp;&nbsp;struct xfrm_encap_tmpl *encap = x-&gt;encap;<br>// 定位UDP数据头位置, 在IP头之后<br>&nbsp;&nbsp;struct udphdr *uh = (void *)(skb-&gt;nh.raw + ihl);</div>
<div>&nbsp;&nbsp;/*<br>&nbsp;&nbsp; * 1) if the NAT-T peer's IP or port changed then<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; advertize the change to the keying daemon.<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; This is an inbound SA, so just compare<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; SRC ports.<br>&nbsp;&nbsp; */<br>// 如果IP头源地址和SA提议中的源地址不同或源端口不同<br>&nbsp;&nbsp;if (iph-&gt;saddr != x-&gt;props.saddr.a4 ||<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uh-&gt;source != encap-&gt;encap_sport) {<br>&nbsp;&nbsp;&nbsp;xfrm_address_t ipaddr;<br>// 保存当前IP头源地址<br>&nbsp;&nbsp;&nbsp;ipaddr.a4 = iph-&gt;saddr;<br>// 进行NAT通知回调处理<br>&nbsp;&nbsp;&nbsp;km_new_mapping(x, &amp;ipaddr, uh-&gt;source);<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;/* XXX: perhaps add an extra<br>&nbsp;&nbsp;&nbsp; * policy check here, to see<br>&nbsp;&nbsp;&nbsp; * if we should allow or<br>&nbsp;&nbsp;&nbsp; * reject a packet from a<br>&nbsp;&nbsp;&nbsp; * different source<br>&nbsp;&nbsp;&nbsp; * address/port.<br>&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;/*<br>&nbsp;&nbsp; * 2) ignore UDP/TCP checksums in case<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; of NAT-T in Transport Mode, or<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; perform other post-processing fixes<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; as per draft-ietf-ipsec-udp-encaps-06,<br>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp; section 3.1.2<br>&nbsp;&nbsp; */<br>// 如果是传输模式或BEET模式, 设置不需要计算校验和<br>&nbsp;&nbsp;if (x-&gt;props.mode == XFRM_MODE_TRANSPORT ||<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x-&gt;props.mode == XFRM_MODE_BEET)<br>&nbsp;&nbsp;&nbsp;skb-&gt;ip_summed = CHECKSUM_UNNECESSARY;<br>&nbsp;}<br>// 新IP头中协议<br>&nbsp;iph-&gt;protocol = nexthdr;<br>// 缩减skb数据包长度<br>&nbsp;pskb_trim(skb, skb-&gt;len - alen - padlen - 2);<br>// 重新定位IP上层数据头位置<br>&nbsp;skb-&gt;h.raw = __skb_pull(skb, sizeof(*esph) + esp-&gt;conf.ivlen) - ihl;</div>
<div>&nbsp;return 0;</div>
<div>out:<br>&nbsp;return -EINVAL;<br>}</div>
<div><br>8.2.3.4 协议输出</div>
<div><br>// 发送数据处理, 在xfrm4_output_one()中调用<br>// 添加ESP头, 对数据包进行加密和认证处理, 数据包长度扩大<br>// 在NAT穿越情况下会封装为UDP数据<br>static int esp_output(struct xfrm_state *x, struct sk_buff *skb)<br>{<br>&nbsp;int err;<br>&nbsp;struct iphdr *top_iph;<br>&nbsp;struct ip_esp_hdr *esph;<br>&nbsp;struct crypto_blkcipher *tfm;<br>&nbsp;struct blkcipher_desc desc;<br>&nbsp;struct esp_data *esp;<br>&nbsp;struct sk_buff *trailer;<br>&nbsp;int blksize;<br>&nbsp;int clen;<br>&nbsp;int alen;<br>&nbsp;int nfrags;</div>
<div>&nbsp;/* Strip IP+ESP header. */<br>// 缩减skb数据, 减去IP头和ESP头, 剩下的数据就是要进行加密和认证的部分<br>&nbsp;__skb_pull(skb, skb-&gt;h.raw - skb-&gt;data);<br>&nbsp;/* Now skb is pure payload to encrypt */</div>
<div>&nbsp;err = -ENOMEM;</div>
<div>&nbsp;/* Round to block size */<br>// 加密块的初始值<br>&nbsp;clen = skb-&gt;len;</div>
<div>// 获取SA的esp_data数据结构<br>&nbsp;esp = x-&gt;data;<br>// 认证初始化向量截断长度<br>&nbsp;alen = esp-&gt;auth.icv_trunc_len;<br>// 加密算法<br>&nbsp;tfm = esp-&gt;conf.tfm;<br>// 给块加密算法描述结构赋值<br>&nbsp;desc.tfm = tfm;<br>&nbsp;desc.flags = 0;<br>// 每个加密块大小<br>&nbsp;blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4);<br>// 对齐要加密的数据总长<br>&nbsp;clen = ALIGN(clen + 2, blksize);<br>// 如果要考虑填充, 继续对齐<br>&nbsp;if (esp-&gt;conf.padlen)<br>&nbsp;&nbsp;clen = ALIGN(clen, esp-&gt;conf.padlen);</div>
<div>// 使数据包可写<br>&nbsp;if ((nfrags = skb_cow_data(skb, clen-skb-&gt;len+alen, &amp;trailer)) &lt; 0)<br>&nbsp;&nbsp;goto error;</div>
<div>&nbsp;/* Fill padding... */<br>// 长度对齐后填充多余长度部分内容<br>&nbsp;do {<br>&nbsp;&nbsp;int i;<br>&nbsp;&nbsp;for (i=0; i&lt;clen-skb-&gt;len - 2; i++)<br>&nbsp;&nbsp;&nbsp;*(u8*)(trailer-&gt;tail + i) = i+1;<br>&nbsp;} while (0);<br>// 最后两字节表示填充数据的长度<br>&nbsp;*(u8*)(trailer-&gt;tail + clen-skb-&gt;len - 2) = (clen - skb-&gt;len)-2;<br>&nbsp;pskb_put(skb, trailer, clen - skb-&gt;len);<br>// 在将IP头部分扩展回来<br>&nbsp;__skb_push(skb, skb-&gt;data - skb-&gt;nh.raw);<br>// 现在的IP头作为外部IP头<br>&nbsp;top_iph = skb-&gt;nh.iph;<br>// esp头跟在IP头后<br>&nbsp;esph = (struct ip_esp_hdr *)(skb-&gt;nh.raw + top_iph-&gt;ihl*4);<br>// 数据总长增加认证部分长度<br>&nbsp;top_iph-&gt;tot_len = htons(skb-&gt;len + alen);<br>&nbsp;*(u8*)(trailer-&gt;tail - 1) = top_iph-&gt;protocol;</div>
<div>&nbsp;/* this is non-NULL only with UDP Encapsulation */<br>&nbsp;if (x-&gt;encap) {<br>// NAT穿越情况下要将数据封装为UDP包<br>&nbsp;&nbsp;struct xfrm_encap_tmpl *encap = x-&gt;encap;<br>&nbsp;&nbsp;struct udphdr *uh;<br>&nbsp;&nbsp;u32 *udpdata32;<br>// IP头后改为UDP头<br>&nbsp;&nbsp;uh = (struct udphdr *)esph;<br>// 填充UDP头参数, 源端口, 目的端口, UDP数据长度<br>&nbsp;&nbsp;uh-&gt;source = encap-&gt;encap_sport;<br>&nbsp;&nbsp;uh-&gt;dest = encap-&gt;encap_dport;<br>&nbsp;&nbsp;uh-&gt;len = htons(skb-&gt;len + alen - top_iph-&gt;ihl*4);<br>// 校验和为0, 表示不需要计算校验和, ESP本身就进行认证了<br>&nbsp;&nbsp;uh-&gt;check = 0;</div>
<div>&nbsp;&nbsp;switch (encap-&gt;encap_type) {<br>&nbsp;&nbsp;default:<br>&nbsp;&nbsp;case UDP_ENCAP_ESPINUDP:<br>// 在该模式下ESP头跟在UDP头后面<br>&nbsp;&nbsp;&nbsp;esph = (struct ip_esp_hdr *)(uh + 1);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;case UDP_ENCAP_ESPINUDP_NON_IKE:<br>// 在该模式下ESP头跟在UDP头后面8字节处<br>&nbsp;&nbsp;&nbsp;udpdata32 = (u32 *)(uh + 1);<br>&nbsp;&nbsp;&nbsp;udpdata32 = udpdata32 = 0;<br>&nbsp;&nbsp;&nbsp;esph = (struct ip_esp_hdr *)(udpdata32 + 2);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;}<br>// 外部IP头协议是UDP<br>&nbsp;&nbsp;top_iph-&gt;protocol = IPPROTO_UDP;<br>&nbsp;} else<br>// 非NAT穿越情况下, 外部IP头中的协议是ESP<br>&nbsp;&nbsp;top_iph-&gt;protocol = IPPROTO_ESP;</div>
<div>// 填充ESP头中的SPI和序列号<br>&nbsp;esph-&gt;spi = x-&gt;id.spi;<br>&nbsp;esph-&gt;seq_no = htonl(++x-&gt;replay.oseq);<br>// 序列号更新通知回调<br>&nbsp;xfrm_aevent_doreplay(x);</div>
<div>// 如果加密初始化向量长度非零, 设置加密算法中的初始化向量<br>&nbsp;if (esp-&gt;conf.ivlen) {<br>&nbsp;&nbsp;if (unlikely(!esp-&gt;conf.ivinitted)) {<br>&nbsp;&nbsp;&nbsp;get_random_bytes(esp-&gt;conf.ivec, esp-&gt;conf.ivlen);<br>&nbsp;&nbsp;&nbsp;esp-&gt;conf.ivinitted = 1;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;crypto_blkcipher_set_iv(tfm, esp-&gt;conf.ivec, esp-&gt;conf.ivlen);<br>&nbsp;}<br>// 加密操作<br>&nbsp;do {<br>&nbsp;&nbsp;struct scatterlist *sg = &amp;esp-&gt;sgbuf;</div>
<div>&nbsp;&nbsp;if (unlikely(nfrags &gt; ESP_NUM_FAST_SG)) {<br>&nbsp;&nbsp;&nbsp;sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);<br>&nbsp;&nbsp;&nbsp;if (!sg)<br>&nbsp;&nbsp;&nbsp;&nbsp;goto error;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;skb_to_sgvec(skb, sg, esph-&gt;enc_data+esp-&gt;conf.ivlen-skb-&gt;data, clen);<br>// 对数据加密<br>&nbsp;&nbsp;err = crypto_blkcipher_encrypt(&amp;desc, sg, sg, clen);<br>&nbsp;&nbsp;if (unlikely(sg != &amp;esp-&gt;sgbuf))<br>&nbsp;&nbsp;&nbsp;kfree(sg);<br>&nbsp;} while (0);</div>
<div>&nbsp;if (unlikely(err))<br>&nbsp;&nbsp;goto error;<br>// 将加密算法初始化向量拷贝到数据包<br>&nbsp;if (esp-&gt;conf.ivlen) {<br>&nbsp;&nbsp;memcpy(esph-&gt;enc_data, esp-&gt;conf.ivec, esp-&gt;conf.ivlen);<br>&nbsp;&nbsp;crypto_blkcipher_get_iv(tfm, esp-&gt;conf.ivec, esp-&gt;conf.ivlen);<br>&nbsp;}<br>// 认证计算, 计算出HASH值并拷贝到数据包中<br>&nbsp;if (esp-&gt;auth.icv_full_len) {<br>&nbsp;&nbsp;err = esp_mac_digest(esp, skb, (u8 *)esph - skb-&gt;data,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(*esph) + esp-&gt;conf.ivlen + clen);<br>&nbsp;&nbsp;memcpy(pskb_put(skb, trailer, alen), esp-&gt;auth.work_icv, alen);<br>&nbsp;}<br>// 重新计算外部IP头校验和<br>&nbsp;ip_send_check(top_iph);</div>
<div>error:<br>&nbsp;return err;<br>}</div>
<div>&nbsp;</div>
<div>...... 待续 ......</div>
页: [1]
查看完整版本: Linux内核中的IPSEC实现(6)