您的位置:首页 > 其它

MIPL程序解读(1)

2008-10-04 09:38 267 查看
这是一年前研究移动ipv6时候写的一个文档。贴出来共享。

1、总体说明

以MIPL项目组的mipv6-2.0.2版本为样本,进行程序分析。MIPL项目组的MIPv6的实现分成两部分,一部分是对内核的修改,对内核打了一个长长的补丁;另一部分是用户空间的一个守护进程。这一部分仅仅是MIPv6的用户空间部分的代码,内核空间的代码另外的文档进行解读。其结构可以用下图来表示(摘录自NEPL (NEMO Platform for Linux) HOWTO.htm中的第一个图),这个NEMO的结构,和MIPL的结构其实是一样的。





在这个图中,可以看到内核和用户空间的接口。程序对于内核的修改,主要是为了给应用层提供接口。比如用户用于tunnel management的ioctl,用于icmp6的raw socket和用于mobility header的raw socket接口。要提供这些接口,就必须对内核进行一部分修改。内核的补丁就是做这个工作。
nemod(MIPL程序也一样)则是应用这些接口,将数据提取到用户空间的程序中,然后进行处理。处理结果再通过ioctl、xfrm或者RT Netlink等接口修改内核的一些行为。
本文档中,对于程序的注释,短的注释直接在程序语句的后面,长的注释放在程序语句的下一行。
看这个程序的时候,请务必参照MIPv6的有关协议来看,对照RFC的规定。不然很多地方就看不懂。
程序中变量和函数的命名有很多简写,要注意其含义。比较不好懂的含义,我基本上都在这篇文档中写出来了。还有一些比较常见的,比如CoTI之类的,就没有一一写出来。随便google一下,或者讲解MIPv6的书籍中,都会讲。理解这些简写的含义,对于理解程序的作用非常有帮助。
从我觉得,由于移动头部的处理和icmpv6的处理都在用户空间程序中,因此基本上可以不改动内核的代码,只改动这个程序的代码,就应该能够实现MIPv6的功能修改。

2、主函数注释

主函数在main.c中,从这里开始程序的分析。
int main(int argc, char **argv)
{
pthread_t sigth;
sigset_t sigblock;
// 声明一个信号集,关于unix信号,具体可以参见《unix环境高级编程》
int logflags = 0;
int ret = 1;
sdbg = stderr;
sigemptyset(&sigblock); //将信号集清空
sigaddset(&sigblock, SIGHUP); //添加信号集中的信号,SIGHUP是连接断开信号
sigaddset(&sigblock, SIGINT); //SIGINT是终端中断符信号
sigaddset(&sigblock, SIGTERM); //终止信号
#ifdef ENABLE_VT
sigaddset(&sigblock, SIGPIPE); //SIGPIPE是写至无读进程的管道信号,这些信号
//缺省的动作都是终止进程
#endif
pthread_sigmask(SIG_BLOCK, &sigblock, NULL);
//设置线程的私有信号掩码,当一个线程创建的时候,它会继承创造它的线程的信号掩码,
//具体可以参见《POSIX多线程程序设计》中第六章对信号的说明
if (conf_parse(&conf, argc, argv))
return 1;
//这个函数对用户从shell命令行传递过来的参数进行解析,然后存放在一个mip6_config
//结构体中,这个结构体保存着很多跟MIPv6性能紧密相关的一些参数。用户可以根据
//命令行中的参数,来调整这些MIPv6参数。
//注意一点,就是在这个函数中,会用conf_default©对mip6_config结构体进行初始化设
//置,这些缺省的参数,是针对一跳的网络,有些值太小,可能不适合多跳的无线自组网,
//后面还会对这个conf_parse()函数以及mip6_config结构做进一步分析说明
if (conf.debug_level > 0)
logflags = LOG_PERROR;
openlog(basename(argv[0]), LOG_PID|logflags, LOG_DAEMON);
//basename命令读取String参数,删除以 /(斜杠) 结尾的前缀以及任何指定的Suffix
//参数,并将剩余的基本文件名称写至标准输出。简单一点说,就是从一个包含目录的
//文件名中,去除目录名,仅仅提出文件名
//openlog函数会打开操作系统的纪录机制 (logger),由于daemon程序和控制台脱离,程//序的运行状态和打印信息一般只能记录在日志文件中。
syslog(LOG_INFO, “%s v%s started (%s)”, PACKAGE_NAME, PACKAGE_VERSION,
entity_string[conf.mip6_entity]);
//entity_string是一个数组,用来表示节点的身份,是漫游节点,还是对端节点,还是家
//乡代理。但问题是好像不能同时将节点设置为对端节点和移动节点。而实际上,一个节
//点极有可能即是对端节点,又是移动节点,而且在自组网中还可能是家乡代理。
//在conf.h中定义了下面几种类型的节点。
//#define MIP6_ENTITY_NO -1
//#define MIP6_ENTITY_CN 0 对端节点
//#define MIP6_ENTITY_MN 1 移动节点
//#define MIP6_ENTITY_HA 2 家乡代理
#ifdef ENABLE_VT
if (vt_init() < 0)
//这个函数是程序运行的时候,用来接收控制台的命令,并向控制台提供程序运行的状态
//信息。就是通过telnet 本机的一个端口号,然后就能够看到程序的状态。在nemo和
//MIPL程序中,都是使用7777的端口号。使用的命令是telnet localhost 7777。在文件vt.h
//中定义。定义语句是
//#define VT_DEFAULT_HOSTNAME "localhost"
//#define VT_DEFAULT_SERVICE "7777"
//vt也有一个线程,专门负责解析telnet命令,然后显示相应的信息,配置程序的参
//数。在vt_start()函数里面启动这个线程。但这个仅仅是一个跟用户交互控制的进程,
//如果取消控制台,也不会影响程序的运行。所以可以放在最后进行分析。
goto vt_failed;
#endif
/* if not debugging, detach from tty */
if (conf.debug_level == 0)
daemon_start(1);
//这个函数将程序转成daemon状态,协议程序在运行的时候一般是daemon状态,以便
//和控制台分离。但调试的时候,一般不用daemon,以便调试信息能够方便的在屏幕显
//示
else {
dbg(“%s started in debug mode, not detaching from terminal/n”,
PACKAGE_NAME);
conf_show(&conf); //显示目前的程序参数配置情况
}

srandom(time(NULL)); //用时间来随机化随机数种子
if (rr_cn_init() < 0)
goto rr_cn_failed;
//MIPv6中规定了用加密来进行MN与HA之间协议消息保护,加密一般要生成加密令//牌(keygen token),一般需要使用家乡地址及两个随机数Kcn与nonce。rr_cn_init()函数
//就是对加密中使用的变量结构进行初始化。
if (policy_init() < 0)
goto policy_failed;
//policy_init主要是初始化acl的绑定,ACL即访问控制列表,用于对数据包进行安全过
//滤以及其它用于流分类的应用
//
if (taskqueue_init() < 0)
goto taskqueue_failed;
//
//这个函数作用是创建一个runner线程,runner()函数在tqueue.c文件中
//runner是从任务队列tq_list中,执行一个一个的任务。当任务队列为空的时候,这个线
//程阻塞在pthread_cond_wait()中,等待条件变量 cond。当其他线程往任务队列添加任务
//的时候或者其它某些条件发生时,将用pthread_cond_signal(&cond)唤醒条件变量。
//每一个任务队列都有一个对应的时间,表示任务队列应该在什么时候执行。任务队列按
//照时间的先后顺序排列,最前面的队列表示最先执行的任务。
//runner线程是一个死循环的线程,会一直守候任务队列,一直运行。
if (bcache_init() < 0)
goto bcache_failed;
//初始化绑定缓存,绑定缓存是一个hash表
if (mh_init() < 0)
goto mh_failed;
//mh是指mobile header,移动头部。
//这部分的初始化,是初始化一个ipv6的socket,socket的选项可以在
//http://www.cebitec.uni-bielefeld.de/cgi-bin/man.cgi?section=7P&topic=ip6查到。
//然后会生成一个新的线程mh_listen。这个线程也是死循环线程,一直运行。
//mh_listen线程的作用是通过一个ipv6的socket接口,将包含mobile header的报文
//接收上来,然后把它由handlers[]数组中注册的函数进行处理。就是处理移动头部。
//handlers[]数组中关于mobile header处理函数的注册在mh_handler_reg ()函数中,分布在
//多个文件中进行,可以用source insight查看源代码,不一一例举。
//关于MIPv6的扩展socket api接口,可以参看文档
//http://arcknowledge.com/ietf.mobileip/2003-02/msg00342.html
//Extension to Sockets API for Mobile IPv6,
//<draft-chakrabarti-mobileip-mipext-advapi-00.txt>
//注意,在MIPL实现中,好像把移动头部做为一个单独的报文,跟icmpv6的头部一样,
//后面没有再跟用户应用层的数据。虽然协议中规定移动头部后面可以跟用户数据。如果
//后面跟了用户应用层数据,按照MIPL的实现方案就可能有问题,因为由MIPL程序接
//收完数据处理完毕以后,还会不会返回给内核,转交给对应的应用程序去处理呢?这点
//还不清楚。
//在邮件列表中,Samita Chakrabarti写下了下面这句:
//“Icmpv6 is different in that it has no "next" header field, Mobility is more like Destination
//options since it's not restricted to being a final header”
if (icmp6_init() < 0)
goto icmp6_failed;
//icmpv6报文的初始化,这个初始化和mh_init()初始化非常类似,就不再进行说明了。

if (xfrm_init() < 0)
goto xfrm_failed;
//xfrm是一个库(或者是linux网络框架),linux 2.6版本用它实现ipsec的一些功能。
//从我的理解,它象是Linux 2.4版本中的netfilter架构,其中有一些hook点,用来对数
//据报文进行检测和过滤。
//这部分是对网络安全方面的,可以尽量不做修改。
//xfrm_init的初始化的作用目前我还不是很清楚,关键对于ipsec的一些策略不清除。
//需要进一步学习和查资料,目前不做过多评述。目前xfrm非常少,连英文都很少,更
//别说中文了。主要可以参照 rfc2401和源代码。
//ipsec主要由rfc 2401,2402,2406和2409构成,可以参见《移动IP技术》一书的说明。
cn_init();
//注册handlers[]数组中的处理函数
if ((is_ha() || is_mn()) && tunnelctl_init() < 0)
goto tunnelctl_failed;
//主要是tunnelctl_init()函数,这个函数其实比较简单,就是创建一个socket。通过这个
//socket来控制tunnel
if (is_ha() && ha_init() < 0)
goto ha_failed;
//家乡代理的初始化
if (is_mn() && mn_init() < 0)
goto mn_failed;
//移动节点的初始化
#ifdef ENABLE_VT
if (vt_start(conf.vt_hostname, conf.vt_service) < 0)
//启动线程vt_server_recv()。
goto vt_start_failed;
#endif
if (pthread_create(&sigth, NULL, sigh, NULL))
goto sigth_failed;
//创建线程sigth,sigth是信号处理函数,当收到特定信号的时候,就会重新启动或者结
//束线程。(这个线程结束了,主线程将往下面执行,整个进程也就结束了。)
pthread_join(sigth, NULL);
//注意pthread_join这个函数,这个函数在主线程中,它会将主线程阻塞,直到sigth线程
//结束才继续往下面执行。由于在sigth线程中有for( ;;)死循环,所以程序会一直等待
//事件,而不会退出
ret = 0;
//正常时候的返回。
//后面这些是当出错的时候,进行的系统清理。可以不管。
sigth_failed:
#ifdef ENABLE_VT
vt_fini(); vt_start_failed:
#endif
if (is_mn())
mn_cleanup();
mn_failed:
if (is_ha())
ha_cleanup();
ha_failed:
if (is_ha() || is_mn())
tunnelctl_cleanup();
tunnelctl_failed:
cn_cleanup(); xfrm_cleanup();
xfrm_failed:
icmp6_cleanup(); icmp6_failed:
mh_cleanup(); mh_failed:
bcache_cleanup(); bcache_failed:
taskqueue_destroy(); taskqueue_failed:
policy_cleanup(); policy_failed: rr_cn_failed:
#ifdef ENABLE_VT
vt_failed:
#endif
syslog(LOG_INFO, “%s v%s stopped (%s)”, PACKAGE_NAME, PACKAGE_VERSION,
entity_string[conf.mip6_entity]); closelog(); return ret;
} //整个主函数结束

main函数分析综述:

函数除了主线程以外,一共还有五个线程,分别是runner,mh_listen,icmp6_listen和sigth,还有一个就是vt_server_recv()。由于vt_server_recv()只是控制显示进程,不影响程序的运行逻辑,所以暂时只分析前面四个线程。这四个线程都是死循环的线程,阻塞在一定的位置,等待的条件的满足。一旦满足某种条件(某个事件触发),就会调用处理函数进行事件处理,处理完以后再继续阻塞等待。

后面最重要的是分析这四个线程分别处理的事件。尤其是前面三个:runner、mh_listen和icmp6_listen涉及的事件。其中最复杂的又是runner函数。只要分析清楚他们各自的功能,整个程序的框架就理清了。
整个程序的框架如下(没有例出vt_server_recv线程)。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: