quagga命令行解析

(3) 2024-04-22 08:23

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说quagga命令行解析,希望能够帮助你!!!。

文章目录

    • 0 引言
    • 1 主要结构体
    • 2 命令的定义
      • DENFUN 系列宏
    • 节点的安装
    • 单条命令的安装
    • quagga 中对readline库使用
      • 命令的输入
      • 命令的补全
    • 命令的解析与执行

0 引言

quagga 是一个优秀的开源路由软件,是zebra 的升级维护版本,实现了rip、ospf、bgp等协议,可以将linux设备变成一个功能完整的路由器。quagga 提供了一个类似Cisco命令行的分级多用户命令解析引擎—VTY ,类似于linux shell功能,涉及各个命令的解析,查找,补全等功能的实现,quagga命令解析主要涉及以下内容:

  • 涉及的数据结构:vector hash
  • command中 DEFUN 系列 宏的使用
  • readline开源库的使用
  • command中自动补全(\t),帮助信息(?)
  • 命令安装
  • 命令的解析

1 主要结构体

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

对应的层次关系图如下:
quagga命令行解析_https://bianchenghao6.com/blog__第1张

2 命令的定义

DENFUN 系列宏

#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库使用

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

命令的解析与执行

quagga命令行解析_https://bianchenghao6.com/blog__第2张

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复