Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说quagga命令行解析,希望能够帮助你!!!。
quagga 是一个优秀的开源路由软件,是zebra 的升级维护版本,实现了rip、ospf、bgp等协议,可以将linux设备变成一个功能完整的路由器。quagga 提供了一个类似Cisco命令行的分级多用户命令解析引擎—VTY ,类似于linux shell功能,涉及各个命令的解析,查找,补全等功能的实现,quagga命令解析主要涉及以下内容:
quagga 中命令是按节点进行划分的,每条命令安装在不同的节点的下面,存储各个命令的主要的数据结构是vector和hash,vector主要是用来存放命令,而hash则是主要用来查找、匹配命令,所有的节点存储在一个全局变量中 vector cmdvec 中。
命令的节点对应的结构体 :
/* Node which has some commands and prompt string and configuration
function pointer . */
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;
/* Hashed index of command node list, for de-dupping primarily */
struct hash *cmd_hash;
};
每条命令则是一个元素,命令挂载到不同节点下面,每个元素对应的结构体:
/* Structure of command element. */
struct cmd_element
{
const char *string; /* Command specification by string. */
int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
const char *doc; /* Documentation of this command. */
int daemon; /* Daemon to which this command belong. */
vector tokens; /* Vector of cmd_tokens */
u_char attr; /* Command attributes */
};
每条命令还会进一步进行解析 存储在 结构体 cmd_token中
/* Command description structure. */
struct cmd_token
{
enum cmd_token_type type;
enum cmd_terminal_type terminal;
/* Used for type == MULTIPLE */
vector multiple; /* vector of cmd_token, type == FINAL */
/* Used for type == KEYWORD */
vector keyword; /* vector of vector of cmd_tokens */
/* Used for type == TERMINAL */
char *cmd; /* Command string. */
char *desc; /* Command's description. */
};
对应的层次关系图如下:
#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)
函数声明的宏:
#define DEFUN_CMD_FUNC_DECL(funcname) \
static int funcname (struct cmd_element *, struct vty *, int, const char *[]);
为这条命令定义一个 struct cmd_element 类型的变量,变量名字 cmdname,并且填充结构中的各个变量
#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)
以 show version 为例:
DEFUN (show_version,
show_version_cmd,
"show version",
SHOW_STR
"Displays zebra version\n")
{
vty_out (vty, "Quagga %s (%s).%s", QUAGGA_VERSION, host.name?host.name:"",
VTY_NEWLINE);
vty_out (vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE);
return CMD_SUCCESS;
}
按照上面宏定义可以进行展开如下:
static int show_version (struct cmd_element *, struct vty *, int, const char *[]);
struct cmd_element show_version_cmd =
{
.string = "show version", \
.func = show_version, \
.doc = "Show running system information\nDisplays zebra version\n"
.attr = 0,
.daemon = 0,
};
// __attribute__ ((unused)) 的作用: 如果定义静态函数而没有使用则会产生一个编译警告,而 __attribute__ ((unused)) 的作用就是去掉这个编译警告
static int show_version (struct cmd_element *self __attribute__ ((unused)),
struct vty *vty __attribute__ ((unused)),
int argc __attribute__ ((unused)),
const char *argv[] __attribute__ ((unused)) )
{
vty_out (vty, "Quagga %s (%s).%s", QUAGGA_VERSION, host.name?host.name:"",
VTY_NEWLINE);
vty_out (vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE);
return CMD_SUCCESS;
}
下面介绍节点安装的过程,以 config_node 节点为例:
首先定义一个节点变量
static struct cmd_node config_node =
{
CONFIG_NODE,
"%s(config)# ",
1
};
然后进行插入:
install_node (&config_node, config_write_host);
下面详细介绍 install_node 函数
/* Install top node of command vector. */
void
install_node (struct cmd_node *node,
int (*func) (struct vty *))
{
// 将 该节点的信息插入到 全局的 vector cmdvec中去 ,位置就是 该节点的枚举值,值就是该结构体的地址
vector_set_index (cmdvec, node->node, node);
printf("node->node %d \n",node->node);
printf("cmdvec active %d alloced %d \n",cmdvec->active,cmdvec->alloced);
/* Node's configuration write function 只有个别命令会用到这个函数 */
node->func = func;
// 创建一个新的 vector 用于存放 在该节点下的所有命令
node->cmd_vector = vector_init (VECTOR_MIN_SIZE);
// 创建一个hash表,用于检索各个命令
node->cmd_hash = hash_create (cmd_hash_key, cmd_hash_cmp);
}
采用宏DEFUN 就可以定义一条命令,上面已经就各个宏定义就行的解析,不在赘述,而安装一条用的命令需要的函数则为:
/* Install a command into a node. */
// 安装 一条命令在 某个节点下面
// ntype 节点
// cmd 命令结构
void install_element (enum node_type ntype, struct cmd_element *cmd)
{
struct cmd_node *cnode;
/* cmd_init hasn't been called */
// 检验 cmdvec 的合法性,cmdvec 在cmd_init 中被初始化
if (!cmdvec)
{
fprintf (stderr, "%s called before cmd_init, breakage likely\n",
__func__);
return;
}
// 从存储节点的全局vector cmdvec中,找到该命令对应的节点,如果找不到,直接退出
cnode = vector_slot (cmdvec, ntype);
if (cnode == NULL)
{
fprintf (stderr, "Command node %d doesn't exist, please check it\n",
ntype);
exit (1);
}
// 查找该节点下是否存在该命令,如果存在 则直接返回
if (hash_lookup (cnode->cmd_hash, cmd) != NULL)
{
#ifdef DEV_BUILD
fprintf (stderr,
"Multiple command installs to node %d of command:\n%s\n",
ntype, cmd->string);
#endif
return;
}
// 将该命令直接插入到 该节点下面对应的hash表中
assert (hash_get (cnode->cmd_hash, cmd, hash_alloc_intern));
// 将该命令的结构体 struct cmd_element * cmd 插入到 该节点下的vector中
vector_set (cnode->cmd_vector, cmd);
/* 接下来进行 解析该命令中每个 token ,然后存储在 struct cmd_element结构体中的 vector tokens;*/
#if 0
/* Command description structure. */
struct cmd_token
{
enum cmd_token_type type;
enum cmd_terminal_type terminal;
/* Used for type == MULTIPLE */
vector multiple; /* vector of cmd_token, type == FINAL */
/* Used for type == KEYWORD */
vector keyword; /* vector of vector of cmd_tokens */
/* Used for type == TERMINAL */
char *cmd; /* Command string. */
char *desc; /* Command's description. */
};
#endif
*/
/* 根据 cmd中的string doc 解析出来 token
例子1:
cmd->string "show version" cmd->doc "Show running system information\nDisplays zebra version\n"
tokens 0x562c9c988060 tokens vec count 2
i 0 ----- tokens 0x562c9c9880c0 info ------
cmd show
desc Show running system information
type 0
terminal 1
--------------------------------
i 1 ----- tokens 0x562c9c988140 info ------
cmd version
desc Displays zebra version
type 0
terminal 1
--------------------------------
例子2:
cmd->string "enable password (8|) WORD"
cmd->doc "Modify enable password parameters\n
Assign the privileged level password\n
Specifies a HIDDEN password will follow\n
dummy string\n
The HIDDEN 'enable' password string\n"
tokens 0x562c9c98af90 tokens vec count 4
i 0 ----- tokens 0x562c9c98aff0 info ------
cmd enable
desc Modify enable password parameters
type 0
terminal 1
--------------------------------
i 1 ----- tokens 0x562c9c98b070 info ------
cmd password
desc Assign the privileged level password
type 0
terminal 1
--------------------------------
i 2 ----- tokens 0x562c9c98b0d0 info ------
cmd (null)
desc (null)
type 1
terminal 0
--------------------------------
i 3 ----- tokens 0x562c9c98b1f0 info ------
cmd WORD
desc The HIDDEN 'enable' password string
type 0
terminal 3
--------------------------------
例子3:
cmd->string "on-match goto <1-65535>"
cmd->doc "Exit policy on matches \n Goto Clause number\nNumber\n"
tokens 0x562c9c999fe0 tokens vec count 3
i 0 ----- tokens 0x562c9c99a040 info ------
cmd on-match
desc Exit policy on matches
type 0
terminal 1
--------------------------------
i 1 ----- tokens 0x562c9c99a0b0 info ------
cmd goto
desc Goto Clause number
type 0
terminal 1
--------------------------------
i 2 ----- tokens 0x562c9c99a120 info ------
cmd <1-65535>
desc Number
type 0
terminal 5
--------------------------------
有关token相关的解释说明查阅以下内容,不在此一一进行翻译
* cmd_token = cmd_terminal
* | cmd_multiple
* | cmd_keyword ;
*
* cmd_terminal_fixed = fixed_string
* | variable
* | range
* | ipv4
* | ipv4_prefix
* | ipv6
* | ipv6_prefix ;
*
* cmd_terminal = cmd_terminal_fixed
* | option
* | vararg ;
*
* multiple_part = cmd_terminal_fixed ;
* cmd_multiple = "(" , multiple_part , ( "|" | { "|" , multiple_part } ) , ")" ;
*
* keyword_part = fixed_string , { " " , ( cmd_terminal_fixed | cmd_multiple ) } ;
* cmd_keyword = "{" , keyword_part , { "|" , keyword_part } , "}" ;
*
* lowercase = "a" | ... | "z" ;
* uppercase = "A" | ... | "Z" ;
* digit = "0" | ... | "9" ;
* number = digit , { digit } ;
*
* fixed_string = (lowercase | digit) , { lowercase | digit | uppercase | "-" | "_" } ;
* variable = uppercase , { uppercase | "_" } ;
* range = "<" , number , "-" , number , ">" ;
* ipv4 = "A.B.C.D" ;
* ipv4_prefix = "A.B.C.D/M" ;
* ipv6 = "X:X::X:X" ;
* ipv6_prefix = "X:X::X:X/M" ;
* option = "[" , variable , "]" ;
* vararg = "." , variable ;
*
* To put that all in a textual description: A cmdstr is a sequence of tokens,
* separated by spaces.
*
* Terminal Tokens:
*
* A very simple cmdstring would be something like: "show ip bgp". It consists
* of three Terminal Tokens, each containing a fixed string. When this command
* is called, no arguments will be passed down to the function implementing it,
* as it only consists of fixed strings.
*
* Apart from fixed strings, Terminal Tokens can also contain variables:
* An example would be "show ip bgp A.B.C.D". This command expects an IPv4
* as argument. As this is a variable, the IP address entered by the user will
* be passed down as an argument. Apart from two exceptions, the other options
* for Terminal Tokens behave exactly as we just discussed and only make a
* difference for the CLI. The two exceptions will be discussed in the next
* paragraphs.
*
* A Terminal Token can contain a so called option match. This is a simple
* string variable that the user may omit. An example would be:
* "show interface [IFNAME]". If the user calls this without an interface as
* argument, no arguments will be passed down to the function implementing
* this command. Otherwise, the interface name will be provided to the function
* as a regular argument.
* Also, a Terminal Token can contain a so called vararg. This is used e.g. in
* "show ip bgp regexp .LINE". The last token is a vararg match and will
* consume all the arguments the user inputs on the command line and append
* those to the list of arguments passed down to the function implementing this
* command. (Therefore, it doesn't make much sense to have any tokens after a
* vararg because the vararg will already consume all the words the user entered
* in the CLI)
*
* Multiple Tokens:
*
* The Multiple Token type can be used if there are multiple possibilities what
* arguments may be used for a command, but it should map to the same function
* nonetheless. An example would be "ip route A.B.C.D/M (reject|blackhole)"
* In that case both "reject" and "blackhole" would be acceptable as last
* arguments. The words matched by Multiple Tokens are always added to the
* argument list, even if they are matched by fixed strings. Such a Multiple
* Token can contain almost any type of token that would also be acceptable
* for a Terminal Token, the exception are optional variables and varag.
*
* There is one special case that is used in some places of Quagga that should be
* pointed out here shortly. An example would be "password (8|) WORD". This
* construct is used to have fixed strings communicated as arguments. (The "8"
* will be passed down as an argument in this case) It does not mean that
* the "8" is optional. Another historic and possibly surprising property of
* this construct is that it consumes two parts of helpstr. (Help
* strings will be explained later)
*
* Keyword Tokens:
*
* There are commands that take a lot of different and possibly optional arguments.
* An example from ospf would be the "default-information originate" command. This
* command takes a lot of optional arguments that may be provided in any order.
* To accomodate such commands, the Keyword Token has been implemented.
* Using the keyword token, the "default-information originate" command and all
* its possible options can be represented using this single cmdstr:
* "default-information originate \
* {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}"
*
* Keywords always start with a fixed string and may be followed by arguments.
* Except optional variables and vararg, everything is permitted here.
*
* For the special case of a keyword without arguments, either NULL or the
* keyword itself will be pushed as an argument, depending on whether the
* keyword is present.
* For the other keywords, arguments will be only pushed for
* variables/Multiple Tokens. If the keyword is not present, the arguments that
* would have been pushed will be substituted by NULL.
*
* A few examples:
* "default information originate metric-type 1 metric 1000"
* would yield the following arguments:
* { NULL, "1000", "1", NULL }
*
* "default information originate always route-map RMAP-DEFAULT"
* would yield the following arguments:
* { "always", NULL, NULL, "RMAP-DEFAULT" }
*/
if (cmd->tokens == NULL)
{
cmd->tokens = cmd_parse_format(cmd->string, cmd->doc);
#if 0
printf("cmd->string %s cmd->doc %s tokens %p tokens vec count %d\n",
cmd->string,cmd->doc,cmd->tokens,vector_count(cmd->tokens));
for(int i = 0;i < vector_count(cmd->tokens);i++)
{
printf("i %d ----- tokens %p info ------\n",i,cmd->tokens->index[i]);
struct cmd_token * temp = (struct cmd_token *) cmd->tokens->index[i];
printf("cmd %s\n",temp->cmd);
printf("desc %s\n",temp->desc);
printf("type %d\n",temp->type);
printf("terminal %d\n",temp->terminal);
printf("--------------------------------\n");
}
#endif
}
if (ntype == VIEW_NODE)
install_element (ENABLE_NODE, cmd);
}
quagga 主要用到readline 中的 命令的输入 Tab补全 ?补全 命令历史记录 上下键查看命令的历史等功能。
static char * vtysh_rl_gets ()
{
HIST_ENTRY *last;
/* If the buffer has already been allocated, return the memory
* to the free pool. */
if (line_read)
{
free (line_read);
line_read = NULL;
}
/* Get a line from the user. Change prompt according to node. XXX. */
/*用 vtysh_prompt () 获取 shell 前缀信息 当视图(节点)改变时,前缀信息也会改变
例如:
vtysh_prompt () = wan#
wan# configure t
vtysh_prompt () = wan(config)#
interface lo
vtysh_prompt () = wan(config-if)#
*/
// 读取用户的输入
line_read = readline (vtysh_prompt ());
/* If the line has any text in it, save it on the history. But only if last command in history isn't the same one. */
//加入到历史中去,以便可以利用 上下键进行命令查询
if (line_read && *line_read)
{
using_history();
last = previous_history();
if (!last || strcmp (last->line, line_read) != 0)
{
add_history (line_read);
append_history(1,history_file);
}
}
return (line_read);
}
void vtysh_readline_init (void)
{
/* readline related settings. */
/*'?' 问好进行关联,当输入‘?’时 自动跳转到 vtysh_rl_describe 中,然后输出命令的命令相关的信息
例如:
wan# sho?
show Show running system information
*/
rl_bind_key ('?', (rl_command_func_t *) vtysh_rl_describe);
/* To disable readline's filename completion. */
rl_completion_entry_function = vtysh_completion_entry_function;
// 添加自己的 命令补全函数
rl_attempted_completion_function = (rl_completion_func_t *)new_completion;
}
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章