[FRR] CLI brief analysis

As a router software, FRR naturally provides a man-machine interface.

FRR provides a snmp management interface, and naturally there will be a corresponding command line management format. Of course, general routing software will not provide an interface form, maybe webui, but FRR does not.

What we need to look at is the code command for this command line processing.

Friends who have been in contact with similar command lines must be a little bit curious, so a large number of commands and parameter inputs can also provide prompts and automatic completion, which is certainly not a very simple thing.

The following is a configuration example:
 

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

Seeing such an order is really a headache.

 

Well, no nonsense, let's look at the code and see how command handles this headache command line:

 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 }

This is a simplified version of the command line initialization.

FRR uses a very common tree list to describe all commands. cmdvec contains all top-level command nodes. Below the node are the command elements contained in the current node.

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

The specific example of the command line has been given above. The functions to explain and execute the command line are as follows:

extern vector cmd_make_strvec (const char *);

extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int);

By finding a match, find the corresponding function to execute:

/* Execute matched command. */

return (*matched_element->func)(matched_element, vty, argc, argv);

The executed function is declared by the following macro: 

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)

Then look at a specific command line statement:

 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 }

This is the command to enter the configuration mode.

There are many commands in FRR. Use these to analyze how the commands are read and executed. The commands defined in FRR are all implemented using macro definitions. This macro definition is still a bit complicated. The following is the macro definition statement of the command.

Defined in 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)

The first funcname is the name of the function, the second is the name of the registered command, the third is the command string entered in the vtysh terminal, and the fourth is the help information, which is displayed when you enter "?".

#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)) )

Suppose we have the following macro definition here:

DEFUN (vtysh_show_hello, vtysh_show_hello_cmd,
 
      "show hello", 
 
      " hello1\n"
 
      " hello2\n")
 
{
 
   printf("hello\n");
 
 
 
  return CMD_SUCCESS; 
 
}

Take a look at how it unfolds:

First look at the structure below, which is used in the macro 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)

There is also a structure struct vty that should be defined in vty.h. According to the macro definition DEFUN, it can be expanded as follows:

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

In command.c, Show version is implemented . The following code is taken from 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;
 
}

The above has analyzed how this function is expanded step by step. When we define a command, we must also install this command under a certain node. Use the following statement.

In the cmd_init (int terminal) function.

/* 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");
 
}
 

The cmdvec variable is initialized at the beginning of the cmd_init function,

cmdvec = vector_init (VECTOR_MIN_SIZE);

The memory is allocated in the vecto_init function and the vector structure is returned.

 

A command should be installed under a certain node, such as the show version command

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);
 
}

In this way, with the above macro-defined commands and the process of registering commands, a complete command is complete.

 

When we enter a command on the command line, let's take a look at its process, that is, how to call the processing function of the command we defined in the end. In the vtysh_main.c function, there is a main function in which the input command line is processed. We only look at the most important part, which is an infinite loop. In this infinite loop, we can see that the vtysh_rl_gets function continuously reads the input from the command line. If there is input, it will call vtysh_execute (line_read); The function processes the input command line
 

/* 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;
 
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/weixin_39094034/article/details/115219795