Adding a menu entry for the CLI

This document shows how to add a menu entry for the CLI. Usually the first thing anyone would want to do for their protocol implementation is to enable that protocol for some of the available interfaces. Thus, in describing the steps to follow, we'll consider adding an Enable option for the RSTP protocol.

Step 1 : setting up protocol information

Most protocols have a small number of configurable parameters that influence the protocol's behaviour. Naturally, we'll want to store these parameters' values somewhere. LiSA stores all such information in a shared memory structure that is protected from concurrent accesses. So the first step is to define our own structure that contains protocol-specific data. Since all we want to do is enable the protocol, our structure will only have a field called enabled. We'll create a header file called rstp_client.h in the include/ folder where we'll keep CLI-interaction related information. We have to include this header in include/shared.h so our structure will be recognized. In this header file we add the following:

struct rstp_configuration {
    unsigned char enabled;
};

The enabled field represents a global option telling us if RSTP support is enabled for the switch. Next, add an entry of this type in the shared memory structure found in lib/shared.c

struct shared {
    //...
    /* CDP configuration */
    struct cdp_configuration cdp;
    /* RSTP configuration */
    struct rstp_configuration rstp;
    //...
};

Finally, we want to be able to access the information we just added so we define setter/getter routines in lib/shared.c

void shared_set_rstp(struct rstp_configuration *rstp)
{
    assert(rstp);
    mm_lock(mm);
    memcpy(&SHM->rstp, rstp, sizeof(struct rstp_configuration));
    mm_unlock(mm);
}
 
void shared_get_rstp(struct rstp_configuration *rstp)
{
    assert(rstp);
    mm_lock(mm);
    memcpy(rstp, &SHM->rstp, sizeof(struct rstp_configuration));
    mm_unlock(mm);
}

Finally, add the two definitions in include/shared.h

//...
 
/* Sets the RSTP global configuration */
void shared_set_rstp(struct rstp_configuration *rstp);
 
/* Gets the RSTP global configuration */
void shared_get_rstp(struct rstp_configuration *rstp);
Step 2 : adding menu entries for global RSTP support

Creating menu entries requires adding struct menu_node fields to the existing menu structure. Adding a global option for enabling RSTP means adding the following code to the config_main structure found in cli/menu/config.c

struct menu_node config_main = {
  /* Root node, .name is used as prompt */
  .name     = "config",
  .subtree  = (struct menu_node *[]) {
    /* #rstp */
    & (struct menu_node){
      .name     = "rstp",
      .help     = "Global RSTP configuration subcommands",
      .mask     = CLI_MASK(PRIV(15)),
      .tokenize = NULL,
      .run      = NULL,
      .subtree  = (struct menu_node *[]) { /*{{{*/
        /* #cdp run */
        & (struct menu_node){
          .name     = "run",
          .help     = "",
          .mask     = CLI_MASK(PRIV(15)),
          .tokenize = NULL,
          .run      = cmd_rstp_run,
          .subtree  = NULL
        },
 
        NULL
      } /*}}}*/
    },
    //...

Also, add an option for disabling RSTP on the switch. This is done Cisco-like by adding no in front of the option. So basically we add an entry in the no subtree.

  /* #no */
    & (struct menu_node){
      .name     = "no",
      .help     = "Negate a command or set its defaults",
      .mask     = CLI_MASK(PRIV(15)),
      .tokenize = NULL,
      .run      = NULL,
      .subtree  = (struct menu_node *[]) { /*{{{*/
        /* #no rstp */
        & (struct menu_node){
          .name     = "rstp",
          .help     = "Global RSTP configuration subcommands",
          .mask     = CLI_MASK(PRIV(15)),
          .tokenize = NULL,
          .run      = NULL,
          .subtree  = (struct menu_node *[]) { /*{{{*/
            /* #no rstp run */
            & (struct menu_node){
              .name     = "run",
              .help     = "",
              .mask     = CLI_MASK(PRIV(15)),
              .tokenize = NULL,
              .run      = cmd_rstp_run,
              .subtree  = NULL
            },
 
            NULL
          } /*}}}*/
        },
    //...

The position of the entry in the code reflects its position in the actual menu. That is, if the first entry in the subtree of config_main is the struct menu_node rstp then it will also be the first entry in the CLI menu. Notice than when one of the entries is selected then the cmd_rstp_run routine is called.

Step 3: implementing the global RSTP enable routine

The run field from struct menu_node represents a pointer to a function having the following signature : int (struct cli_context *, int, char , struct menu_node ). For a detailed description see the struct menu_node definition in cli/cli.h. So, first thing to do is define a routine called cmp_rstp_run having this signature in the cli/command/config_if.h header.

The routine's implementation will reside in cli/command/config_if.c. It gets the rstp_configuration data we previously defined and sets/resets the enabled field according to the given option. It then writes the configuration back in the shared mem.

int cmd_rstp_run(struct cli_context *ctx, int argc, char **argv, struct menu_node **nodev)
{
    struct rstp_configuration rstp;
    int enabled = 1;
 
    if (!strcmp(nodev[0]->name, "no"))
            enabled = 0;
    shared_get_rstp(&rstp);
    rstp.enabled = enabled;
    shared_set_rstp(&rstp);
 
    return CLI_EX_OK;
}
Step 4 : adding menu entries for enabling RSTP on a specific interface

Now that we have the protocol infrastructure in place, we can add menu entries for enabling RSTP on a certain interface. We want the menu entry to be available only for the selected interface. Only after we've given the commands conf t and int EthX should the entry become available. So we have to edit the cli/menu/config_if.c file, because that's where interface-related commands are. To add the enable option to the menu, we add this to the mentioned file:

//...
struct menu_node config_if_main = {
  /* Root node, .name is used as prompt */
  .name     = "config-if",
  .subtree  = (struct menu_node *[]) {
 
    /* #rstp */
    & (struct menu_node){
      .name     = "rstp",
      .help     = "RSTP interface subcommands",
      .mask     = CLI_MASK(PRIV(2), IFF_SWITCHED),
      .tokenize = NULL,
      .run      = NULL,
      .subtree  = (struct menu_node *[]) {
        /* #rstp enable */
        & (struct menu_node){
          .name     = "enable",
          .help     = "Enable RSTP on interface",
          .mask     = CLI_MASK(PRIV(2)),
          .tokenize = NULL,
          .run      = cmd_rstp_if_set,
          .subtree  = NULL
        },
 
        NULL
      }
    },
 
    //...

The result of this is that the first entry from the config-if submenu is rstp enable. To position the entry elsewhere (that is, to make it be the 2nd,3rd entry etc.) simply move the piece of code after the similar entries found in struct config_if_main. Of course we also have to add the disable option. To keep things similar to Cisco commands, we add the following piece of code under the no submenu in the same file:

//...
/* #no */
    & (struct menu_node){
      .name     = "no",
      .help     = "Negate a command or set its defaults",
      .mask     = CLI_MASK(PRIV(2)),
      .tokenize = NULL,
      .run      = NULL,
      .subtree  = (struct menu_node *[]) { /*{{{*/
 
        /* #no rstp */
        & (struct menu_node){
          .name     = "rstp",
          .help     = "RSTP interface subcommands",
          .mask     = CLI_MASK(PRIV(2)),
          .tokenize = NULL,
          .run      = NULL,
          .subtree  = (struct menu_node *[]) { /*{{{*/
            /* #no rstp enable */
            & (struct menu_node){
              .name     = "enable",
              .help     = "Enable RSTP on interface",
              .mask     = CLI_MASK(PRIV(2)),
              .tokenize = NULL,
              .run      = cmd_rstp_if_set,
              .subtree  = NULL
            },
 
            NULL
          } /*}}}*/
        },
        //...

Notice that the routine called when the entries are selected is cmd_rstp_if_set. So…

Step 5 : implementing the interface enable/disable routine

The cmd_rstp_if_set routine's job is to send a message to the daemon implementing the protocol that it wants a specific interface to be enabled/disabled. This requires an interprocess communication mechanism between the daemon and the CLI process. It involves the daemon creating a thread specifically for this task. The details are not related to the menu entries so they won't be described here.