FRR中CLI框架分析及Yang

1.FRR中YANG库

  YANG是一种数据建模语言(data modeling language),用于对配置数据(model configuration data),状态数据(state data),远程过程调用(Remote Procedure Calls)和网络管理协议通知(notifications for network management protocols)进行建模。YANG建模语言库中文翻译地址 https://tonydeng.github.io/rfc7950-zh/

  NETCONF 协议定义了配置数据存储和一系列的创建,获取,更新,删除(CRUD)操作,可用于访问数据存储,其中采用YANG数目模型语言。RESTCONF描述了一种 RESTful 协议,此协议提供 HTTP 上的编程接口,用于访问 YANG 定义的数据,使用 NETCONF 定义的数据存储。

  YANG的数据模型语言库libyang, 是基于C语言编写的数据模型语言解析器和工具包,并提供API;其中定义了数据存储内容,操作数据,自定义协议操作,通知事件的语法和语义。链接地址https://github.com/CESNET/libyang

功能特征:
Parsing (and validating) schemas in YANG format.
Parsing (and validating) schemas in YIN format.
Parsing, validating and printing instance data in XML format.
Parsing, validating and printing instance data in JSON format (RFC 7951).
Manipulation with the instance data.
Support for default values in the instance data (RFC 6243).
Support for YANG extensions.
Support for YANG Metadata (RFC 7952).
yanglint - feature-rich YANG tool.

YANG范例文件frr-bfdd.yang

 grouping session-common {
    description "Common BFD session settings";

    leaf detection-multiplier {
      type multiplier;
      default 3;
      description "Local session detection multiplier";
    }

    leaf desired-transmission-interval {
      type uint32;
      units microseconds;
      default 300000;
      description "Minimum desired control packet transmission interval";
    }

    leaf required-receive-interval {
      type uint32;
      units microseconds;
      default 300000;
      description "Minimum required control packet receive interval";
    }

    leaf administrative-down {
      type boolean;
      default true;
      description "Disables or enables the session administratively";
    }
  }

2.FRR中CLI和API的结合

  frr中的vtysh本身是个命令行交互接口,vtysh virtual terminal interface shell
提供了一个类Cisco命令行的分级多用户命令解析引擎--VTY(Virtual Terminal)。它是类似于Linux Shell的虚拟终端接口,负责对访问的安全验证、数据缓冲、命令解析、模式切换和命令调用。
  用户通过VTYSH的每一次接口访问都会发起一个对应的VTY。VTY会根据用户优先级初始化并挂载相应的命令集Command Node。Command Node中以链表的形式包含了该用户可以访问和使用的Command。
  用户通过各种接口访问VTY,VTY解析用户的每个命令,并且通过命令集链表找到并执行Command相应函数。这样,通过访问VTY实现基于命令集的管理功能。

2.1 定义CLI对各自API调用
  frr/lib文件中,定义了command.h以及command.c函数,其中command.h定义了frr集中控制的命令宏,以及command.c中定义了frr的基础控制命令,实例如下:

//frr/lib/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)

//frr/lib/command.c
/* 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;
}

2.2 各节点的命令定义

  Vtysh中所有的命令节点如上所示。比如VIEW_NODE节点,在VIEW_NODE节点下可以进行show 查看等相关的操作,不能进行任何路由协议相关的配置操作。如果要进行任何配置操作,比如对bgp 或ospf进行配置,则必须进入CONFIG_NODE节点下才能操作。这里的节点下会提前注册一些相关的命令,也是关联的命令视图,即要对frr中某个路由协议进行操作(显示/配置等),必须先进入相应的命令试图下。

//在command.h中定义了注册节点
/* List of CLI nodes. Please remember to update the name array in command.c. */
enum node_type {
    ...
    BGP_NODE,        /* BGP protocol mode which includes BGP4+ */
    BGP_VPNV4_NODE,      /* BGP MPLS-VPN PE exchange. */
    BGP_VPNV6_NODE,      /* BGP MPLS-VPN PE exchange. */
    BGP_IPV4_NODE,       /* BGP IPv4 unicast address family.  */
    BGP_IPV4M_NODE,      /* BGP IPv4 multicast address family.  */
    BGP_IPV4L_NODE,      /* BGP IPv4 labeled unicast address family.  */
    BGP_IPV6_NODE,       /* BGP IPv6 address family */
    BGP_IPV6M_NODE,      /* BGP IPv6 multicast address family. */
    BGP_IPV6L_NODE,      /* BGP IPv6 labeled unicast address family. */
    BGP_VRF_POLICY_NODE,     /* BGP VRF policy */
    BGP_VNC_DEFAULTS_NODE,   /* BGP VNC nve defaults */
    BGP_VNC_NVE_GROUP_NODE,  /* BGP VNC nve group */
    BGP_VNC_L2_GROUP_NODE,   /* BGP VNC L2 group */
    BGP_FLOWSPECV4_NODE,    /* BGP IPv4 FLOWSPEC Address-Family */
    BGP_FLOWSPECV6_NODE,    /* BGP IPv6 FLOWSPEC Address-Family */
    BFD_NODE,        /* BFD protocol mode. */
    BFD_PEER_NODE,       /* BFD peer configuration mode. */
    ...
};

  各个节点的文件夹中,都定义了XXX_vty.c的文件用于注册命令并包含了实现各自功能的api函数,例如frr/bgpd/bgp_vty.h.c文件,frr/vrrpd/vrrp_vty.h.c文件。

//bgp的router bgp命令注册
/* "router bgp" commands. */
DEFUN_NOSH (router_bgp,
       router_bgp_cmd,
       "router bgp [(1-4294967295)$instasn [<view|vrf> VIEWVRFNAME]]",
       ROUTER_STR
       BGP_STR
       AS_STR
       BGP_INSTANCE_HELP_STR)
{
    ...
}

2.3 整个CLI工具的主函数以及各节点命令入口
  登录工具vtysh的CLI中,包含所有支持的模块CLI命令,例如bgp、bfd、ospf。在其源码c文件位置frr/vtysh/vtysh.c中,DEFUN定义了一组vtysh的基础命令,DEFUNSH定义各个节点基础命令exit/quit等。

//定义各个节点类型
struct vtysh_client vtysh_client[] = {
    {.fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .next = NULL},
    {.fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .next = NULL},
    {.fd = -1, .name = "ripngd", .flag = VTYSH_RIPNGD, .next = NULL},
    {.fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .next = NULL},
    {.fd = -1, .name = "ospf6d", .flag = VTYSH_OSPF6D, .next = NULL},
    {.fd = -1, .name = "ldpd", .flag = VTYSH_LDPD, .next = NULL},
    {.fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .next = NULL},
    {.fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .next = NULL},
    {.fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .next = NULL},
    {.fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .next = NULL},
    {.fd = -1, .name = "eigrpd", .flag = VTYSH_EIGRPD, .next = NULL},
    {.fd = -1, .name = "babeld", .flag = VTYSH_BABELD, .next = NULL},
    {.fd = -1, .name = "sharpd", .flag = VTYSH_SHARPD, .next = NULL},
    {.fd = -1, .name = "fabricd", .flag = VTYSH_FABRICD, .next = NULL},
    {.fd = -1, .name = "watchfrr", .flag = VTYSH_WATCHFRR, .next = NULL},
    {.fd = -1, .name = "pbrd", .flag = VTYSH_PBRD, .next = NULL},
    {.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL},
    {.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL},
    {.fd = -1, .name = "vrrpd", .flag = VTYSH_VRRPD, .next = NULL},
};

void vtysh_init_vty(void)
{
    ...
    /* Initialize commands. */
    cmd_init(0);
    cmd_variable_handler_registerregister(vtysh_var_handler);

    /* Install nodes. */
    ...
    install_node(&bgp_node, NULL);
    install_node(&rip_node, NULL);
    install_node(&vrf_node, NULL);
    install_node(&nh_group_node, NULL);
    install_node(&rmap_node, NULL);
    install_node(&pbr_map_node, NULL);
    install_node(&zebra_node, NULL);
    ...
}

//在vtysh_main.c函数中的main函数调用该初始化函数,进而依次执行初始化。这里不一一罗列main函数中命令执行;
/* VTY shell main routine. */
int main(int argc, char **argv, char **env)
{
    ...
    /* Signal and others. */
    vtysh_signal_init();

    /* Make vty structure and register commands. */
    vtysh_init_vty();
    vtysh_init_cmd();
    vtysh_user_init();
    vtysh_config_init();
    vty_init_vtysh();
    ...
    vtysh_readline_init();

    /* Main command loop. */
    while (vtysh_rl_gets())
        vtysh_execute(line_read);

    ...
    /* Rest in peace. */
    exit(0);
}

  通过static int vtysh_execute_func(const char *line, int pager)作为命令行入口函数,引导到各自的节点命令中。

发表评论

邮箱地址不会被公开。 必填项已用*标注