FRR作为一个路由器软件,自然要提供人机接口。

FRR提供snmp管理接口,而且,自然就会有对应的命令行管理格式,当然一般路由软件不会提供界面形式的,也许有webui,然而FRR并没有。

我们要看的就是这个命令行处理的代码 command。

接触过类似命令行的朋友肯定有一点点好奇吧,那么数量庞大的命令和参数输入,还可以提供提示和自动补齐,这肯定不是一件很简单的事情。

下面是一个配置示例:

1 !2 interface bge03 ip ospf authentication message-digest4 ip ospf message-digest-key 1 md5 ABCDEFGHIJK5 !6 router ospf7 network 192.168.0.0/16 area 0.0.0.18 area 0.0.0.1 authentication message-digest

看到这样的命令,实在是头疼。

嗯,不废话了,我们还是看代码吧,看command是怎么处理这令人头疼的命令行吧:

 1 void cmd_init(int terminal) {2    ......34     cmdvec = vector_init(VECTOR_MIN_SIZE);5  6     /* Install top nodes. */7     install_node(&view_node, NULL);8     install_node(&enable_node, NULL);910     /* Each node's basic commands. */11     install_element(VIEW_NODE, &show_version_cmd);1213     .....14 }

这个就是命令行初始化的简化版本。

FRR使用了非常常见的树形列表来描述所有的命令, cmdvec包含所有的顶层命令节点,节点下面是当前节点的所包含的命令元素.

 1 struct cmd_node2 {3   /* Node index. */4   enum node_type node;       56   /* Prompt character at vty interface. */7   const char *prompt;           89   /* Is this node's configuration goes to vtysh ? */10   int vtysh;11  12   /* Node's configuration write function */13   int (*func) (struct vty *);1415   /* 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   };1112 #define DEFUN_CMD_FUNC_DECL(funcname) \13   static int funcname (struct cmd_element *, struct vty *, int, const char *[]);1415 #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)) )2122 #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 nodestruct 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 voidvtysh_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_ERRif (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 liststruct 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 itlist_delete(&argv_list);XFREE(MTYPE_TMP, argv);return ret;}

FRR -- CLI简要分析相关推荐

  1. 【FRR 】CLI简要分析

    FRR作为一个路由器软件,自然要提供人机接口. FRR提供snmp管理接口,而且,自然就会有对应的命令行管理格式,当然一般路由软件不会提供界面形式的,也许有webui,然而FRR并没有. 我们要看的就 ...

  2. Asterisk cli模块分析

    最近写一些工具库,需要远程命令行调试(cli)功能,原有的一个cli模块是将接收处理的命令具体实现在cli模块中,其他模块需要修改添加自己的cli命令都需要去修改cli模块代码,觉得模块间耦合度太高, ...

  3. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析...

    构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入 ...

  4. [Java] HashMap 源码简要分析

    特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似. 源码简要分析 publ ...

  5. Android Hal层简要分析

    Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...

  6. 【安全漏洞】简要分析复现了最近的ProxyShell利用链

    前言 近日,有研究员公布了自己针对微软的Exchange服务的攻击链的3种利用方式.微软官方虽然出了补丁,但是出于种种原因还是有较多用户不予理会,导致现在仍然有许多有漏洞的服务暴露在公网中,本文主要在 ...

  7. Android L Settings 简要分析

    1.本文说明 本文主要针对L平台上Settings模块正常启动流程做一个简要分析,并试着分析一下Settings下面Storage选项的实现过程. 2.Settings概览 在之前的KK平台上Sett ...

  8. Android 5.1 Settings源码简要分析

    概述: 先声明:本人工作快两年了,仍是菜鸟级别的,惭愧啊!以前遇到好多知识点都没有记录下来,感觉挺可惜的,现在有机会接触Android 源码.我们一个Android组的搞Setting,我觉得是得写得 ...

  9. PyTorch多卡分布式训练:DistributedDataParallel (DDP) 简要分析

    ©作者 | 伟大是熬出来的 单位 | 同济大学 研究方向 | 机器阅读理解 前言 因为课题组发的卡还没有下来,先向导师问了实验室的两张卡借用.之前都是单卡训练模型,正好在这个机会实践以下单机多卡训练模 ...

  10. oracle查询表实际大小,简要分析估算oracle表的大小

    查询oracle表的大小有几种方法,笔者简要分析下他们的异同 环境,newsadmin.newlog,原本有244,459,078条记录,delete后,现在只有51,109,919记录. 一.seg ...

最新文章

  1. gets fgets 区别
  2. 在CentOS5.5上做Heroku的开发
  3. 在虚拟机中ftp服务器的配置,ftp服务器在虚拟机中配置文件
  4. Linux连接荣耀路由器pro2,荣耀路由pro2和电脑怎么连接?连接一体机方法
  5. MySQL中事务的分类
  6. java的访问修饰符
  7. 系统分析与设计-我爱烤鱼创新过程与UP过程对比分析之我见
  8. 省市县地区编码五级联东2021年版sql
  9. 网课答案搜题API接口使用
  10. python 天勤 金叉 编程代码_基于tqsdk(天勤)编译的均线交叉与通道突破相结合的交易系统...
  11. IDEA 自定义注释
  12. PyTorch的参数固定以及detach clone
  13. CORTEX-M4F基本知识
  14. linux区分物理机和虚拟机,如何判断linux服务器是虚拟机还是物理机
  15. 小程序开发学习一:开发语言解析
  16. ARM-Linux开机自启动设置-mini2440开发板
  17. 职业规划-Web前端开发工程师
  18. aws 邮件服务器 接收邮件,Amazon SES
  19. 【专访】润和软件刘洋:同心聚力开拓基于OpenHarmony的国产化科技创新之路
  20. 需要类型转换时请为模板定义非成员函数——条款46

热门文章

  1. http缓存与cdn相关技术
  2. php异步表单,利用ajax实现表单的异步互动——2018年4月10日
  3. 企业级 SpringBoot 教程 (七)springboot开启声明式事务
  4. 客户端常见的几种数据传输方法
  5. layui 工具条实现分页
  6. projecteuler_problem12
  7. testng多线程并行执行测试
  8. python 利用urllib2通过指定网页访问url,并捕获内容
  9. 关于linux下的iptables 的浅析命令和了解
  10. jQuery 的 ajax 请求方法