quagga代码分析.docx
- 文档编号:24564144
- 上传时间:2023-05-28
- 格式:DOCX
- 页数:23
- 大小:1.42MB
quagga代码分析.docx
《quagga代码分析.docx》由会员分享,可在线阅读,更多相关《quagga代码分析.docx(23页珍藏版)》请在冰豆网上搜索。
quagga代码分析
quagga代码解读
编辑:
王斌2009-6-8
修改:
1vtysh命令添加
vtysh是quagga与用户交互的接口软件,用户通过vtysh输入命令,vtysh可以将命令解析并通过socket发送给相应的后台进程进行对应处理,处理结果由后台进程通过socket发送回vtysh并显示在屏幕上。
Quagge的程序结构如下:
其中rip,ospf,interface…都是quagga后台的进程负责实现对应的功能。
其他还有zebra,bgp…
Vtysh接收到输入的命令后会通过命令对应的deamon字段(可以理解为一个进程的标志,标志该命令输入哪个进程)传送给对应进程处理,进程处理完后会自动返回一个结果给vtysh.
Vtysh运行后进入vtysh_main.c的主函数main中,经过一系列初始化后进入循环等待命令输入:
输入的命令会传入line_read的地址中,通过vtysh_execute()进行后续的解析和执行。
在vtysh_rl_gets()可以看到这样一段代码
会把输入的命令存入history中。
添加一个命令需要几部分的工作:
1找到该命令所在视图,若视图不存在则需要定义该视图
2定义声明该命令的宏,并将命令注册到对应视图下
3定义命令动作
下面我们慢慢介绍上面的过程。
1.1定义并引入node
代码中的node指vtysh的视图模式,所谓视图最直观的表现就是命令行前端显示出来的提示信息,如图:
vtysh开始的视图为dptech-desktop#,当输入configuret/n后视图变成了dptech-desktop(config)#,输入routerospf后视图变成了dptech-desktop(config-router)#.命令都是在视图下注册的,视图的作用是区分在本视图下哪些命令有效,哪些无效。
在vtysh.c中定义了node:
Cmd_node的结构体定义如下:
首先是node_type,是枚举类型,因此定义一个视图首先要在枚举中添加该视图。
Prompt字段即是进入该视图后命令行前端显示的提示字符。
一个node在定义后还需要引入到vtysh中
通过在vtysh.c中的vtysh_init_vty(void)函数(该函数在vthsh启动时由main()函数调用)中调用install_node()可以将node引入到vtysh。
其本质就是将node加入到一个包含所有node的数组中。
Node也同时需要在后台的其他对应进程中定义并引入,目的是为了视图同步。
因为后台运行的其他进程(如rip、ospf)都可以通过其他telnet客户端访问调用。
因此如果不同步则在同时两个客户端接入后台进程时会产生一些问题。
在vtysh_init_vty(void)函数中Node引入后还需要定义进入和退出node以及帮助的命令,并引入
View_node中添加了help命令config_help_cmd。
1.2添加command
需要用到Command.h中的三个宏定义:
从定义中我们能够看到区别,DEFUN和DEFUNSH两个宏中的FUN代表包含函数,它们除了由DEFUN_CMD_ELEMENT定义了一个结构体外都声明并引入了一个函数。
函数由宏DEFUN_CMD_FUNC_DECL声明(DECL表示declear),由DEFUN_CMD_FUNC_TEXT定义函数头。
通过宏所引入的函数可以完成命令对应操作。
DEFSH中只定义了一个结构体
还有以SH结尾的两个结构体DEFUNSH和DEFSH中都定义了一个daemon字段,该字段用于记录进程号,如
上图中的结构体表示对应在vtysh中输入exit这条命令会将该命令传送给VTYSH_INTERFACE这个进程处理,”Exitcurrentmodeanddowntopreviousmode\n”表示该宏的作用,vtysh_exit_interface为该宏所定义函数的函数名,vtysh_exit_interface_cmd为该命令的id(在引入到node中时使用该字段)。
Exit表示对应在vtysh中输入的command。
通过定义这三个个结构体中的一个便可以引入我们需要添加的命令行命令。
但是注意加入一条命令时需要分别在vtysh和其对应执行进程中使用对应宏定义该命令,因为进程在接收到由vtysh发送来的输入命令后也需要查找这些宏来进行相应处理。
通常后台进程中只定义DEFUN宏即可,因为一般不需要再通过daemon字段传送给其他进程处理。
而在vtysh中一般使用DEFUNSH和DEFSH,除非像help或者List这样不需要后台进程处理的命令使用DEFUN定义。
Command需要通过install_element()函数引入到对应的node下,如
引入时使用了前面所介绍宏定义中的vtysh_exit_interface_cmd字段(ospf_router_id_cmd便是对应宏中该字段的值)。
这样在之前提到的node数组中,每个节点的node都会对应一组命令,所有的命令和视图就是被放在一个“大表”中。
再次提示要同时在vtysh和对应进程中添加命令。
至此命令添加完毕。
1.3编译
为什么会单独提到编译这点,因为它比较重要而且很容易被忽略。
命令添加完毕后,在linux下进行编译安装时要注意在./configure后添加命令—enable-vtysh,否则安装后新添加的命令便无法体现在vtysh中。
详细的linux下安装方法请参考
“/VSS/学习资料/quagga资料/quagga在Linux下的安装方法.doc”
1.4添加“ipospfenable”命令行
该命令添加比较简单,因为作用是接口ospf使能,因此只需要在interfacenode中添加该命令并添加对应函数。
首先简单的不和ospf进行交互,使用DEFUN便可实现。
在ospf_vty.c中添加如下图代码。
vtyshell在输入”ip?
”后会显示可在ip后添加的命令及命令的说明,“enableipospfrouterinthisInterface\n”便是命令的说明,在命令“ipospfenable”后的第一对双引号中是对应ospf的说明,第二对双引号中是对应enable的说明,如果命令由更多words组成,则以此类推。
本例中只给出一个说明,因此输入命令“ipospf?
”后可以看到enable后没有命令说明。
注意在vtysh外其他进程中打调试命令可以使用vty_out()函数,fprintf()试了一下,好像打不出来。
添加该宏后在ospf_vty_if_init(void)函数中注册该命令:
然后在linux下编译安装,注意需要./configure–-enable-vtysh.还有就是因为修改ospf_vty.c中的文件,因此安装时候如果原来系统里已经安装过quagga请将libospfd.so.0重新由/usr/local/lib目录下拷入/usr/lib目录,否则命令无法运行。
说明libospfd.so.0可能由ospf_vty.c编译生成或者相关。
安装完毕后先运行zebra(因为interfacenode属于zebra进程),再运行vtysh.运行结果:
2Network使能过程
从ospf_vty.c中可以看到network使能命令的定义:
通过
VTY_GET_IPV4_PREFIX()
VTY_GET_OSPF_AREA_ID()
得到网络前缀(networkprefix)和区域ID(areaID)。
然后通过函数ospf_network_set()使能网段。
ospf_network_set()中首先调用了route_node_get()用来得到ospf结构中network表(用于记录network使能的地址段)中与输入地址段匹配的结点(node)。
如果该结点的info字段有值,说明该地址段已经被network使能过了,于是
/*Thereisalreadysamenetworkstatement.*/
route_unlock_node(rn);
return0;
返回。
否则在这个结点的info字段中添加该area,然后运行ospf_network_run()
ospf_network_run()中,首先判断routerid是否存在,若不存在则通过ospf_router_id_update()函数获得。
获得路由器ID的规则是:
1.StaticallyassignedrouterIDisalwaysthefirstchoice.
2.IfthereisnostaticallyassignedrouterID,thentrytostick
withthemostrecentvalue,sincechangingrouterID'sisverydisruptive.
3.Lastchoice:
justgowithwhateverthezebradaemonrecommends.
1如果有静态路由则优先使用静态路由作为路由器ID
2如果没有分配静态路由ID则使用最近使用的路由器ID
3如果以上都无法得到路由器ID,使用zebra中router_id_zebra指定的ID,router_id_zebra.该值在ospf进程启动时,在ospf_zebra_init()中得到一个初值。
看样子好像是zebra进程传过来的值,没有再继续跟。
可以看到宏ALL_LIST_ELEMENTS_RO(list,node,data)的定义:
#defineALL_LIST_ELEMENTS_RO(list,node,data)\
(node)=listhead(list);\
(node)!
=NULL&&((data)=listgetdata(node),1);\
(node)=listnextnode(node)
For循环的过程是从每个om->iflist上的node中取出om->iflist->node->data存入ifp(用于储存接口相关信息),由函数
ospf_network_run_interface(p,area,ifp);
调用。
该循环作用是遍历所有接口,使能与p匹配的地址段。
ospf_network_run_interface(p,area,ifp)函数主要部分为:
/*ifinterfaceprefixismatchspecifiedprefix,
thencreatesocketandjoinmulticastgroup.*/
for(ALL_LIST_ELEMENTS_RO(ifp->connected,cnode,co))//遍历该接口上所有连接的地址段
{
structprefix*addr;
if(CHECK_FLAG(co->flags,ZEBRA_IFA_SECONDARY))
continue;
addr=CONNECTED_ID(co);
/*如果符合输入地址段的接口地址并没有被network使能过,
*则创建一个ospf_interface结构体,付值后将该*/
if(p->family==co->address->family//为同种ip地址(ipv4或ipv6)
&&!
ospf_if_is_configured(area->ospf,&(addr->u.prefix4))//该地址段并没有被使能
&&ospf_network_match_iface(co,p))//ip地址段匹配
{
structospf_interface*oi;
oi=ospf_if_new(area->ospf,ifp,co->address);//在已经使能ospf(目前只能通过network使能)的interface中寻找地址段匹配的结点,找不到则新建结点
oi->connected=co;
oi->area=area;
oi->params=ospf_lookup_if_params(ifp,oi->address->u.prefix4);//从接口的匹配地址段结点上获得参数
oi->output_cost=ospf_if_get_output_cost(oi);
/*Addpseudoneighbor.*/
ospf_nbr_add_self(oi);//将自己作为邻居信息的一部分添加到oi->nbrs->node->info中,查找oi->nbrs->node的依据是接口地址段,找不到对应的地址段则添加一个新的。
/*Relateospfinterfacetoospfinstance.*/
oi->ospf=area->ospf;
/*updatenetworktypeasinterfaceflag*/
/*Ifnetworktypeisspecifiedpreviously,
skipnetworktypesetting.*/
oi->type=IF_DEF_PARAMS(ifp)->type;//从结点默认参数中获得类型
ospf_area_add_if(oi->area,oi);//将结点链在area->oiflist链表里
/*ifrouter_idisnotconfigured,dontbringup
*interfaces.
*ospf_router_id_update()willcallospf_if_update
*wheneverr-idisconfiguredinstead.
*/
if((area->ospf->router_id.s_addr!
=0)
&&if_is_operative(ifp))
ospf_if_up(oi);
}
}
3Nonetwork去使能
我们再看一下nonetwork去使能的过程,主要由以下函数完成:
ospf_network_unset(structospf*ospf,structprefix_ipv4*p,structin_addrarea_id)
该函数的函数体:
{
structroute_node*rn;
structospf_network*network;
structexternal_info*ei;
structlistnode*node,*nnode;
structospf_interface*oi;
rn=route_node_lookup(ospf->networks,(structprefix*)p);//在ospf->network中寻找于输入地址段匹配的结点
if(rn==NULL)
return0;
network=rn->info;
if(!
IPV4_ADDR_SAME(&area_id,&network->area_id))//若区域不匹配
return0;
ospf_network_free(ospf,rn->info);//若匹配,从相关表中去掉结点,后面会再介绍
rn->info=NULL;
route_unlock_node(rn);//在route_node_lookup()中lock,因此在此unlock
/*Findinterfacesthatnotconfiguredalready.*/
for(ALL_LIST_ELEMENTS(ospf->oiflist,node,nnode,oi))//遍历所有ospf过的接口(ospf->oiflist上的结点)
{
intfound=0;
structconnected*co=oi->connected;//获得该接口上地址
if(oi->type==OSPF_IFTYPE_VIRTUALLINK)
continue;
for(rn=route_top(ospf->networks);rn;rn=route_next(rn))//遍历所有network上的结点
{
if(rn->info==NULL)
continue;
if(ospf_network_match_iface(co,&rn->p))//判断该接口上地址与network结点地址段是否匹配,若有匹配则found置1,说明该结点上还有被network使能的地址段
{
found=1;
route_unlock_node(rn);//route_top(),route_next()中会lock(rn)
break;
}
}
if(found==0)
ospf_if_free(oi);//found==0说明该接口上没有network使能过的地址段,将该结点从相关链表中去掉
}
/*Updateconnectedredistribute.*/
if(ospf_is_type_redistributed(ZEBRA_ROUTE_CONNECT))
if(EXTERNAL_INFO(ZEBRA_ROUTE_CONNECT))
for(rn=route_top(EXTERNAL_INFO(ZEBRA_ROUTE_CONNECT));
rn;rn=route_next(rn))
if((ei=rn->info)!
=NULL)
if(!
ospf_external_info_find_lsa(ospf,&ei->p))
if(ospf_distribute_check_connected(ospf,ei))
ospf_external_lsa_originate(ospf,ei);
return1;
}
前面调用的函数ospf_network_free():
void
ospf_network_free(structospf*ospf,structospf_network*network)
{
ospf_area_check_free(ospf,network->area_id);//察看输入的area下还有没有活动的接口,如果没有将该area从ospf->area中去掉。
这里有个问题,运行到这里时候符合ip地址段和area_id的ospf_interface结构体到这里并没有从area的链上面去掉,而是在后面的循环中由ospf_if_free()函数去掉。
因此本次空链的area在下次运行nonetwork时才会被free。
如果只一次network和一次nonetwork时候可能会有未释放的指针。
ospf_schedule_abr_task(ospf);
XFREE(MTYPE_OSPF_NETWORK,network);//free开始XMALLOC()分配的指针
}
4Ipospfcost命令实现过程
Ospf_init()中会创建一个interface的list,在每个接口的结构体中都有个info的字段,其定义如下:
structospf_if_info
{
structospf_if_params*def_params;
structroute_table*params;
structroute_table*oifs;
unsignedintmembership_counts[MEMBER_MAX];/*multicastgrouprefcnts*/
};
然后通过ospf_if_new_hook()初始化对应的指针:
IF_OIFS(ifp)=route_table_init();//ifp->info->oifsoifs链表保存已经ospf使能的interface
IF_OIFS_PARAMS(ifp)=route_table_init();//ifp->info->params参数链表,是一个routetable二叉树papams->info也是个ospf_if_params结构。
之所以设置该字段的原因是因为每个接口除了有个默认的参数结构以外还需要对于每个接口上配置的各个主次ip地址段分别对应一个params,这些params以ip段为关键字存在这个routetable二叉树里面。
IF_DEF_PARAMS(ifp)=ospf_new_if_params();//ifp->info->def_params是一个ospf_if_params结构,保存已经定义的一些参数及其默认值
再通过ospf_if_delete_hook()清空参数。
Ipospfcost<0-65535>a,b,c,d的命令执行时,初始化params指针指向默认参数表。
params=IF_DEF_PARAMS(ifp);
如果输入了a,b,c,d:
params=ospf_get_if_params(ifp,addr);//按照地址a,b,c,d获得params表里面ifp->info->params->node*->info(这个就是结点的params),找到了就unlocknode找不到就添加一个node.Node如果unlock到0便从表中删除该node
ospf_if_update_params(ifp,addr);//按照地址获得oifs表里面的ifp->info->oifs->node*->info->params,把params表里面对应这个地址的参数写入oifs里的这个params字段中,红色部分是一个ospf_interface的指针
*注:
node代表链表中的某个结点,是通过top结点查下去找到的
这两个函数中分别调用了
route_node_get(structroute_table*table,structprefix*p)
route_node_lookup(structroute_table*table,structprefix*p)
都是用于遍历通过参数传进去的table找到地址字段为p的结点并返回,区别是get函数找不到遍会创建结点,lookup函数只寻找。
ospf_if_recalculate_output_cost()函数遍历ospf过的interface,修改他们的output_cost字段:
newcost=ospf_if_get_output_cost(oi);//计算得到新的cost
if(oi->output_cost!
=newco
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- quagga 代码 分析