您的位置:首页 > 运维架构 > Linux

Linux内核中的IPSEC实现(6)

2018-02-23 14:01 489 查看
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 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 br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  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 br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  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< br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' /> 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) < br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 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 br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  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 br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  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< br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' /> 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 {
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: