Linux内核中的IPSEC实现(6)

Linux内核中的IPSEC实现(6)

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。

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 {

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值