zebra-vtysh 简要分析
一、Zebra-vtysh 简介
Quagga 是一个路由软件包,提供基于 TCP/IP 路由服务,支持 RIPv1, RIPv2, RIPng,
OSPFv2, OSPFv3, BGP- 4, 和 BGP-4+等众多路由协议。Quagga 还支持 BGP 特性路由
反射器(Route Reflector)。除了传统的 IPv4 路由协议,Quagga 也支持 IPv6 路由协议。
如果运行的 SNMP 守护进程(需要 ucd-snmp)支持 SMUX 协 议,Quagga 还能支持路由
协议 MIBs。Zebra 是集成在 Quagga 中的一个工具包,负责对这许多的路由协议守护进程
的管理(只对这些路由协议的守护进程进行配置和查询)。Zerbra 还负责内核路由表的更新、
接口的查找以及在不同的路由协议之间完成路由条目的统一重建。
Vtysh 是 Quagga 中集成的一个 shell 子系统。提供类似与 Cisco 的一套命令行机制供
customer 使用。
二、Zebra-vtysh 框架简述
Zebra-vtysh 有很多视图(view),每个视图下有很多命令(command),其结构如下图所示:
图一 Zebra-vtysh 逻辑架构图
一个 node 就对应着一个视图(View)。常用的视图包括:普通视图,管理视图,文件系统视
图,配置视图,以及接口配置视图和 VLAN 视图等。
Zebra-vtysh 代码框架如下图所示:
图二 Zebra-vtysh 软件框架图
如上图所示,在 Zebra 中,总共有五个路由守护进程,和一个管理进程。这些路由进程
可以和管理进程分布在不同的机器上,每一个进程可以分别监听从不同的端口来的 VTY 连
接。同时,可以在一个远程主机上通过 telnet 来连接 zebra
三、Zebra-vtysh 代码分析
关键数据结构定义:
Zebra-vtysh 中需要用到以下的数据结构:
struct _vector
{
unsigned int active;
unsigned int alloced;
void **index;
/* number of active slots */
/* number of allocated slot */
/* index to data */
};
struct cmd_node
{
/* Node index. */
enum node_type node;
/* Prompt character at vty interface. */
const char *prompt;
/* Is this node's configuration goes to vtysh ? */
int vtysh;
/* Node's configuration write function */
int (*func) (struct vty *);
/* Vector of this node's command list. */
vector cmd_vector;
};
/* Structure of command element. */
struct cmd_element
{
/* Command specification by string. */
const char *string;
int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
const char *doc;
int daemon;
vector strvec;
unsigned int cmdsize;
char *config;
vector subconfig;
u_char attr;
/* Documentation of this command. */
/* Daemon to which this command belong. */
/* Pointing out each description vector. */
/* Command index count. */
/* Configuration string */
/* Sub configuration string */
/* Command attributes */
};
查看一下 element 的定义:
DEFUN (no_service_password_encrypt,
no_service_password_encrypt_cmd,
"no service password-encryption",
NO_STR
"Set up miscellaneous service\n"
"Enable encrypted passwords\n")
{
}
if (! host.encrypt)
return CMD_SUCCESS;
host.encrypt = 0;
if (host.password_encrypt)
XFREE (MTYPE_HOST, host.password_encrypt);
host.password_encrypt = NULL;
if (host.enable_encrypt)
XFREE (MTYPE_HOST, host.enable_encrypt);
host.enable_encrypt = NULL;
return CMD_SUCCESS;
咋一看,不知所谓!其实这个 element 是通过宏 DEFUN 来定义的。DEFUN 宏以及相关的
调用的宏定义如下:
#define DEFUN_CMD_FUNC_DECL(funcname) \
static int funcname (struct cmd_element *, struct vty *, int, const
char *[]);
#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs,
dnum) \
struct cmd_element cmdname = \
{ \
.string = cmdstr, \
.func = funcname, \
.doc = helpstr, \
.attr = attrs, \
.daemon = dnum, \
};
#define DEFUN_CMD_FUNC_TEXT(funcname) \
static int funcname \
(struct cmd_element *self __attribute__ ((unused)), \
struct vty *vty __attribute__ ((unused)), \
int argc __attribute__ ((unused)), \
const char *argv[] __attribute__ ((unused)) )
#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
DEFUN_CMD_FUNC_DECL(funcname) \
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
DEFUN_CMD_FUNC_TEXT(funcname)
第一个funcname是函数的名称,第二个是注册的命令的名字,第三个是在vtysh终端下输
入的命令字符串,第四个是帮助信息,当输入“?”时,显示出来。
假设我们这里有一个下面宏定义:
DEFUN (vtysh_show_hello,
vtysh_show_hello_cmd,
"show hello",
" hello1\n"
" hello2\n"
)
{
printf("hello\n");
return CMD_SUCCESS;
}
看一下它是如何展开的:
DEFUN_CMD_FUNC_DECL(funcname)是声明一个函数指针 funcname,在示例中就可以
展开为:
int vtysh_show_hello(struct cmd_element *, struct vty *, int, const
char *[]);
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)是定义一
个结构体,在示例中就可以展开为:
struct cmd_element vtysh_show_hello_cmd = \
{ \
.string
.func
.doc
.attr
.daemon
= “show hello”, \
= vtysh_show_hello, \
= " hello1\n" " hello2\n", \
= 0, \
= 0, \
};
所以宏定义 DEFUN 可以展开信息如下:
int vtysh_show_hello(struct cmd_element *, struct vty *, int, const
char *[]);
struct cmd_element vtysh_show_hello_cmd = \
{ \
.string
.func
= “show hello”, \
= vtysh_show_hello, \
= " hello1\n" " hello2\n", \
.doc
.attr
= 0, \
.daemon = 0, \
};
static int vtysh_show_hello
(struct cmd_element *self __attribute__ ((unused)), \
struct vty *vty __attribute__ ((unused)), \
int argc __attribute__ ((unused)), \
const char *argv[] __attribute__ ((unused)) )
{
printf("hello\n");
return CMD_SUCCESS;
}
至此,定义一个 element 的流程已经完全清楚了。
构建一整张视图需要经过如下操作:
vector_init();
install_node();
install_element();
//创建视图的根节点
// 安装 node
//安装 command
代码流程简析:
Zebra 线程机制概述
Zebra 这个软件包整体结构大致可分为两大块: 协议模块和守护进程模块。协议模块
实现各个协议的功能,各个协议以子模块的形式加载到 zebra 中;守护进程模块的功能主要
是管理各个协议的信令传输、表项操作、系统操作调用等事务,为各个协议提供底层信息以
及相关的硬件处理等功能支持。Zebra 与各个协议的交互采用的是 C-S 模式,在每个协议
子模块中均有一个 Client 端与守护进程模块中的 Server 端交互,它们所使用的 socket 为
zebra 内部使用的 socket,不与外部交互。
ZEBRA 中的 thread 其实并不是 Linux 中的线程,因为它只是对线程的应用层模拟。ZEBRA
借助自己 的 thread 结构,将所有的事件(比如文件描述的读写事件,定时事件等)和对应
的处理函数封装起来,并取名为 struct thread。然后这些 threads 又被装入不同的“线程“链
表挂载到名为 thread_master 的结构中,这样所有的操作只需要面向 thead_master。
/* Thread itself. */
struct thread
{
/* thread type */
unsigned char type;
struct thread *next;
struct thread *prev;
struct thread_master *master;
int (*func) (struct thread *); /* event function */
void *arg;
union {
/* event argument */
/* next pointer of the thread */
/* previous pointer of the thread */
/* pointer to the struct thread_master. */
int val;
int fd;
struct timeval sands;
/* second argument of the event. */
/* file descriptor in case of read/write. */
/* rest of time sands value. */
} u;
RUSAGE_T ru;
/* Indepth usage info. */
};
/* Linked list of thread. */
struct thread_list
{
struct thread *head;
struct thread *tail;
int count;
};
/* Master of the theads. */
struct thread_master
{
struct thread_list read;
struct thread_list write;
struct thread_list timer;
struct thread_list event;
struct thread_list ready;
struct thread_list unuse;
fd_set readfd;
fd_set writefd;
fd_set exceptfd;
unsigned long alloc;
};
zebra 中的线程是分队列调度的,每个队列以一个链表的方式实现。线程队列可以分成
六个队列:ready、event、read、write、timer 和 unused 。
实际上,zebra 中的线程是“假线程”,它并没有实现线程中的优先级抢占问题。在 zebra
的线程管理中,有个虚拟的时间轴,必须等前一时间的线程处理完,才看下一时间的线程是
否触发。由于电脑处理速度较快且处理每个线程的时间间隔较小,所以处理其多线程来可以
打到类似“并行处理”的效果。
Zebra 中的线程调用机制:
1,bgp daemon 不断地从 event queue 中取出 thread 并且执行它。一旦该 thread 被执行
了,将该 thread 的 type 设置为 unuse。并且将该 thread 添加到 unuse queue 中。
2,如果 event queue 为空时,bgp daemon 通过 select 函数监控读、写、异常三个描述
符集。一旦有某个描述符准备就绪,则将该描述符所对应的 thread 加入 ready queue.
而对于 timer queue 中的 thread,只有当 select 函数超时后才会进入 ready queue.
函数 thread_fetch 无疑是这个框架的核心,下面就详细分析一下这个函数。
struct thread * thread_fetch (struct thread_master *m, struct thread *fetch)
{
struct thread *thread;
fd_set readfd;
fd_set writefd;
fd_set exceptfd;
struct timeval timer_val = { .tv_sec = 0, .tv_usec = 0 };
struct timeval timer_val_bg;
struct timeval *timer_wait = &timer_val;
struct timeval *timer_wait_bg;
while (1)
{
int num = 0;
/* Signals pre-empt everything */
quagga_sigevent_process ();
/* 先处理 ready 队列中的进程 */
if ((thread = thread_trim_head (&m->ready)) != NULL)
return thread_run (m, thread, fetch);
/*接着处理 event 队列*/
thread_process (&m->event);
/* Structure copy.
readfd = m->readfd;
writefd = m->writefd;
*/
exceptfd = m->exceptfd;
/* Calculate select wait timer if nothing else to do */
if (m->ready.count == 0)
{
}
quagga_get_relative (NULL);
timer_wait = thread_timer_wait (&m->timer, &timer_val);
timer_wait_bg = thread_timer_wait (&m->background, &timer_val_bg);
if (timer_wait_bg &&
(!timer_wait || (timeval_cmp (*timer_wait, *timer_wait_bg) > 0)))
timer_wait = timer_wait_bg;
/* 监听所有的 socket 描述符,看看是否有 socket 可读可写 */
num = select (FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
if (num < 0)
{
}
if (errno == EINTR)
continue; /* signal received - process it */
zlog_warn ("select() error: %s", safe_strerror (errno));
return NULL;
*/
/* 处理 timer
quagga_get_relative (NULL);
thread_timer_process (&m->timer, &relative_time);
/* Got IO, process it */
if (num > 0)
{
}
/* Normal priority read thead. */
thread_process_fd (&m->read, &readfd, &m->readfd);
/* Write thead. */
thread_process_fd (&m->write, &writefd, &m->writefd);
/* Background timer/events, lowest priority */
thread_timer_process (&m->background, &relative_time);
/* 处理 ready 的线程 */
if ((thread = thread_trim_head (&m->ready)) != NULL)
return thread_run (m, thread, fetch);
}
}