- 浏览: 314244 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
JQ_AK47:
...
Linux下直接发送以太包 -
winsen2009:
谢谢分享,如果能再来一个列子就更好了,刚接触看完还是不懂的用
UNPv1_r3读书笔记: SCTP编程
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
6. XFRM的其他操作 6.1 HASH处理 关于HASH值的计算方法主要在net/xfrm/xfrm_hash.h中定义: // IPV4地址HASH static inline unsigned int __xfrm4_addr_hash(xfrm_address_t *addr) { // 就是地址本身 return ntohl(addr->a4); } // IPV6地址HASH static inline unsigned int __xfrm6_addr_hash(xfrm_address_t *addr) { // 取后2个32位数异或 return ntohl(addr->a6[2] ^ addr->a6[3]); } // IPV4源,目的地址HASH static inline unsigned int __xfrm4_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr) { // 将两个地址异或 return ntohl(daddr->a4 ^ saddr->a4); } // IPV4源,目的地址HASH static 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]); } // 目的地址HASH static 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; } // 索引号HASH static inline unsigned int __idx_hash(u32 index, unsigned int hmask) { // 低24位和高24位异或, 高8位不动, 再和掩码相与 return (index ^ (index >> 8)) & hmask; } // 选择子HASH static 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; } // 地址HASH static 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_SIZE) 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_SIZE) 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++) { // 查找和指定算法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(); i++) 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++) { 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(); i++) 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++) { 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++) { // 比较算法名称或缩写名称是否和指定名称相同 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++) { // 根据算法名称确定该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++) { // 根据算法名称确定该加密算法是否存在, 返回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++) { // 根据算法名称确定该压缩算法是否存在, 返回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); ...... 待续 ......
发表评论
-
Linux内核中流量控制(24)
2011-01-10 16:33 2203本文档的Copyleft归yfydz所 ... -
Linux内核中流量控制(23)
2011-01-10 16:30 1486本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(22)
2011-01-10 16:29 1930本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(21)
2011-01-10 16:28 1345本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(20)
2011-01-10 16:27 1516本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(19)
2011-01-10 16:27 1972本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(18)
2011-01-10 16:26 1564Linux内核中流量控制(18) ... -
Linux内核中流量控制(17)
2011-01-10 16:25 1940本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(16)
2011-01-10 16:25 1795本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(15)
2011-01-10 16:24 1881本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(14)
2011-01-10 16:23 1954本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(13)
2011-01-10 16:22 2627本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(12)
2011-01-10 16:21 2100本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(11)
2011-01-10 16:21 3228本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(10)
2011-01-10 16:20 2001本文档的Copyleft归yfydz所 ... -
Linux内核中流量控制(9)
2011-01-10 16:19 1826本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(8)
2011-01-10 16:18 1493本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(7)
2011-01-10 16:18 2919本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(6)
2011-01-10 16:17 1489本文档的Copyleft归yfydz所有,使用GPL发布,可以 ... -
Linux内核中流量控制(5)
2011-01-10 16:16 1724本文档的Copyleft归yfydz所有,使用GPL发布,可以 ...
相关推荐
Linux内核中IPSec网关的设计与实现.pdf
基于Linux2.6内核的IPSec实现研究.pdf
很清楚的分析了一个数据包如何通过查询路由进入内核ipsec协议栈的处理、Linux 内核ipsec协议栈详细的加解密流程以及加解密完后如何将数据包发送出去。 文档中前半部分主要介绍一些关键的数据结构,及其相互之间的...
Linux 2.6内核中IPSec协议接入机制研究与分析.pdf
基于Linux2_6内核的IPSec实现研究
linux的IPSec内核实现,请大家能够喜欢,多给点分!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Linux 2.6内核中IPSec支持机制分析.pdf
Linux内核中IPSec网关的设计与实习.pdf
Linux2.6内核IPSec支持机构的研究与分析.pdf
IPsec在Linux内核2.4和2.6中实现之分析和比较.pdf
Linux内核2.6版中IPSec实现的研究.pdf
Linux2.4内核下IPSec加密卡的实现研究.pdf
SM3算法及在Linux内核IPSEC协议栈的实现.pdf
一种基于Linux内核的IPsec协议栈设计.pdf
讲解linux内核最新代码中ipsec pfkey的实现过程。很具有参考价值。
基于IPv6的IPSec原理分析和在Linux内核中的实现,孙善鹏,,IPsec是通过对IP协议(互联网协议)的分组进行加密和认证来保护IP协议的网络传输协议族,它在IPv6标准协议栈中被要求实现给IPv6网络数�
Linux 2.6内核中IPSec“无缝接入”分析.pdf
文档从源码层面分析linux 内核加解密架构的实现。文档前半部分分析内核加解密用到的重要数据结构各成员变量的作用及各个数据结构之间的关系。后半部分分析加解密流程,函数调用关系。重要源码都有中文注释。是一篇从...
linux的ipsec内核实现源码分析,本人的源码阅读笔记,主要讲解了xfrm模块的实现和源码流程,有需要的可以下载参考
描述IPsec 在Linux下的实现及背景内容,包含Linux下IPsec工具,内核IPsec支持等内容。