[FRR] CLI 간단한 분석

라우터 소프트웨어로서 FRR은 자연스럽게 인간-머신 인터페이스를 제공합니다.

FRR은 snmp 관리 인터페이스를 제공하며 당연히 해당 명령 줄 관리 형식이있을 것입니다. 물론 일반 라우팅 소프트웨어는 webui와 같은 인터페이스 형식을 제공하지 않지만 FRR은 제공하지 않습니다.

우리가 살펴 봐야 할 것은이 명령 줄 처리를위한 코드 명령입니다.

유사한 명령 줄과 접촉 한 친구는 약간 호기심이 많을 것이므로 많은 명령과 매개 변수 입력도 프롬프트와 자동 완성을 제공 할 수 있습니다. 이는 확실히 매우 간단한 일이 아닙니다.

다음은 구성 예입니다.
 

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

그러한 명령을 보는 것은 정말 골치 아픈 일입니다.

 

말도 안되는 소리가 아닙니다. 코드를보고 명령이이 문제가있는 명령 줄을 어떻게 처리하는지 살펴 보겠습니다.

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

위 명령 줄의 구체적인 예는 아래와 같으며, 명령 줄을 설명하고 실행하는 기능은 다음과 같다.

extern vector cmd_make_strvec (const char *);

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

일치하는 항목을 찾으면 실행할 해당 함수를 찾습니다.

/* Execute matched command. */

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)

vty.h에 정의되어야하는 구조 구조체 vty도 있습니다. 매크로 정의 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 터미널) 함수에서.

/* 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 함수에 할당되고 벡터 구조가 반환됩니다.

 

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 함수에는 입력 명령 줄을 처리하는 주요 함수가 있습니다. 가장 중요한 부분 인 무한 루프 만 살펴 보겠습니다.이 무한 루프에서 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;
 
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

추천

출처blog.csdn.net/weixin_39094034/article/details/115219795