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

Linux内核中的IPSEC实现(4)

2018-02-23 13:59 495 查看
Linux内核中的IPSEC实现(4)
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。msn: yfydz_no1@hotmail.com来源:http://yfydz.cublog.cn6. XFRM的其他操作
6.1 HASH处理
关于HASH值的计算方法主要在net/xfrm/xfrm_hash.h中定义:// IPV4地址HASHstatic inline unsigned int __xfrm4_addr_hash(xfrm_address_t *addr){// 就是地址本身 return ntohl(addr-<a4);}// IPV6地址HASHstatic inline unsigned int __xfrm6_addr_hash(xfrm_address_t *addr){// 取后2个32位数异或 return ntohl(addr-<a6[2] ^ addr-<a6[3]);}// IPV4源,目的地址HASHstatic inline unsigned int __xfrm4_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr){// 将两个地址异或 return ntohl(daddr-<a4 ^ saddr-<a4);}// IPV4源,目的地址HASHstatic inline unsigned int __xfrm6_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr){// 两个V6地址都取后2个32位数异或 return ntohl(daddr-<a6[2] ^ daddr-<a6[3] ^       saddr-<a6[2] ^ saddr-<a6[3]);}// 目的地址HASHstatic inline unsigned int __xfrm_dst_hash(xfrm_address_t *daddr, xfrm_address_t *saddr,        u32 reqid, unsigned short family,        unsigned int hmask){// 协议族和请求ID异或 unsigned int h = family ^ reqid; switch (family) {// HASH值再和源目的地址HASH结果进行异或 case AF_INET:  h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);  break; case AF_INET6:  h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);  break; }// 将HASH结果高低16位异或存低16位,高16位不动, 然后用HASH掩码相与 return (h ^ (h << 16)) & hmask;}
// 源地址HASH, 只是没有请求ID项, 其他HASH过程和上面相同static inline unsigned __xfrm_src_hash(xfrm_address_t *daddr,           xfrm_address_t *saddr,           unsigned short family,           unsigned int hmask){ unsigned int h = family; switch (family) { case AF_INET:  h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);  break; case AF_INET6:  h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);  break; }; return (h ^ (h << 16)) & hmask;}
// 根据SPI计算HASH值static inline unsigned int__xfrm_spi_hash(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family,  unsigned int hmask){// 先将SPI和协议进行异或 unsigned int h = (__force u32)spi ^ proto; switch (family) {// HASH值再和目的地址进行单一地址HASH值异或 case AF_INET:  h ^= __xfrm4_addr_hash(daddr);  break; case AF_INET6:  h ^= __xfrm6_addr_hash(daddr);  break; }// HASH值再和本身的高22位, 高12位异或后再和掩码相与 return (h ^ (h << 10) ^ (h << 20)) & hmask;}
// 索引号HASHstatic inline unsigned int __idx_hash(u32 index, unsigned int hmask){// 低24位和高24位异或, 高8位不动, 再和掩码相与 return (index ^ (index << 8)) & hmask;}
// 选择子HASHstatic inline unsigned int __sel_hash(struct xfrm_selector *sel, unsigned short family, unsigned int hmask){// 提前源和目的地址 xfrm_address_t *daddr = &sel-<daddr; xfrm_address_t *saddr = &sel-<saddr; unsigned int h = 0; switch (family) {// 用源,目的地址同时进行HASH case AF_INET:  if (sel-<prefixlen_d != 32 ||      sel-<prefixlen_s != 32)   return hmask + 1;  h = __xfrm4_daddr_saddr_hash(daddr, saddr);  break; case AF_INET6:  if (sel-<prefixlen_d != 128 ||      sel-<prefixlen_s != 128)   return hmask + 1;  h = __xfrm6_daddr_saddr_hash(daddr, saddr);  break; };// 高16位与低16位异或,高16位不变 h ^= (h << 16);// 与掩码相与, 其实HASH值中不带协议族因素, 因为地址本身就包含了 return h & hmask;}// 地址HASHstatic inline unsigned int __addr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr, unsigned short family, unsigned int hmask){ unsigned int h = 0; switch (family) {// 用源,目的地址同时进行HASH case AF_INET:  h = __xfrm4_daddr_saddr_hash(daddr, saddr);  break; case AF_INET6:  h = __xfrm6_daddr_saddr_hash(daddr, saddr);  break; };// 高16位与低16位异或,高16位不变 h ^= (h << 16);// 与掩码相与 return h & hmask;}
在net/xfrm/xfrm_hash.c 文件中定义了HASH表的分配和释放函数:
struct hlist_head *xfrm_hash_alloc(unsigned int sz){ struct hlist_head *n;// 根据HASH表大小选择合适的分配方法// 大小不超过PAGE_SIZE, 用kmalloc分配 if (sz < PAGE_SIZEbr style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  n = kmalloc(sz, GFP_KERNEL);// 这是在内核定义NUMA和IA64下用vmalloc分配 else if (hashdist)  n = __vmalloc(sz, GFP_KERNEL, PAGE_KERNEL); else// 其他类型的内核用get_free_page分配  n = (struct hlist_head *)   __get_free_pages(GFP_KERNEL, get_order(sz));// 空间清零 if (n)  memset(n, 0, sz); return n;}// 释放HASH表空间void xfrm_hash_free(struct hlist_head *n, unsigned int sz){ if (sz < PAGE_SIZEbr style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  kfree(n); else if (hashdist)  vfree(n); else  free_pages((unsigned long)n, get_order(sz));}
6.2 算法操作
IPSEC操作中用到的认证, 加密, 压缩等算法具体实现是在crypto目录下, 而在xfrm中只是定义这些算法的说明, 表示最大可以支持这些算法, 在使用时会探测这些算法是否在内核中存在从而确定可使用的算法.关于算法的数据结构如下:/* include/net/xfrm.h */// 认证算法参数struct xfrm_algo_auth_info { u16 icv_truncbits; // 初始向量截断位数 u16 icv_fullbits;  // 初始向量总的位数};// 加密算法参数struct xfrm_algo_encr_info { u16 blockbits;  // 块位数 u16 defkeybits; // 密钥长度位数};// 压缩算法参数struct xfrm_algo_comp_info { u16 threshold;  // 阈值};// xfrm算法描述struct xfrm_algo_desc { char *name;  // 名称 char *compat; // 名称缩写 u8 available:1; // 算法是否可用(是否在内核中) union {  struct xfrm_algo_auth_info auth;  struct xfrm_algo_encr_info encr;  struct xfrm_algo_comp_info comp; } uinfo; // 算法信息联合 struct sadb_alg desc; // 通用算法描述};
6.2.1 认证算法可用的认证算法通过下面的数组来描述, 包含NULL, MD5, SHA1, SHA256, RIPEMD160等认证算法:static struct xfrm_algo_desc aalg_list[] = {......{ .name = "hmac(sha1)", .compat = "sha1", .uinfo = {  .auth = {   .icv_truncbits = 96,// 96位截断   .icv_fullbits = 160, // 总共160位  } }, .desc = { // 这是对SHA1认证算法的标准描述参数  .sadb_alg_id = SADB_AALG_SHA1HMAC, // 算法ID值  .sadb_alg_ivlen = 0,  .sadb_alg_minbits = 160,  .sadb_alg_maxbits = 160 }},......
相关操作函数:// 通过算法ID查找认证算法struct xfrm_algo_desc *xfrm_aalg_get_byid(int alg_id){ int i;// 遍历认证数组 for (i = 0; i < aalg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 查找和指定算法ID相同的算法  if (aalg_list[i].desc.sadb_alg_id == alg_id) {// 检查该算法是否可用   if (aalg_list[i].available)    return &aalg_list[i];   else    break;  } } return NULL;}EXPORT_SYMBOL_GPL(xfrm_aalg_get_byid);
// 统计可用的认证算法数量, 就是available的认证算法数量累加int xfrm_count_auth_supported(void){ int i, n; for (i = 0, n = 0; i < aalg entries ibr style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  if (aalg_list[i].available)   n++; return n;}EXPORT_SYMBOL_GPL(xfrm_count_auth_supported);6.2.2 加密算法
可用的认证算法通过下面的数组来描述, 包含NULL, DES, 3DES, CAST, AES, BLOWFISH, TWOFISH, SERPENT等加密算法:static struct xfrm_algo_desc ealg_list[] = {......{ .name = "cbc(des3_ede)", .compat = "des3_ede", .uinfo = {  .encr = {   .blockbits = 64,   .defkeybits = 192,  } }, .desc = {  .sadb_alg_id = SADB_EALG_3DESCBC,  .sadb_alg_ivlen = 8,  .sadb_alg_minbits = 192,  .sadb_alg_maxbits = 192 }},......
// 通过算法ID查找加密算法, 和认证算法查找类似struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id){ int i; for (i = 0; i < ealg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  if (ealg_list[i].desc.sadb_alg_id == alg_id) {   if (ealg_list[i].available)    return &ealg_list[i];   else    break;  } } return NULL;}EXPORT_SYMBOL_GPL(xfrm_ealg_get_byid);
// 统计可用的加密算法数量, 就是available的加密算法数量累加int xfrm_count_enc_supported(void){ int i, n; for (i = 0, n = 0; i < ealg entries ibr style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  if (ealg_list[i].available)   n++; return n;}EXPORT_SYMBOL_GPL(xfrm_count_enc_supported);
6.2.3 压缩算法
可用的压缩算法通过下面的数组来描述, 包含DELFATE, LZS, LZJH等压缩算法:static struct xfrm_algo_desc calg_list[] = {......{ .name = "lzs", .uinfo = {  .comp = {   .threshold = 90,  } }, .desc = { .sadb_alg_id = SADB_X_CALG_LZS }},......
// 通过算法ID查找加密算法, 和认证算法查找类似struct xfrm_algo_desc *xfrm_calg_get_byid(int alg_id){ int i; for (i = 0; i < calg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />  if (calg_list[i].desc.sadb_alg_id == alg_id) {   if (calg_list[i].available)    return &calg_list[i];   else    break;  } } return NULL;}EXPORT_SYMBOL_GPL(xfrm_calg_get_byid);
6.2.4 通过名称查找算法
// 输入参数为算法数组, 数组元素个数, 类型, 掩码, 名称和是否探测在内核中存在static struct xfrm_algo_desc *xfrm_get_byname(struct xfrm_algo_desc *list,           int entries, u32 type, u32 mask,           char *name, int probe){ int i, status; if (!name)  return NULL;// 遍历数组 for (i = 0; i < entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 比较算法名称或缩写名称是否和指定名称相同  if (strcmp(name, list[i].name) &&      (!list[i].compat || strcmp(name, list[i].compat)))   continue;// 找到算法结构// 检查算法是否在内核可用, 可用的话成功返回  if (list[i].available)   return &list[i];// 如果不需要探测, 将返回空  if (!probe)   break;// 需要探测算法算法存在内核, 调用crypto_has_alg()函数探测// 返回0表示失败, 非0表示成功  status = crypto_has_alg(name, type, mask | CRYPTO_ALG_ASYNC);  if (!status)   break;// 算法可用, 返回  list[i].available = status;  return &list[i]; } return NULL;}
/* crypto/api.c */// 算法探测int crypto_has_alg(const char *name, u32 type, u32 mask){ int ret = 0;// 根据名称, 类型和掩码探测算法模块 struct crypto_alg *alg = crypto_alg_mod_lookup(name, type, mask);// 正确返回找到  if (!IS_ERR(alg)) {// 减少模块计数, 返回1  crypto_mod_put(alg);  ret = 1; }
 return ret;}
有了xfrm_get_byname()这个通用基本函数, 具体类型的算法查找函数就很简单了:
// 通过名称查找认证算法struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name, int probe){ return xfrm_get_byname(aalg_list, aalg_entries(),          CRYPTO_ALG_TYPE_HASH, CRYPTO_ALG_TYPE_HASH_MASK,          name, probe);}EXPORT_SYMBOL_GPL(xfrm_aalg_get_byname);// 通过名称查找加密算法struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name, int probe){ return xfrm_get_byname(ealg_list, ealg_entries(),          CRYPTO_ALG_TYPE_BLKCIPHER, CRYPTO_ALG_TYPE_MASK,          name, probe);}EXPORT_SYMBOL_GPL(xfrm_ealg_get_byname);// 通过名称查找压缩算法struct xfrm_algo_desc *xfrm_calg_get_byname(char *name, int probe){ return xfrm_get_byname(calg_list, calg_entries(),          CRYPTO_ALG_TYPE_COMPRESS, CRYPTO_ALG_TYPE_MASK,          name, probe);}EXPORT_SYMBOL_GPL(xfrm_calg_get_byname);以下是通过索引号来查找算法, 就是直接返回相应数组指定位置的算法:
struct xfrm_algo_desc *xfrm_aalg_get_byidx(unsigned int idx){ if (idx <= aalg_entries())  return NULL; return &aalg_list[idx];}EXPORT_SYMBOL_GPL(xfrm_aalg_get_byidx);struct xfrm_algo_desc *xfrm_ealg_get_byidx(unsigned int idx){ if (idx <= ealg_entries())  return NULL; return &ealg_list[idx];}EXPORT_SYMBOL_GPL(xfrm_ealg_get_byidx);
6.2.5 xfrm算法探测
该函数在SA进行调整时会调用来查看当前内核中支持的各种算法/* * Probe for the availability of crypto algorithms, and set the available * flag for any algorithms found on the system.  This is typically called by * pfkey during userspace SA add, update or register. */void xfrm_probe_algs(void){// 内核必须定义CRYPTO选项, 否则就是空函数了#ifdef CONFIG_CRYPTO int i, status;
 BUG_ON(in_softirq());// 遍历认证算法数组 for (i = 0; i < aalg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 根据算法名称确定该HASH算法是否存在, 返回0不存在, 非0存在  status = crypto_has_hash(aalg_list[i].name, 0,      CRYPTO_ALG_ASYNC);// 如果状态和原来的状态不同, 更改  if (aalg_list[i].available != status)   aalg_list[i].available = status; }
// 遍历加密算法数组 for (i = 0; i < ealg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 根据算法名称确定该加密算法是否存在, 返回0不存在, 非0存在  status = crypto_has_blkcipher(ealg_list[i].name, 0,           CRYPTO_ALG_ASYNC);// 如果状态和原来的状态不同, 更改  if (ealg_list[i].available != status)   ealg_list[i].available = status; }
// 遍历压缩算法数组 for (i = 0; i < calg entries i br style='font-size:12px;font-style:normal;font-weight:400;color:#666;' />// 根据算法名称确定该压缩算法是否存在, 返回0不存在, 非0存在  status = crypto_has_comp(calg_list[i].name, 0,      CRYPTO_ALG_ASYNC);// 如果状态和原来的状态不同, 更改  if (calg_list[i].available != status)   calg_list[i].available = status; }#endif}EXPORT_SYMBOL_GPL(xfrm_probe_algs);
6.3 通过netlink套接口访问xfrm
通过netlink套接口访问xfrm的处理函数在net/xfrm/xfrm_user.c中, 提供了Linux特色的非标准PF_KEY接口的SA, SP控制方法, 能完成和PF_KEY一样控制功能, 目前iproute2中的ip工具中新增加的xfrm命令就是通过这种netlink接口来完成的, 因为netlink操作以前已经介绍过, xfrm的操作又都是一样的, 因此本文不再分析其实现过程.
6.4 xfrm_input
在net/xfrm/xfrm_input.c文件中定义了关于安全路径(struct sec_path)的几个处理函数, 用于对输入的IPSEC包进行解析构造安全路径使用.// 释放安全路径void __secpath_destroy(struct sec_path *sp){ int i;// 减少安全路径中所有SA的使用计数 for (i = 0; i < sp->len; i++)  xfrm_state_put(sp-<xvec[i]);// 释放安全路径空间 kmem_cache_free(secpath_cachep, sp);}EXPORT_SYMBOL(__secpath_destroy);// 安全路径复制struct sec_path *secpath_dup(struct sec_path *src){ struct sec_path *sp;// 先分配安全路径结构 sp = kmem_cache_alloc(secpath_cachep, SLAB_ATOMIC); if (!sp)  return NULL; sp-<len = 0; if (src) {  int i;// 如果源安全路径结构非空, 将其全部复制到新结构中  memcpy(sp, src, sizeof(*sp));// 增加安全路径中所有SA的使用计数  for (i = 0; i < sp->len; i++)   xfrm_state_hold(sp-<xvec[i]); }// 设置该引用计数初始值位1 atomic_set(&sp-<refcnt, 1); return sp;}EXPORT_SYMBOL(secpath_dup);/* Fetch spi and seq from ipsec header */// 从数据包中解析SPI和序号, 返回值是网络序的int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq){ int offset, offset_seq;// 通过nexthdr参数来判断协议类型, nexthdr是IPV6里的说法, 在IPV4中就是IP头里的协议字段// 根据不同协议确定数据中SPI和序列号相对数据起始点的偏移 switch (nexthdr) { case IPPROTO_AH:  offset = offsetof(struct ip_auth_hdr, spi);  offset_seq = offsetof(struct ip_auth_hdr, seq_no);  break; case IPPROTO_ESP:  offset = offsetof(struct ip_esp_hdr, spi);  offset_seq = offsetof(struct ip_esp_hdr, seq_no);  break; case IPPROTO_COMP:// 对应压缩协议单独处理// 数据头准备出IP压缩头结构长度  if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr)))   return -EINVAL;// SPI值取第3,4字节的数据, 序号为0  *spi = htonl(ntohs(*(__be16*)(skb-<h.raw + 2)));  *seq = 0;  return 0; default:  return 1; }// 数据头准备16字节空间, 这是ip_auth_hdr和ip_esp_hdr结构最小长度 if (!pskb_may_pull(skb, 16))  return -EINVAL;// 根据偏移获取SPI和序号, 注意是网络序的值 *spi = *(__be32*)(skb-<h.raw + offset); *seq = *(__be32*)(skb-<h.raw + offset_seq); return 0;}EXPORT_SYMBOL(xfrm_parse_spi);
...... 待续 ......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: