VPP/How to Create a VPP binary control-plane API
Contents
Intro
We need to modify four primary files to add a new binary control-plane API.
- vpp/api/vpe.api
- vpp/api/api.c
- vpp/api/custom_dump.c
- vpp-api-test/vat/api_format.c
I'll describe each one using the set interface l2 bridge command as an example.
While learning about this specific CLI command, I used cscope to search for the command, using "Find this text string" set to set interface l2 bridge which led me to the file open-repo/vnet/vnet/l2/l2_input.c where I traced its operation.
Additions to vpp/api/vpe.api
The first step: determine the parameters the API will need passed to it.
Based on the CLI command "set interface l2 bridge <interface> <bridge-domain> [<split-horizon-group>] [bvi], there are two required parameters and two optional parameters.
- <interface>: In VPE, an ASCII interface name is converted to unsigned 32-bit interface index number and from inspecting existing code, we'll name the field rx_sw_if_index
- <bridge-domain>: this is a numeric and convention calls this field bd_id which is 32 bits
- <split-horizon-group>: this is an optional numeric value internally specified as a 32-bit value which defaults to 0
- bvi: is a flag to indicate this is a "Bridged Virtual Interface" which although is a simple boolean, the underlying function calls treat as an unsigned 32-bit value
In addition to API-related specifics, VPE API requests always include the fields (client_index and context) and a reply structure (the base API name with _reply appended), yielding the following text to add into vpe.api:
define sw_interface_set_l2_bridge { u32 client_index; u32 context; u32 rx_sw_if_index; u32 bd_id; u32 bvi; u32 shg; u8 enable; }; define sw_interface_set_l2_bridge_reply { u32 context; i32 retval; };
Changes to vpp/api/api.c
Once the communicating data structure is defined in vpe.api, two items for the API function must be added to api.c:
Within the #define foreach_vpe_api_msg portion of the file, add the new entry for the API function, in this case:
- _(SW_INTERFACE_SET_L2_BRIDGE, sw_interface_set_l2_bridge) \
(making sure to properly terminate the line ending depending on if it's in the middle of the #define or at the end).
Then add the actual handler function:
static void vl_api_sw_interface_set_l2_bridge_t_handler ( vl_api_sw_interface_set_l2_bridge_t *mp) { /* stuff required for each API function */ vl_api_sw_interface_set_l2_bridge_reply_t * rmp; int rv = 0; /* stuff specific to the sw_interface_set_l2_bridge API function */ u32 rx_sw_if_index = ntohl(mp->rx_sw_if_index); u32 bd_id = ntohl(mp->bd_id); u32 bvi = ntohl(mp->bvi); u32 shg = ntohl(mp->shg); /* stuff specific to this function */ /* stuff required for each API function */ REPLY_MACRO(VL_API_SW_INTERFACE_SET_L2_BRIDGE_REPLY); }
It's probably obvious, but in the prior examples (and the ones that follow), the text SW_INTERFACE_SET_L2_BRIDGE and sw_interface_set_l2_bridge should be changed to the your specific new API function names.
Please remember to convert message structure members from network byte order to local variables in host byte order, as shown above.
This specific example uses REPLY_MACRO to return whatever is placed in the rv variable. Within this macro, (among other things) rv is carefully stuffed into rmp->.
If you need to return additional information, use the REPLY_MACRO2 macro. An example from vl_api_l2_flags:
REPLY_MACRO2(VL_API_L2_FLAGS_REPLY, rmp->resulting_feature_bitmap = ntohl(rbi));
The second argument to REPLY_MACRO2 can contain multiple statements.
Changes to vpp/api/custom_dump.c
Each API entry needs a corresponding *_print function to support debugging VPE coding and configuration issues. In short, this function prints the configuration of each API interaction via the control plane. The output format should show all the information needed to recreate what the control plane without the actual control plane setup.
This function is complemented by an "custom dump parser" (AKA api_format; described in the next section), so it's important that these items are symmetrical in terms of what custom_dump prints, api_format should be able to parse and act upon.
static void *vl_api_sw_interface_set_l2_bridge_t_print (vl_api_sw_interface_set_l2_bridge_t * mp, void *handle) { /* stuff required for all API functions */ u8 * s; s = format (0, "SCRIPT: sw_interface_set_l2_bridge "); /* stuff specific to new API function */ /* stuff required for all API functions */ s [vec_len(s)-1] = 0; vl_print (handle, (char *)s); vec_free(s); return handle; }
Changes to vpp-api-test/vat/api_format.c
As hinted in the custom_dump.c section, vat via the file api_format.c is complementary program to consume output dumps in order to "play back" configurations and setup established via the control plane.
To accomplish this, each API function needs to:
- provide a reply function
- parse dump text and invoke corresponding internal functions
- provide a help string to guide visual inspection of dump files and manual testing via vat tool.
Reply Function
The reply function is specified in two places:
#define foreach_standard_reply_retval_handler \
as
_(sw_interface_set_l2_bridge_reply) \
and in:
#define foreach_vpe_api_reply_msg \
as
_(SW_INTERFACE_SET_L2_BRIDGE_REPLY, sw_interface_set_l2_bridge_reply) \
Standard warnings are given about properly terminating #define statements.
Parsing and function invocation
There are many examples of parsing dump strings in api_format.c but most follow the pattern shown in the example function below.
Here are some things to note:
- the string which invokes the function is based on the original structure name listed in vpe.api so sw_interface_set_l2_bridge in the dump file trigger the function named api_sw_interface_set_l2_bridge .
- for each parameter it's preferred that the function to be tagged such that their order isn't fixed (this is reflected in the parsing function below using a while loop matching possible tags, then possible parameters to those tags.
static int api_sw_interface_set_l2_bridge (vat_main_t * vam) { unformat_input_t * i = vam->input; vl_api_sw_interface_set_l2_bridge_t *mp; f64 timeout; u32 rx_sw_if_index; u8 rx_sw_if_index_set = 0; u32 bd_id; u8 bd_id_set = 0; u32 bvi = 0; u32 shg = 0; u8 enable = 1; /* Parse args required to build the message */ while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) { if (unformat (i, "rx_sw_if_index %d", &rx_sw_if_index)) rx_sw_if_index_set = 1; else if (unformat (i, "bd_id %d", &bd_id)) bd_id_set = 1; else if (unformat (i, "rx")) { if (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) { if (unformat (i, "%U", unformat_sw_if_index, vam, &rx_sw_if_index)) rx_sw_if_index_set = 1; } else break; } else if (unformat (i, "shg %d", &shg)) { /* no specific action beyond unformat needed(?) */ } else if (unformat (i, "bvi")) { bvi = 1; } else if (unformat (i, "enable")) enable = 1; else if (unformat (i, "disable")) enable = 0; else break; } if (rx_sw_if_index_set == 0) { errmsg ("missing rx interface name or rx_sw_if_index\n"); return -99; } if (enable && (bd_id_set == 0)) { errmsg ("missing bridge domain\n"); return -99; } M(SW_INTERFACE_SET_L2_BRIDGE, sw_interface_set_l2_bridge); mp->rx_sw_if_index = ntohl(rx_sw_if_index); mp->bd_id = ntohl(bd_id); mp->bvi = ntohl(bvi); mp->shg = ntohl(shg); mp->enable = enable; S; W; /* NOTREACHED */ return 0; }
Help strings
Help strings are provided through the after the function specification section of api_format.c. This string shows:
- the name of the string
- parameter names inside angle brackets
- optional parameters inside square brackets
As with other #define specified structures inside VPE, take care that the line endings are properly terminated.
_(sw_interface_set_l2_bridge, \ "rx <intfc> | rx_sw_if_index <id> bd_id <bridge-domain-id>\n" \ "[shg <split-horizon-group>] [bvi]\n" \ "enable | disable") \