FRR作爲一個路由器軟件,自然要提供人機接口。
FRR提供snmp管理接口,而且,自然就會有對應的命令行管理格式,當然一般路由軟件不會提供界面形式的,也許有webui,然而FRR並沒有。
我們要看的就是這個命令行處理的代碼 command。
接觸過類似命令行的朋友肯定有一點點好奇吧,那麼數量龐大的命令和參數輸入,還可以提供提示和自動補齊,這肯定不是一件很簡單的事情。
下面是一個配置示例:
1 !
2 interface bge0
3 ip ospf authentication message-digest
4 ip ospf message-digest-key 1 md5 ABCDEFGHIJK
5 !
6 router ospf
7 network 192.168.0.0/16 area 0.0.0.1
8 area 0.0.0.1 authentication message-digest
看到這樣的命令,實在是頭疼。
嗯,不廢話了,我們還是看代碼吧,看command是怎麼處理這令人頭疼的命令行吧:
1 void cmd_init(int terminal) {
2 ......
3
4 cmdvec = vector_init(VECTOR_MIN_SIZE);
5
6 /* Install top nodes. */
7 install_node(&view_node, NULL);
8 install_node(&enable_node, NULL);
9
10 /* Each node's basic commands. */
11 install_element(VIEW_NODE, &show_version_cmd);
12
13 .....
14 }
這個就是命令行初始化的簡化版本。
FRR使用了非常常見的樹形列表來描述所有的命令, cmdvec包含所有的頂層命令節點,節點下面是當前節點的所包含的命令元素.
1 struct cmd_node
2 {
3 /* Node index. */
4 enum node_type node;
5
6 /* Prompt character at vty interface. */
7 const char *prompt;
8
9 /* Is this node's configuration goes to vtysh ? */
10 int vtysh;
11
12 /* Node's configuration write function */
13 int (*func) (struct vty *);
14
15 /* Vector of this node's command list. */
16 vector cmd_vector;
17 };
上面已經舉過命令行的具體例子,解釋和執行命令行的函數如下:
1 extern vector cmd_make_strvec (const char *);
2 extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int);
通過查找匹配,找到對應的函數執行:
1 /* Execute matched command. */
2 return (*matched_element->func)(matched_element, vty, argc, argv);
執行的函數由如下的宏聲明:
1 /* helper defines for end-user DEFUN* macros */
2 #define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
3 struct cmd_element cmdname = \
4 { \
5 .string = cmdstr, \
6 .func = funcname, \
7 .doc = helpstr, \
8 .attr = attrs, \
9 .daemon = dnum, \
10 };
11
12 #define DEFUN_CMD_FUNC_DECL(funcname) \
13 static int funcname (struct cmd_element *, struct vty *, int, const char *[]);
14
15 #define DEFUN_CMD_FUNC_TEXT(funcname) \
16 static int funcname \
17 (struct cmd_element *self __attribute__ ((unused)), \
18 struct vty *vty __attribute__ ((unused)), \
19 int argc __attribute__ ((unused)), \
20 const char *argv[] __attribute__ ((unused)) )
21
22 #define DEFUN(funcname, cmdname, cmdstr, helpstr) \
23 DEFUN_CMD_FUNC_DECL(funcname) \
24 DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
25 DEFUN_CMD_FUNC_TEXT(funcname)
然後看一個具體命令行聲明:
1 /* Configration from terminal */
2 DEFUN(config_terminal,
3 config_terminal_cmd,
4 "configure terminal",
5 "Configuration from vty interface\n"
6 "Configuration terminal\n") {
7 if (vty_config_lock(vty)) vty->node = CONFIG_NODE;
8 else {
9 vty_out(vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE);
10 return CMD_WARNING;
11 }
12 return CMD_SUCCESS;
13 }
這是進入配置模式的命令。
在FRR中有很多的命令,利用這些下面分析一下命令是怎麼被讀取和執行的。在FRR中定義的命令都是利用宏定義實現的,這個宏定義還是有點複雜,下面是命令的宏定義語句。
定義在command.h
#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終端下輸入的命令字符串,第四個是幫助信息,當輸入“?”時,顯示出來。
#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)) )
假設我們這裏有一個下面宏定義:
DEFUN (vtysh_show_hello, vtysh_show_hello_cmd,
"show hello",
" hello1\n"
" hello2\n")
{
printf("hello\n");
return CMD_SUCCESS;
}
看一下它是如何展開的:
首先看一下下面的這個結構體,在宏DEFUN_CMD_ELEMENT中使用到。
/* Structure of command element. */
struct cmd_element {
const char *string; /* Command specification by string. */
const char *doc; /* Documentation of this command. */
int daemon; /* Daemon to which this command belong. */
uint8_t attr; /* Command attributes */
/* handler function for command */
int (*func)(const struct cmd_element *, struct vty *, int,
struct cmd_token *[]);
const char *name; /* symbol name for debugging */
};
#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
int funcname (struct cmd_element *, struct vty *, int, char **);\
struct cmd_element cmdname = \
{ \
cmdstr, \
funcname, \
helpstr \
}; \
int funcname (struct cmd_element *self, struct vty *vty, int argc, char **argv)
還有一個結構struct vty應定義在vty.h中。根據宏定義DEFUN,可展開如下:
int vtysh_show_hello (struct cmd_element *, struct vty *, int, char **);
struct cmd_element vtysh_show_hello_cmd =
{
"show hello",
vtysh_show_hello,
" hello1\n hello2\n"
};
int vtysh_show_hello (struct cmd_element *self, struct vty *vty, int argc, char **argv)
{
printf("hello\n");
return CMD_SUCCESS;
}
在command.c中,實現了Show version,下面的代碼摘自FRR裏面。
/* Show version. */
DEFUN (show_version,
show_version_cmd,
"show version",
SHOW_STR
"Displays zebra version\n")
{
vty_out(vty, "%s %s (%s).\n", FRR_FULL_NAME, FRR_VERSION,
cmd_hostname_get() ? cmd_hostname_get() : "");
vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO);
vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS);
return CMD_SUCCESS;
}
上面已經分析了這個函數是怎麼一步一步展開的,我們在定義一個命令的時候,也必須要在某一個節點下安裝這個命令。使用下面的語句。
在cmd_init(int terminal)函數中。
/* Install top node of command vector. */
void install_node(struct cmd_node *node, int (*func)(struct vty *))
{
vector_set_index(cmdvec, node->node, node);
node->func = func;
node->cmdgraph = graph_new();
node->cmd_vector = vector_init(VECTOR_MIN_SIZE);
// add start node
struct cmd_token *token =
cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
graph_new_node(node->cmdgraph, token,
(void (*)(void *)) & cmd_token_del);
node->cmd_hash = hash_create_size(16, cmd_hash_key, cmd_hash_cmp,
"Command Hash");
}
cmdvec變量在cmd_init函數一開始時進行了初始化,
cmdvec = vector_init (VECTOR_MIN_SIZE);
在vecto_init函數中分配了內存,並且返回vector結構。
一個命令要在某個結點下安裝,如show version命令
install_element(VIEW_NODE, &show_version_cmd);
/* Install a command into a node. */
void install_element(enum node_type ntype, struct cmd_element *cmd)
{
struct cmd_node *cnode;
/* cmd_init hasn't been called */
if (!cmdvec) {
fprintf(stderr, "%s called before cmd_init, breakage likely\n",
__func__);
return;
}
cnode = vector_lookup(cmdvec, ntype);
if (cnode == NULL) {
fprintf(stderr,
"%s[%s]:\n"
"\tnode %d (%s) does not exist.\n"
"\tplease call install_node() before install_element()\n",
cmd->name, cmd->string, ntype, node_names[ntype]);
exit(EXIT_FAILURE);
}
if (hash_lookup(cnode->cmd_hash, cmd) != NULL) {
fprintf(stderr,
"%s[%s]:\n"
"\tnode %d (%s) already has this command installed.\n"
"\tduplicate install_element call?\n",
cmd->name, cmd->string, ntype, node_names[ntype]);
return;
}
assert(hash_get(cnode->cmd_hash, cmd, hash_alloc_intern));
struct graph *graph = graph_new();
struct cmd_token *token =
cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
cmd_graph_parse(graph, cmd);
cmd_graph_names(graph);
cmd_graph_merge(cnode->cmdgraph, graph, +1);
graph_delete_graph(graph);
vector_set(cnode->cmd_vector, cmd);
if (ntype == VIEW_NODE)
install_element(ENABLE_NODE, cmd);
}
這樣加上上面用宏定義的命令和註冊命令的流程,一個完整的命令就算完成了。
當我們在命令行中輸入一個命令時,我們看一下它的流程是什麼,也就是最後是怎麼調用到我們定義的命令的處理函數。在vtysh_main.c函數中,有一個main函數,在這個函數中對輸入的命令行進行了處理。我們只看其中最主要的一個部分,也就是一個無限循環,在這個無限循環中我們可以看到vtysh_rl_gets函數不斷的讀取命令行中的輸入,如果有輸入的話,就會調用vtysh_execute (line_read);函數對輸入的命令行進行處理
/* VTY shell main routine. */
int main(int argc, char **argv, char **env)
{
/* Main command loop. */
while (vtysh_rl_gets())
vtysh_execute(line_read);
}
int vtysh_execute(const char *line)
{
return vtysh_execute_func(line, 1);
}
在static void
vtysh_execute_func (const char *line, int pager)
{
……………………
saved_ret = ret = cmd_execute_command (vline, vty, &cmd, 1);
…………………
}
/* Command execution over the vty interface. */
static int vtysh_execute_func(const char *line, int pager)
{
int ret, cmd_stat;
unsigned int i;
vector vline;
const struct cmd_element *cmd;
int tried = 0;
int saved_ret, saved_node;
……………
saved_ret = ret = cmd_execute(vty, line, &cmd, 1);
………………
}
int cmd_execute(struct vty *vty, const char *cmd,
const struct cmd_element **matched, int vtysh)
{
ret = cmd_execute_command(vline, vty, matched, vtysh);
}
int cmd_execute_command(vector vline, struct vty *vty,
const struct cmd_element **cmd, int vtysh)
{
int ret, saved_ret = 0;
enum node_type onode, try_node;
int orig_xpath_index;
onode = try_node = vty->node;
orig_xpath_index = vty->xpath_index;
if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
vector shifted_vline;
unsigned int index;
vty->node = ENABLE_NODE;
vty->xpath_index = 0;
/* We can try it on enable node, cos' the vty is authenticated
*/
shifted_vline = vector_init(vector_count(vline));
/* use memcpy? */
for (index = 1; index < vector_active(vline); index++)
vector_set_index(shifted_vline, index - 1,
vector_lookup(vline, index));
ret = cmd_execute_command_real(shifted_vline, FILTER_RELAXED,
vty, cmd);
vector_free(shifted_vline);
vty->node = onode;
vty->xpath_index = orig_xpath_index;
return ret;
}
……………………………
}
在這個函數中,調用了我們定義的命令的處理函數
/* Execute command by argument vline vector. */
static int cmd_execute_command_real(vector vline, enum cmd_filter_type filter,
struct vty *vty,
const struct cmd_element **cmd)
{
struct list *argv_list;
enum matcher_rv status;
const struct cmd_element *matched_element = NULL;
struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node);
status = command_match(cmdgraph, vline, &argv_list, &matched_element);
if (cmd)
*cmd = matched_element;
// if matcher error, return corresponding CMD_ERR
if (MATCHER_ERROR(status)) {
if (argv_list)
list_delete(&argv_list);
switch (status) {
case MATCHER_INCOMPLETE:
return CMD_ERR_INCOMPLETE;
case MATCHER_AMBIGUOUS:
return CMD_ERR_AMBIGUOUS;
default:
return CMD_ERR_NO_MATCH;
}
}
// build argv array from argv list
struct cmd_token **argv = XMALLOC(
MTYPE_TMP, argv_list->count * sizeof(struct cmd_token *));
struct listnode *ln;
struct cmd_token *token;
unsigned int i = 0;
for (ALL_LIST_ELEMENTS_RO(argv_list, ln, token))
argv[i++] = token;
int argc = argv_list->count;
int ret;
if (matched_element->daemon)
ret = CMD_SUCCESS_DAEMON;
else {
/* Clear enqueued configuration changes. */
vty->num_cfg_changes = 0;
memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes));
ret = matched_element->func(matched_element, vty, argc, argv);
////最後調用處理函數,也就是我們使用DEFUN宏定義的命令
}
// delete list and cmd_token's in it
list_delete(&argv_list);
XFREE(MTYPE_TMP, argv);
return ret;
}