首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

Linux内核中的IPSEC兑现(6)

2012-07-01 
Linux内核中的IPSEC实现(6)本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档

Linux内核中的IPSEC实现(6)
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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 AH8.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 ESP8.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_lenstatic 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;} ...... 待续 ......

热点排行