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