quagga源码分析--大内总管zebra

zebra,中文翻译是斑马,于是我打开了宋冬野的《斑马,斑马》作为BGM来完成这个篇章,嘿嘿,小资一把!

zebra姑且戏称它是quagga项目的大内总管。

因为它负责管理其他所有协议进程的路由信息的更新与交互,并负责与内核交换信息,如下的架构:

1 +----+  +----+  +-----+  +-----+
2 |bgpd|  |ripd|  |ospfd|  |zebra|
3 +----+  +----+  +-----+  +-----+
4                             |
5 +---------------------------|--+
6 |                           v  |
7 |  UNIX Kernel  routing table  |
8 |                              |
9 +------------------------------+

好了,简介完了,开始看代码吧:

1、zebra作为其他协议进程的服务端:

1 /* Make zebra server socket, wiping any existing one (see bug #403). */
2 void
3 zebra_zserv_socket_init(char *path) {
4 #ifdef HAVE_TCP_ZEBRA
5     zebra_serv();
6 #else
7     zebra_serv_un(path ? path : ZEBRA_SERV_PATH);
8 #endif /* HAVE_TCP_ZEBRA */
9 }

zebra绑定了(loopback,2600)的地址和端口,并开始监听socket,同时加入到thread事件ZEBRA_SERV当中,来接收客户端发送过来的路由信息:

 1 accept_sock = socket(AF_INET, SOCK_STREAM, 0);
 2
 3 addr.sin_family = AF_INET;
 4
 5 addr.sin_port = htons(ZEBRA_PORT);
 6 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
 7     addr.sin_len = sizeof(struct sockaddr_in);
 8 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
 9 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
10
11 ret  = bind(accept_sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
12
13 ret = listen(accept_sock, 1);

zebra_event(ZEBRA_SERV, accept_sock, NULL);

2、客户端(比如isis协议):

2.1 创建客户端,并在初始化加入到thread事件调度当中去。

1 void
2 isis_zebra_init(struct thread_master *master) {
3     zclient = zclient_new(master);
4     zclient_init(zclient, ZEBRA_ROUTE_ISIS);
5
6     ......
7
8     return;
9 }
 1 void
 2 zclient_init (struct zclient *zclient, int redist_default)
 3 {
 4   int i;
 5   /* Enable zebra client connection by default. */
 6   zclient->enable = 1;
 7   /* Set -1 to the default socket value. */
 8   zclient->sock = -1;
 9   .....
10   zclient_event (ZCLIENT_SCHEDULE, zclient);
11 }  
 1 static void
 2 zclient_event(enum event event, struct zclient *zclient) {
 3     switch (event) {
 4     case ZCLIENT_SCHEDULE:
 5         if (!zclient->t_connect) zclient->t_connect =
 6                 thread_add_event(zclient->master, zclient_connect, zclient, 0);
 7         break;
 8         ......
 9     }
10 }

2.2 在zclient_connect里调用zclient_socket完成客户端sock的初始化:

1 int zclient_socket_connect(struct zclient *zclient) {
2 #ifdef HAVE_TCP_ZEBRA
3     zclient->sock = zclient_socket();
4 #else
5     zclient->sock = zclient_socket_un(zclient_serv_path_get());
6 #endif
7     return zclient->sock;
8 }
 1 static int
 2 zclient_socket(void) {
 3     int sock;
 4     int ret;
 5     struct sockaddr_in serv;
 6
 7     /* We should think about IPv6 connection. */
 8     sock = socket(AF_INET, SOCK_STREAM, 0);
 9     if (sock < 0) return -1;
10
11     /* Make server socket. */
12     memset(&serv, 0, sizeof(struct sockaddr_in));
13     serv.sin_family = AF_INET;
14     serv.sin_port = htons(ZEBRA_PORT);
15 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
16     serv.sin_len = sizeof(struct sockaddr_in);
17 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
18     serv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
19
20     /* Connect to zebra. */
21     ret = connect(sock, (struct sockaddr *)&serv, sizeof(serv));
22     if (ret < 0) {
23         close(sock);
24         return -1;
25     }
26     return sock;
27 }

嗯,服务端和客户端就这样完成了tcp通信连接,其他的客户端(如bgpd,ospfd等等)都是调用zclient_socket完成连接,代码复用了哦!

3、下面来看看作为大内总管,zebra如何处理日常事务:

首先是在thread的调度中,增加了read事件(可以看到整个系统,都是由thread模块在在暗中维持的运转)

1 thread_add_read(zebrad.master, zebra_client_read, client, sock);
 1 /* Handler of zebra service request. */
 2 static int
 3 zebra_client_read(struct thread *thread) {
 4     ......
 5     command = stream_getw(client->ibuf);
 6
 7     .....
 8
 9     switch (command) {
10     .....
11
12     case ZEBRA_IPV4_ROUTE_ADD:
13         zread_ipv4_add(client, length, vrf_id);
14         break;
15     case ZEBRA_IPV4_ROUTE_DELETE:
16         zread_ipv4_delete(client, length, vrf_id);
17         break;
18     ......
19     default:
20         zlog_info("Zebra received unknown command %d", command);
21         break;
22     }
23
24     ......
25
26     zebra_event(ZEBRA_READ, sock, client);
27     return 0;
28 }

如上述代码,从消息内容中读取到对应事件号,比如ZEBRA_IPV4_ROUTE_ADD,ZEBRA_IPV4_ROUTE_DELETE,即是增加ipv4路由和删除ipv4路由。

4、再来看看大内总管(zebra)如何与皇上(内核)交互的:

在main里对这个过程做了初始化,函数是rib_init。

1 /* Routing information base initialize. */
2 void
3 rib_init(void)
4 {
5     rib_queue_init(&zebrad);
6 }
7
8     /* fill in the work queue spec */
9     zebra->ribq->spec.workfunc = &meta_queue_process;

上面代码创建一个工作队列,作为thread调度模块的一个低等级的后台调度(THREAD_BACKGROUND)执行的任务。在meta_queue_process函数里处理各个子队列:

 1 /* Dispatch the meta queue by picking, processing and unlocking the next RN from
 2  * a non-empty sub-queue with lowest priority. wq is equal to zebra->ribq and data
 3  * is pointed to the meta queue structure.
 4  */
 5 static wq_item_status
 6 meta_queue_process(struct work_queue *dummy, void *data)
 7 {
 8     struct meta_queue *mq = data;
 9     unsigned i;
10
11     for (i = 0; i < MQ_SIZE; i++) if (process_subq(mq->subq[i], i))
12         {
13             mq->size--;
14             break;
15         }
16     return mq->size ? WQ_REQUEUE : WQ_SUCCESS;
17 }

process_subq函数里调用rib_process函数,即开始了对路由信息的处理,整个内核的路由的新旧比较与更新以及它的数据结构内容比较多(尴尬,发现布局不太合理了。),后面单独写一篇,这里就简单看一下流程:

 1 int
 2 kernel_route_rib (struct prefix *p, struct rib *old, struct rib *new)
 3 {
 4   int route = 0;
 5
 6   if (zserv_privs.change(ZPRIVS_RAISE))
 7     zlog (NULL, LOG_ERR, "Can‘t raise privileges");
 8
 9   if (old)
10     route |= kernel_rtm (RTM_DELETE, p, old);
11
12   if (new)
13     route |= kernel_rtm (RTM_ADD, p, new);
14
15   if (zserv_privs.change(ZPRIVS_LOWER))
16     zlog (NULL, LOG_ERR, "Can‘t lower privileges");
17
18   return route;
19 }

可以看到,最后使用netlink通信来更新内核的路由信息。

时间: 2024-10-29 19:08:45

quagga源码分析--大内总管zebra的相关文章

quagga源码分析--通用库command

quagga是一个完整又成熟的系统,作为一个路由器软件,自然要提供人机接口. quagga提供snmp管理接口,而且,自然就会有对应的命令行管理格式,当然一般路由软件不会提供界面形式的,也许有webui,然而quagga并没有. 我们要看的就是这个命令行处理的代码 command. 接触过类似命令行的朋友肯定有一点点好奇吧,那么数量庞大的命令和参数输入,还可以提供提示和自动补齐,这肯定不是一件很简单的事情. 下面是一个配置示例: 1 ! 2 interface bge0 3 ip ospf au

quagga源码分析--通用库thread

quagga是开源路由器软件,提供的用户界面与思科,华为的路由器的人机接口几乎一致,非常有学习价值,尤其是开源的协议代码,简直亮瞎了我丹的小眼睛. quagga的介绍,我就不赘述了,有兴趣的可以找度娘或者去官网看看. 一.通用库thread quagga是一个纯C实现的项目. C语言的项目,就是一个个C,H文件组成,当C文件很多的时候,相信一开始每个开源爱好者都会感到很头疼吧,完全不知道这么多文件要怎么组织,怎么去阅读吧? 哈,其实呢,quagga的C文件就像一个个散落在地上的珍珠,而threa

quagga源码分析--内核通信netlink

Linux操作系统中当CPU处于内核状态时,可以分为有用户上下文的状态和执行硬件.软件中断两种.其中当处于有用户上下文时,由于内核态和用户态的内 存映射机制不同,不可直接将本地变量传给用户态的内存区:处于硬件.软件中断时,无法直接向用户内存区传递数据,代码执行不可中断.针对传统的进程间通信 机制,他们均无法直接在内核态和用户态之间使用,原因如下表:  通信方法 无法介于内核态与用户态的原因 管道(不包括命名管道) 局限于父子进程间的通信. 消息队列 在硬.软中断中无法无阻塞地接收数据. 信号量

插件开发之360 DroidPlugin源码分析(五)Service预注册占坑

请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52264977 在了解系统的activity,service,broadcastReceiver的启动过程后,今天将分析下360 DroidPlugin是如何预注册占坑的?本篇文章主要分析Service预注册占坑,Service占了坑后又是什么时候开始瞒天过海欺骗AMS的?先看下Agenda: AndroidMainfest.xml中概览 Service中关键方法被h

【精】EOS智能合约:system系统合约源码分析

系统合约在链启动阶段就会被部署,是因为系统合约赋予了EOS链资源.命名拍卖.基础数据准备.生产者信息.投票等能力.本篇文章将会从源码角度详细研究system合约. 关键字:EOS,eosio.system,智能合约,name类型,native.hpp,newaccount,bidname,core token init,onblock,更新已入选生产节点 eosio.system 概览 笔者使用的IDE是VScode,首先来看eosio.system的源码结构.如下图所示. 本文分析的源码来自于

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线