作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
=======================================================
作为程序员的我们,必须保证灵活的设计,才能够应付变化的需求。
但是,当把二进制程序发布给用户以后,用户有了新的需求,如果只能由开发者对程序进行修改,无疑是低效率的。而且有的时候,某些用户的需求,应用并不广泛,开发者不可能为一个用户添加该功能。这时候,如果该程序可以支持用户自定义的扩展功能,无疑是对用户是一大福音。
那么,如何让我们的程序支持用户自定义的扩展功能呢?恩~~,还是从Linux的宝库里面寻找吧。今天选择两个学习对象:1. iptables;2. tc
iptables的扩展实现:为了实现新的扩展,需要在iptables的源代码目录下的extensions目录添加新的功能的代码。iptables的扩展功能框架非常清晰,只需要按照iptables的match结构
xtables_match和target结构xtables_target的定义,实现相应的功能即可。具体的参见
http://blog.chinaunix.net/uid-23069658-id-3230608.html
这篇文章。(该文章的iptable版本较低,数据结构与最近版本的iptables完全不一致)
话说,我之前对于iptable的扩展是一窍不通。后来通过该文章知道了如何扩展iptables。几天的时间,就实现了多个iptables的扩展功能——包括用户态和内核态的代码。由此可见,
iptables的扩展架构多么友好。但是这里也有个小问题。这样的扩展方式,需要重新编译iptables的代码,生成新的iptable二进制文件。这与我们心目中理想的扩展还差了一小步,稍微有点难以接受。试想,现在的浏览器大都支持扩展插件,有哪个插件需要重新编译浏览器的?
我们C语言程序员也可以实现这样的功能!让我们学习一下tc的扩展吧。以tc中的filer为例。tc的用户(必须也是程序员呵),可以实现自己的filter的实现,并将生成的so库文件放置在tc的库目录下。那么该扩展功能既可以被tc支持。同时,还要编写一个tc的内核实现模块并加载。这样,新的功能,在不重新编译tc,不重启机器的情况下,就得以支持了。这样就很类似浏览器插件的功能了吧:)也是我们期待的结构。
下面看看tc是如何做到这点的,最直接的方法就是查看tc的代码。
查看函数tc_filter_modify的部分代码:
- strncpy(k, *argv, sizeof(k)-1);
- q = get_filter_kind(k);
- argc--; argv++;
接下来进入关键的get_filter_kind的函数:
- struct filter_util *get_filter_kind(const char *str)
- {
- void *dlh;
- char buf[256];
- struct filter_util *q;
/* 这里去遍历已知的filter列表,通过名字查找对应的filter类型 */
- for (q = filter_list; q; q = q->next)
- if (strcmp(q->id, str) == 0)
- return q;
/*
这部分代码是tc支持自定义扩展的关键代码
没有找到,那么就去tc目录下加载对应名字的动态库
*/
- snprintf(buf, sizeof(buf), "%s/f_%s.so", get_tc_lib(), str);
- dlh = dlopen(buf, RTLD_LAZY);
- if (dlh == NULL) {
- /*
- 如果打开该动态库失败,那么就直接去主程序中寻找。
- 这样的情况一般是对于tc自身已支持的filter类型。
- */
- dlh = BODY;
- if (dlh == NULL) {
- dlh = BODY = dlopen(NULL, RTLD_LAZY);
- if (dlh == NULL)
- goto noexist;
- }
- }
/*
打开动态库后,再根据filter的类型名找到对应的name_filter_util符号
该符号实际上即为tc的扩展接口,其为一个结构体,定义了filter的操作函数。
得到该符号后,tc的用户态部分就已经可以支持新的扩展功能了。
*/
- snprintf(buf, sizeof(buf), "%s_filter_util", str);
- q = dlsym(dlh, buf);
- if (q == NULL)
- goto noexist;
- reg:
- q->next = filter_list;
- filter_list = q;
- return q;
- noexist:
- q = malloc(sizeof(*q));
- if (q) {
- memset(q, 0, sizeof(*q));
- strncpy(q->id, str, 15);
- q->parse_fopt = parse_nofopt;
- q->print_fopt = print_nofopt;
- goto reg;
- }
- return q;
- }
对比iptables和tc的支持扩展的形式,无疑tc更胜一筹,因为无需重新编译iptables就可以支持新的扩展。这都是依赖于dlopen和dlsym来实现的,在我们自己的项目中,也可以采取同样的方式来支持用户自定义的扩展功能。