FRR -- CLI簡要分析

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_initint 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;

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章