标签:aaa viso 编写 sizeof 限制 font tde node unsigned
即匹配到一个路由项。然后将数据包发给该路由项指示的下一跳。
假设我们把上面对IP路由查找的过程向上抽象一个层次,就会发现,事实上它还能够有别的用。抽象后的表述为:以数据包的源地址或者目标地址为键值去查询一张表。查到结果项以后运行结果项指示的一个动作。一个结果项为:
struct result_node { uint32 network; uint32 netmask; void *action; };以上这个思想多亏了路由查找中的“最长前缀匹配”原则,该原则是隐式的,可是该原则保证了最精确的匹配。
可是现如今,不须要吝啬内存了。我们便能够在内核里面塞入不论什么能够塞入的东西。仅仅要设计得当,让它符合UNIX哲学精神就可以。
总的来讲。我希望从一个到达的数据包上取出一个事先配置好的字符串。
这些缺陷难道Linux内核以及Netfilter社区的那帮大牛们意识不到吗?绝对不是这样,由于他们遵循的是Worse is better原则。该原则的核心就是简单主宰一切,为了简单能够舍弃该舍弃的一切。
而我的做法,即实现非常多协议栈不包括的东西,看起来就是违背的就是Worse is better原则,我用Completeness(完备性)原则替换了Simplicity(简单性)原则。而这两个的原则的正确表述应该是:
UNIX思想教导我们要将问题拆成互相独立的小问题,然后让它们相互配合去解决终于的大问题,可是相互配合本身有时会成为新的问题,须要付出巨大的管理成本。并非每一类问题都能够拆成cat file|grep key那样的方式的。
当我发现IP路由的查找过程事实上就是一个“匹配-动作”的模式之后,我便改了“动作”的解释,将“发往下一跳”改为“取出info信息”,注意。仅仅是取出info信息,并没有设置它到conntrack,为何不把这两件事一起做呢?由于这两件事之间的关系和cat file|grep key之间的关系非常像。上一小节的4个问题。我是这么回答的:
1.使用路由查找模块是全然独立于iptables,和iptables没有半毛钱关系;
2.使用路由查找模块不须要和不论什么其他模块接口,你仅仅须要调用下面接口:
int nf_route_table_search(const u_int32_t src, char *info, int len);
假设src找到。则将和该地址所在的最长前缀匹配的网络关联的info信息取出来,假设没有与之关联的,则返回非0
3.效率更不用说,全然就是Linux内核的hash路由查找算法,用了十几年了。跑在各种环境下。
4.尽管我没有时间去研究nf-HiPAC项目,可是我对路由查找算法却早已精通,一个思想是:永远使用自己最熟悉的技术。
在内核中,struct fib_node指示一个路由项,为了和标准的区分,我加了一个nf_前缀表明它是和Netfilter关联并由nf_conntrack使用的“路由项”。同一时候去掉携带数据类型的定义:
/* * 下面结构体指示一个“路由节点”。它能够携带一个extra_data * 你能够随意定义它,比方能够例如以下定义: * struct my_extra { * char info[INFO_SIZE]; * int policy; // NF_ACCEPT,NF_DROP或者其他? * // .... extra of extra ?? * }; * 它带来了无限的扩展性,你能够使用这个“路由”节点存储不论什么数据。 */ struct nf_fib_node { struct hlist_node nf_fn_hash; u_int32_t fn_key; int len; // extra_len指示你传入的extra data的长度 int extra_len; // 0长度数组,你能够随意定义它 char extra[0]; };
在非常多的系统上。ACL对数据包的具体匹配行为依赖管理员的规则配置顺序。注意以上说的是ACL的用户配置接口,当然对于这一点,Linux的iptables以及Cisco的ACL是一致的,可是对于内部的实现,各家系统就有所差别了。以Linux的iptables为例,考虑下面的规则序列:
i=1; # 增加巨量的iptables规则 for ((; i < 255; i++)); do j=1; for ((; j < 255; j++)); do iptables -A FORWARD -s 192.168.$i.$j -j DROP; done done
这样的思想全然颠倒了以往以规则中的地址来匹配数据包的匹配主导权,变成了按数据包的源/目标IP去匹配规则。
使用路由查找的思想无疑是非常方便的!
按照上一小节我对struct nf_fib_node的扩展。在这个意义上,它的extra的定义已经相当明白:
struct my_extra { // 替换iptables中的matches struct list_head matches_list; // 替换iptables的target int policy; // NF_ACCEPT,NF_DROP或者其他? };
兴许的数据包不再查询这个路由表。
内核的路由查找模块的移植代码一会儿我再给出,如今先给出针对confirm的改动。改动的仅有ipv4_confirm函数。它是挂接于INET IPv4协议族的一个HOOK函数,这样我便不必考虑其他的协议了,从而省略了协议推断:
static unsigned int ipv4_confirm(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { ... out: /* We‘ve seen it coming out the other side: confirm it */ ret = nf_conntrack_confirm(skb); if (ret == NF_ACCEPT) { #include "nf_conntrack_info.h" #define MAX_LEN 128 struct nf_conn_priv { struct nf_conn_counter ncc[IP_CT_DIR_MAX]; char *info; }; struct nf_conn_priv *ncp; struct nf_conn_counter *acct; acct = nf_conn_acct_find(ct); if (acct) { char buf[MAX_LEN] = {0}; int len = MAX_LEN; struct iphdr *iph = ip_hdr(skb); // 查询“路由表”,获取结果 int rv = nf_route_table_search(iph->saddr, buf, &len); if (!rv) { ncp = (struct nf_conn_priv *)acct; if (ncp->info == NULL) { ncp->info = (char *)kcalloc(1, len+1, GFP_ATOMIC); } // 拷贝获取的结果到conntrack memcpy(ncp->info, buf, len); } } } return ret; }
好了,如今该把对Linux路由查找算法的移植展示出来了,我想hash算法已经经得住了这么多年的性能考验,也就没有移植trie树算法。同一时候我裁减掉了rehash机制。这个移植非常easy。基本就是net/ipv4/fib_hash.c的改动操作,去除了fib_node的类型化定义。也就是说将其提升到了“泛型”的层次。
代码例如以下:
头文件:nf_conntrack_rtable.h
#include <linux/types.h> #include <linux/list.h> #define SIZE 128 struct nf_fn_zone { struct nf_fn_zone *fz_next; /* Next not empty zone */ struct hlist_head *fz_hash; /* Hash table pointer */ int fz_nent; /* Number of entries */ int fz_divisor; /* Hash divisor */ u32 fz_hashmask; /* (fz_divisor - 1) */ #define FZ_HASHMASK(fz) ((fz)->fz_hashmask) int fz_order; /* Zone order */ u_int32_t fz_mask; #define FZ_MASK(fz) ((fz)->fz_mask) }; struct nf_fn_hash { struct nf_fn_zone *nf_fn_zones[33]; struct nf_fn_zone *nf_fn_zone_list; }; /* * 下面结构体指示一个“路由节点”,它能够携带一个extra_data * 你能够随意定义它,比方能够例如以下定义: * struct my_extra { * char info[128]; * int policy; // NF_ACCEPT,NF_DROP或者其他? * // .... extra of extra ?? * }; * 它带来了无限的扩展性。你能够使用这个“路由”节点存储不论什么数据。 */ struct nf_fib_node { struct hlist_node nf_fn_hash; u_int32_t fn_key; int len; // extra_len指示你传入的extra data的长度 int extra_len; // 0长度数组,你能够随意定义它 char extra[0]; }; // 查找接口 int nf_route_table_search(const u_int32_t dst, void *res, int *res_len); // 增加接口 int nf_route_table_add( u_int32_t network, u_int32_t netmask, void *extra, int extra_len); // 节点删除接口 int nf_route_table_delete(u_int32_t network, u_int32_t mask); // 清除接口 void nf_route_table_clear(void);
#include <linux/types.h> #include <linux/inetdevice.h> #include <linux/slab.h> #include <linux/kernel.h> #include "nf_conntrack_info.h" #ifndef NULL #define NULL 0 #endif // 使用lock总是安全的。内核编程两要素:1.安全;2.高效 static DEFINE_RWLOCK(nf_hash_lock); struct nf_fn_hash *route_table = NULL; static inline u_int32_t nf_fz_key(u_int32_t dst, struct nf_fn_zone *fz) { return dst & FZ_MASK(fz); } static inline u32 nf_fn_hash(u_int32_t key, struct nf_fn_zone *fz) { u32 h = key>>(32 - fz->fz_order); h ^= (h>>20); h ^= (h>>10); h ^= (h>>5); h &= FZ_HASHMASK(fz); return h; } static struct hlist_head *fz_hash_alloc(int divisor) { unsigned long size = divisor * sizeof(struct hlist_head); return kcalloc(1, size, GFP_ATOMIC); } static struct nf_fn_zone * fn_new_zone(struct nf_fn_hash *table, int z) { int i; struct nf_fn_zone *fz = kcalloc(1, sizeof(struct nf_fn_zone), GFP_ATOMIC); if (!fz) return NULL; if (z) { fz->fz_divisor = 16; } else { fz->fz_divisor = 1; } fz->fz_hashmask = (fz->fz_divisor - 1); fz->fz_hash = fz_hash_alloc(fz->fz_divisor); if (!fz->fz_hash) { kfree(fz); return NULL; } fz->fz_order = z; fz->fz_mask = inet_make_mask(z); /* Find the first not empty zone with more specific mask */ for (i=z+1; i<=32; i++) if (table->nf_fn_zones[i]) break; write_lock_bh(&nf_hash_lock); if (i>32) { /* No more specific masks, we are the first. */ fz->fz_next = table->nf_fn_zone_list; table->nf_fn_zone_list = fz; } else { fz->fz_next = table->nf_fn_zones[i]->fz_next; table->nf_fn_zones[i]->fz_next = fz; } table->nf_fn_zones[z] = fz; write_unlock_bh(&nf_hash_lock); return fz; } // 路由表操作接口:1.查找;2.删除。參数过于多。相似Win32 API,风格不好,但使用方便 int nf_route_table_opt(const u_int32_t dst, const u_int32_t mask, int del_option, void *res, int *res_len) { int rv = 1; struct nf_fn_zone *fz; struct nf_fib_node *del_node = NULL; if (NULL == route_table) { printk(""); return 1; } read_lock(&nf_hash_lock); for (fz = route_table->nf_fn_zone_list; fz; fz = fz->fz_next) { struct hlist_head *head; struct hlist_node *node; struct nf_fib_node *f; u_int32_t k = nf_fz_key(dst, fz); head = &fz->fz_hash[nf_fn_hash(k, fz)]; hlist_for_each_entry(f, node, head, nf_fn_hash) { if (f->fn_key == k){ if ( 1 == del_option && mask == FZ_MASK(fz)){ del_node = f; } else if (0 == del_option){ // 将用户传入的extra数据拷贝给调用者。 memcpy(res, (const void *)(f->extra), f->extra_len); *res_len = f->extra_len; } rv=0; goto out; } } } rv = 1; out: read_lock(&nf_hash_lock); if (del_node) { write_lock_bh(&nf_hash_lock); __hlist_del(&del_node->nf_fn_hash); kfree(del_node); write_unlock_bh(&nf_hash_lock); } return rv; } static inline void fib_insert_node(struct nf_fn_zone *fz, struct nf_fib_node *f) { struct hlist_head *head = &fz->fz_hash[nf_fn_hash(f->fn_key, fz)]; hlist_add_head(&f->nf_fn_hash, head); } int nf_route_table_search(u_int32_t dst, void *res, int *res_len) { return nf_route_table_opt(dst, 32, 0, res, res_len); } int nf_route_table_delete(u_int32_t network, u_int32_t mask) { return nf_route_table_opt(network, mask, 1, NULL, NULL); } int nf_route_table_add(u_int32_t network, u_int32_t netmask, void *extra, int extra_len) { struct nf_fib_node *new_f; struct nf_fn_zone *fz; new_f = kcalloc(1, sizeof(struct nf_fib_node) + extra_len, GFP_ATOMIC); new_f->len = inet_mask_len(netmask); new_f->extra_len = extra_len; new_f->fn_key = network; memcpy(new_f->extra, extra, extra_len); if (new_f->len > 32) { return -1; } INIT_HLIST_NODE(&new_f->nf_fn_hash); if ( NULL == route_table){ route_table = kcalloc(1, sizeof(struct nf_fn_hash), GFP_ATOMIC); fz = fn_new_zone(route_table,new_f->len); } else { fz = route_table->nf_fn_zones[new_f->len]; } if (!fz && !(fz = fn_new_zone(route_table,new_f->len))) { return -1; } fib_insert_node(fz, new_f); return 0; } void nf_route_table_clear( void ) { struct nf_fn_zone *fz,*old; if (NULL == route_table) { printk(""); return; } write_lock_bh(&nf_hash_lock); for (fz = route_table->nf_fn_zone_list; fz; ) { if (fz != NULL){ kfree(fz->fz_hash); fz->fz_hash=NULL; old = fz; fz = fz->fz_next; kfree(old); old=NULL; } } kfree(route_table); route_table=NULL; write_unlock_bh(&nf_hash_lock); return; }
nf_conntrack_ipv4-objs := nf_conntrack_l3proto_ipv4.o nf_conntrack_proto_icmp.o nf_conntrack_info.o然后编译就可以。出来的nf_conntrack_ipv4.ko就已经增加自己的扩展了。
仅仅要不是纯机器操作,就不要使用二进制,二进制是机器的世界,仅仅要涉及到人的因素,文本是最好的信息交换方式。
因此,我选择了procfs作为和内核机制通信的方式,以它来配置策略,或者说增加/删除“扩展路由”。
效果
当我运行下面的操作:
echo +192.168.10.0 255.255.255.0 1234abcd >/proc/nfrtable
此时有来自192.168.10.30的数据包过来,你就会在/proc/net/nf_conntrack中看到和该数据流关联的1234abcd信息,反之假设有来自192.168.20.0/24网段的数据包,将不会有这个信息。此外,当你运行iptables-save的时候。再也看不到关于info的设置了,事实上。INFO模块根本就不须要了。
正如一座金矿,管理者仅仅是在管理它,不论什么淘金者都能够挖走自己须要的金子,可是管理者不提供不论什么车辆负责搬运。即使你有钱想租也没有。要知道,金子是非常重的哦!
Linux金矿还有还有一项规则,即不论什么人都能够在里面增加自己的代码,工具以及不论什么其他东西。因此就引出一个问题,那就是Linux内核API的不稳定性。你别指望有一个品管团队来检查你的代码实现以及接口定义。社区在乎的是。仅仅要它攻克了实际问题,就会被採纳。假设你在使用2.6.18内核。然后你觉得不好,然后你改动了它的一部分接口,确实没有被Linus骂,那么这些接口将成为2.6.18-x或者2.6.19的标准接口。
然而Linux提供了LKM机制,而LKM须要调用的正是内核接口。因此LKM的版本号号必须和其宿主的内核版本号号绝对一致。由于内核接口随时都是能够变化的。
Linux的风格决定了它的灵活性,代价就是诸多的关于LKM的限制。Windows正好相反。它提供最大限度的兼容性接口,无论是内核驱动还是用户应用都是如此。当然也有代价,那就是Windows内核和拥护库的日益臃肿。由于须要大量的适配器来兼容新老接口。
微软的系统的API也是不断变化的,仅仅是它向用户隐藏了这样的变化而已,实施这样的隐藏的正是微软的在职人员...
标签:aaa viso 编写 sizeof 限制 font tde node unsigned
原文地址:http://www.cnblogs.com/llguanli/p/7043958.html