Difference between revisions of "VPP/How To Use The C API"

From fd.io
< VPP
Jump to: navigation, search
Line 12: Line 12:
  
 
The API at the C source code level
 
The API at the C source code level
    - All API functions are described in C header files.
+
* All API functions are described in C header files.
    - All interaction with the VPP API is through direct calls to C functions.
+
* All interaction with the VPP API is through direct calls to C functions.
  
 
There are several VPP related libraries of code available to you,
 
There are several VPP related libraries of code available to you,
 
the C application developer.  Here is a quick overview of the libraries:
 
the C application developer.  Here is a quick overview of the libraries:
  
    vlib
+
* vlib
    vlibapi
+
* vlibapi
    vlibmemory
+
* vlibmemory
    vlibsocket - Collectively, the bulk of the VPP C-function API calls.
+
* vlibsocket
 +
*: Collectively, the bulk of the VPP C-function API calls.
  
    vpp - VPP messaging API proper.
+
* vpp
 +
*: VPP messaging API proper.
  
    vppinfra - Some basic infrastructure pieces.  Vectors, hash, bitops, etc.
+
* vppinfra
 +
*: Some basic infrastructure pieces.  Vectors, hash, bitops, etc.
  
    vpp_plugins
+
* vpp_plugins
  
    dpdk - The Data-plane development kit.  Primary purpose is two-fold:
+
* dpdk
First, to receive networking packets from an interface and place
+
*: The Data-plane development kit.  The primary purpose is two-fold:
them in a convenient, vector in memory Second, to take network
+
*:* First, to receive networking packets from an interface and place them in a convenient, vector in memory.
packets from an in-memory vector and send them out on an interface.
+
*:* Second, to take network packets from an in-memory vector and send them out on an interface. This library, though built as part of VPP, has its own project and development here: FIXME: DPDK URL ref.
This library, though built as part of VPP, has its own project and
+
development here: FIXME: DPDK URL ref.
+
  
    jvpp-common
+
* jvpp-common
    pneum
+
* pneum
    svm
+
* svm
    vat
+
* vat
    vnet
+
* vnet
 +
 
 +
=== The Runtime Environment ===
  
 
The runtime environment for the application is explicitly multi-threaded.
 
The runtime environment for the application is explicitly multi-threaded.
Line 55: Line 58:
 
First, there is a simple "request and reply" type.  These messages
 
First, there is a simple "request and reply" type.  These messages
 
are a simple exchange of data initiated by you, the Application Writer,
 
are a simple exchange of data initiated by you, the Application Writer,
and cause a single reply message to bereturned from VPP back to you.
+
and cause a single reply message to be returned from VPP back to you.
 
You construct a message, and you get a reply back.
 
You construct a message, and you get a reply back.
  
 
The initiating request message of this form all have names that
 
The initiating request message of this form all have names that
 
DO NOT end in "DUMP".  For example VL_API_SW_INTERFACE_ADD_DEL_ADDRESS.
 
DO NOT end in "DUMP".  For example VL_API_SW_INTERFACE_ADD_DEL_ADDRESS.
Its corresponding relpy message is the exact same name with "_REPLY"
+
Its corresponding reply message is the exact same name with "_REPLY"
 
as a suffix.  You are expected to write a handler for this reply
 
as a suffix.  You are expected to write a handler for this reply
 
with the name sw_interface_add_del_address_reply() and prototype
 
with the name sw_interface_add_del_address_reply() and prototype
Line 70: Line 73:
 
</pre>
 
</pre>
  
The other type of message exchange is a "dump" request and a repeated
+
The second type of message exchange is a "dump" request and a repeated
 
reply message in the form of multiple "details".  The requesting message
 
reply message in the form of multiple "details".  The requesting message
 
has a name ending in the suffix "_DUMP", and a reply message ending
 
has a name ending in the suffix "_DUMP", and a reply message ending
Line 87: Line 90:
 
reply handlers (both simple and details) that you need to keep in mind.
 
reply handlers (both simple and details) that you need to keep in mind.
  
First, reply handlers are run aysnchronously.
+
First, reply handlers are run asynchronously.
  
 
Think about it for a second.  You make some API message request.
 
Think about it for a second.  You make some API message request.
Line 94: Line 97:
 
When, exactly, will that be?  The answer is simple:  Whenever it can.
 
When, exactly, will that be?  The answer is simple:  Whenever it can.
 
In fact, it could be being called in a different thread of execution
 
In fact, it could be being called in a different thread of execution
while your mailine app just kept slogging along toward the next API
+
while your mainline app just kept slogging along toward the next API
 
call it wants to make, and maybe even *does* make.
 
call it wants to make, and maybe even *does* make.
  
Line 106: Line 109:
  
 
Because a details handler may be called multiple times as the result
 
Because a details handler may be called multiple times as the result
of a asingle dump request, it must be terminated with a CONTROL_PING
+
of a single dump request, it must be terminated with a CONTROL_PING
 
message.  The purpose of the control ping message is to ensure that
 
message.  The purpose of the control ping message is to ensure that
 
the queue of sending-side messages waits for completion of all of the
 
the queue of sending-side messages waits for completion of all of the
sent details before allowing it to continu with new sent messages.
+
sent details before allowing it to continue with new sent messages.
  
 
This waiting-until-complete mechanism is enforced in a few message
 
This waiting-until-complete mechanism is enforced in a few message
Line 122: Line 125:
 
the definition of messages used by that module.
 
the definition of messages used by that module.
  
Sripts then process that API file and turn it into various C header
+
Scripts then process that API file and turn it into various C header
 
definitions with types, message names, print functions, etc.
 
definitions with types, message names, print functions, etc.
 
The API files are exported and installed with names like "vpe.api.h"
 
The API files are exported and installed with names like "vpe.api.h"
Line 143: Line 146:
  
 
I'm going to talk about the three of these (vl_msg_id, vl_typedefs,
 
I'm going to talk about the three of these (vl_msg_id, vl_typedefs,
nd vl_msg_name_crc_list) in some detail, and then just blindly reference
+
and vl_msg_name_crc_list) in some detail, and then just blindly reference
 
the others, wave my hands and vaguely suggest you do something similar.
 
the others, wave my hands and vaguely suggest you do something similar.
  
Line 206: Line 209:
 
</pre>
 
</pre>
  
Good, but we have to be careful here.  Indiscriminantly applying this
+
Good, but we have to be careful here.  Indiscriminately applying this
 
approach to all sections of the various *.api.h won't be right.
 
approach to all sections of the various *.api.h won't be right.
  
 
Some of the sections expect a context for processing in, say, a C function,
 
Some of the sections expect a context for processing in, say, a C function,
 
and not just a declaration context at the C file scope.  In fact, some
 
and not just a declaration context at the C file scope.  In fact, some
of the sections expect a #define functional definition that is appied
+
of the sections expect a #define functional definition that is applied
 
to each *something* in the list.  I know that sounds a bit vague.
 
to each *something* in the list.  I know that sounds a bit vague.
 
So let's see it in action.
 
So let's see it in action.
Line 238: Line 241:
  
 
While this idiom produces declarations at the C file level, it shows
 
While this idiom produces declarations at the C file level, it shows
the use of an dditional processing macro that might be defined during the
+
the use of an additional processing macro that might be defined during the
 
processing of the entire section of the *.api.h file.
 
processing of the entire section of the *.api.h file.
  
Line 374: Line 377:
  
 
I'd like to say it was all just that easy.  But I'd be lying.
 
I'd like to say it was all just that easy.  But I'd be lying.
There are two wrinkles swiring around the actual API message numbers
+
There are two wrinkles swirling around the actual API message numbers
 
that you have to understand.  First, the enumerated list as found
 
that you have to understand.  First, the enumerated list as found
 
in the *.api.h files can lie to you unless you have some conditional
 
in the *.api.h files can lie to you unless you have some conditional
Line 408: Line 411:
 
structure is constructed in place in the API file beside each message
 
structure is constructed in place in the API file beside each message
 
that it is willing to name.  And you, the application writer, have
 
that it is willing to name.  And you, the application writer, have
to supply both the messge name, and the CRC value to VPP when you
+
to supply both the message name, and the CRC value to VPP when you
 
request the *real* message id.
 
request the *real* message id.
  
It goes like this.  First declare a gobal message id map:
+
It goes like this.  First declare a global message id map:
  
 
<pre>
 
<pre>
Line 557: Line 560:
 
with a base_msg_id in it.  And I captured and stored the base
 
with a base_msg_id in it.  And I captured and stored the base
 
message id in that global.  What you can't see yet, is that
 
message id in that global.  What you can't see yet, is that
I also buried the use of that variable inside of the fuction
+
I also buried the use of that variable inside of the function
 
called vmid_map_fn(), used as the first parameter to the message
 
called vmid_map_fn(), used as the first parameter to the message
 
registration API call.
 
registration API call.
Line 641: Line 644:
 
</pre>
 
</pre>
  
On the other hand, here are two globals that the VPP libary must have,
+
On the other hand, here are two globals that the VPP library must have,
 
does not supply itself, and instead you must:
 
does not supply itself, and instead you must:
  
Line 704: Line 707:
 
     ntohs(), htons() u16 or short data
 
     ntohs(), htons() u16 or short data
  
Single-byte quantites need no byte swapping, right?  Right.
+
Single-byte quantities need no byte swapping, right?  Right.
  
 
Larger or bytes-stream like values need careful handling too.
 
Larger or bytes-stream like values need careful handling too.
Line 714: Line 717:
 
Ooo, yeah, IP addresses.  Let's talk about those for a  minute.
 
Ooo, yeah, IP addresses.  Let's talk about those for a  minute.
 
These days, there are both IPv4 and IPv6 addresses.  The former
 
These days, there are both IPv4 and IPv6 addresses.  The former
are 4-bytes long, usually respresented as a dotted quad (10.10.220.1)
+
are 4-bytes long, usually represented as a dotted quad (10.10.220.1)
 
or as the actual string of a dotted quad ("10.10.220.1").
 
or as the actual string of a dotted quad ("10.10.220.1").
 
The latter is a 16-byte mess usually represented as a prefix and
 
The latter is a 16-byte mess usually represented as a prefix and
Line 722: Line 725:
 
I'm not going to describe how to convert from a string-form of
 
I'm not going to describe how to convert from a string-form of
 
an IP address into the equivalent byte-stream from first principles.
 
an IP address into the equivalent byte-stream from first principles.
Instead I will leave you with these code snipets.
+
Instead I will leave you with these code snippets.
  
 
You might use something like this on a request message:
 
You might use something like this on a request message:
Line 755: Line 758:
 
                 inet_ntop(AF_INET, mp->ip, addrbuf, sizeof(addrbuf));
 
                 inet_ntop(AF_INET, mp->ip, addrbuf, sizeof(addrbuf));
 
         }
 
         }
<pre>
+
</pre>
  
 
If you don't want to use inet_ntop() and inet_pton(), feel free to
 
If you don't want to use inet_ntop() and inet_pton(), feel free to

Revision as of 16:52, 9 March 2017

Introduction

So, VPP is pretty cool and you want to use it to make your own router. Yeah, me too! You could write it using the Python, Java or Lua API to VPP. But why use one of those classically hip languages when you could go old-school and use the C API?

Now that we are on the same page, let's dig in!

C API Overview

The API at the C source code level

  • All API functions are described in C header files.
  • All interaction with the VPP API is through direct calls to C functions.

There are several VPP related libraries of code available to you, the C application developer. Here is a quick overview of the libraries:

  • vlib
  • vlibapi
  • vlibmemory
  • vlibsocket
    Collectively, the bulk of the VPP C-function API calls.
  • vpp
    VPP messaging API proper.
  • vppinfra
    Some basic infrastructure pieces. Vectors, hash, bitops, etc.
  • vpp_plugins
  • dpdk
    The Data-plane development kit. The primary purpose is two-fold:
    • First, to receive networking packets from an interface and place them in a convenient, vector in memory.
    • Second, to take network packets from an in-memory vector and send them out on an interface. This library, though built as part of VPP, has its own project and development here: FIXME: DPDK URL ref.
  • jvpp-common
  • pneum
  • svm
  • vat
  • vnet

The Runtime Environment

The runtime environment for the application is explicitly multi-threaded.


Message API Review

This section is a quick review of material from the API Concepts Wiki page.

There are two basic types of message exchanges that you can have with the VPP library.

First, there is a simple "request and reply" type. These messages are a simple exchange of data initiated by you, the Application Writer, and cause a single reply message to be returned from VPP back to you. You construct a message, and you get a reply back.

The initiating request message of this form all have names that DO NOT end in "DUMP". For example VL_API_SW_INTERFACE_ADD_DEL_ADDRESS. Its corresponding reply message is the exact same name with "_REPLY" as a suffix. You are expected to write a handler for this reply with the name sw_interface_add_del_address_reply() and prototype

void
vl_api_sw_interface_add_del_address_reply_t_handler(
			vl_api_sw_interface_add_del_address_reply *mp);

The second type of message exchange is a "dump" request and a repeated reply message in the form of multiple "details". The requesting message has a name ending in the suffix "_DUMP", and a reply message ending in the suffix "_DETAILS". For example, the message VL_API_IP_ADDRESS_DUMP would have a reply handler function with the following prototype:

void
vl_api_ip_address_details_t_handler(vl_api_ip_address_details_t *mp);


Important Information about Reply Handlers

There are a few critical pieces of information about reply handlers (both simple and details) that you need to keep in mind.

First, reply handlers are run asynchronously.

Think about it for a second. You make some API message request. Fire it off into the library and forget about it. And then what? Well, at *some point*, you are told, the reply handler will run. When, exactly, will that be? The answer is simple: Whenever it can. In fact, it could be being called in a different thread of execution while your mainline app just kept slogging along toward the next API call it wants to make, and maybe even *does* make.

The point is, the reply handlers could be running simultaneously with other code in your application. Are you locking your shared data structures? Do they need locks? Are you writing independent code and data streams that won't interfere with each other? Good.

Second, the dump/details style messages need to be finished using an additional, final CONTROL_PING message.

Because a details handler may be called multiple times as the result of a single dump request, it must be terminated with a CONTROL_PING message. The purpose of the control ping message is to ensure that the queue of sending-side messages waits for completion of all of the sent details before allowing it to continue with new sent messages.

This waiting-until-complete mechanism is enforced in a few message send and wait primitives discussed below.


API Include Files

As with all program developers, laziness is considered a virtue. The VPP development is no exception! Each module of the VPP library has a single "API description file" that is the single source for the definition of messages used by that module.

Scripts then process that API file and turn it into various C header definitions with types, message names, print functions, etc. The API files are exported and installed with names like "vpe.api.h" or "snat.api.h" under /usr/include/vpp/api or /usr/include/vpp_plugins.

Within the various api.h are different sections that supply a vast set of definitions or data about each message within that group of API messages.

As of this writing, the various sections are:

  vl_msg_id
  vl_union_id
  vl_printfun
  vl_endianfun
  vl_api_version
  vl_typedefs
  vl_msg_name
  vl_msg_name_crc_list

I'm going to talk about the three of these (vl_msg_id, vl_typedefs, and vl_msg_name_crc_list) in some detail, and then just blindly reference the others, wave my hands and vaguely suggest you do something similar.

Here is the trick. Each of these sections constructs lists of messages and data about messages within the header files. Then, you are expected to *do* something with this list. There are a couple idioms used here. You should familiarize yourself with them.

First, the bulk declarations.

If you crack open, say, vpe.api.h, somewhere in there you will see a section of code all surrounded by an #ifdef like this:

/****** Typedefs *****/

#ifdef vl_typedefs

typedef VL_API_PACKED(struct _vl_api_create_vlan_subif {
    u16 _vl_msg_id;
    u32 client_index;
    u32 context;
    u32 sw_if_index;
    u32 vlan_id;
}) vl_api_create_vlan_subif_t;

...

You are expected to #define vl_typedefs and #include the vpe.api.h file before you can use any of these message typedefs. Like this:

#define vl_typedefs
#include <vpp/api/vpe_all_api_h.h>
#undef vl_typedefs

In fact, you will need to do something similar for each section of each *.api.h file you want. So you could do this:

#define vl_typedefs
#include <vpp/api/vpe_all_api_h.h>
#undef vl_typedefs

#define vl_endianfun
#include <vpp/api/vpe_all_api_h.h>
#undef vl_endianfun
...

That can be simplified so that just one #include takes place:

#define vl_typedefs
#define vl_endianfun
#include <vpp/api/vpe_all_api_h.h>
#undef vl_typedefs
#undef vl_endianfun
...

Good, but we have to be careful here. Indiscriminately applying this approach to all sections of the various *.api.h won't be right.

Some of the sections expect a context for processing in, say, a C function, and not just a declaration context at the C file scope. In fact, some of the sections expect a #define functional definition that is applied to each *something* in the list. I know that sounds a bit vague. So let's see it in action.

Let's say you want (and you do!) a message-specific printf() function for debug purposes, you don't have to write them all. They are almost free:

#define vl_print(handle, ...)	vlib_cli_output (handle, __VA_ARGS__)
#define vl_printfun
#include <vpp/api/vpe_all_api_h.h>
#undef vl_printfun

Notice here we had to both define a vl_print() function to say what was to be done with each member within the bl_printfun section of the *.api.h file. In this case, the buried calls to vl_print() are rewritten as an actual output routine from VLIB called vlib_cli_output().

If you wanted no output, you could easily have instead written:

#define vl_print(handle, ...)

While this idiom produces declarations at the C file level, it shows the use of an additional processing macro that might be defined during the processing of the entire section of the *.api.h file.

Another idiom frequently employed allows you to apply a #define operation to each member of a list. For example, the section that processes the message names and CRC list looks like this:

/****** Message name, crc list ******/

#ifdef vl_msg_name_crc_list
#define foreach_vl_msg_name_crc_vpe \
_(VL_API_CREATE_VLAN_SUBIF, create_vlan_subif, af9ae1e9) \
_(VL_API_CREATE_VLAN_SUBIF_REPLY, create_vlan_subif_reply, 8f36b888) \
...

It then lists each and every message, by name, and its CRC value. Here, you are expected to #define an operation called "_", that will be applied to each of those message-crc lines.

I'll show where, why and how to do this below.


VPE vs. Plugin Messages

In all of the discussion above, I have meticulously referenced either the "vpe.api.h" or something vague like "the various *.api.h" files. That is because I was dodging an issue in order to simplify the discussion above.

But now it is time to face that fate head on!

The VPP library presents you, the application developer, with two types of modules: the base module (AKA "vpe"), and plugins. In fact several plugins. You must use the base module, vpe, but you can opt to or opt not to use each plugin.

As of this writing, the plugins that have API module-level support are ACL and SNAT. DPDK as a plugin is in the works and coming soon. By library name, there are also plugins for flowerpkt, ioam, ila and lb.

I'm going to use SNAT as a point of discussion here.

Message Registration

In order to use the messages provided by a *.api.h file, (yes, either the vpe.api.h, or any of the plugin api files like snat.api.h), you must first register your intent to use the message by stating what the handler will be for the reply (or details) message that result

Here is an example registration call for the VL_API_CONTROL_PING_REPLY message.

        vl_msg_api_set_handlers(VL_API_CONTROL_PING_REPLY,
                                control_ping_reply,
                                vl_api_control_ping_reply_t_handler,
				vl_noop_handler,
                                vl_api_control_ping_t_endian,
                                vl_api_control_ping_t_print,
                                sizeof(vl_api_control_ping_t),
				1);

The prototype for this call is found in "vlibapi/api.h":

void vl_msg_api_set_handlers (int msg_id,
                              char *msg_name,
                              void *handler,
                              void *cleanup,
                              void *endian,
                              void *print,
                              int msg_size,
                              int traced);

While it is hard to tell even from the prototype, all of the parameters handler, cleanup, endian, and print are function names. The endian and print handlers can be automatically generated from the *.api.h files using the #define vl_endianfun and vl_printfun, respectively. The cleanup function was summarily tossed at the vl_noop_handler.

On the other hand, the important function here is the one named vl_api_control_ping_reply_t_handler(). Like all of the messages you intend to send to the VPP library as an API call, you must supply a reply (generic) handler. That handler must be type-matched to each message and it must be named either <msg_name>_reply_t_handler() if it is a simple reply handler, or <msg_name>_details_t_handler() if it is a "dump" style reply handler.

You are going to write a bunch of reply handlers. One for every VPP message. It could get tedious. To simplify some of the tedium, and yet at the same time obscure it behind some C token pasting the folowing idiom has become the accepted norm.

First, list the messages for which you want replies. This includes both the simple replies and the details replies. For the VPE module, we'll stat with just one message, the so-called "control-ping":

#define foreach_vpe_api_reply_msg                               \
        _(CONTROL_PING_REPLY, control_ping_reply)

Then, inside a C function, call the message registration API:

static void vpp_vpe_init(void)
{
#define _(N,n)                                                  \
        vl_msg_api_set_handlers(VL_API_##N,                     \
                                #n,                             \
                                vl_api_##n##_t_handler,         \
                                vl_noop_handler,                \
                                vl_api_##n##_t_endian,          \
                                vl_api_##n##_t_print,           \
                                sizeof(vl_api_##n##_t), 1);
        foreach_vpe_api_reply_msg;
#undef _
}

When you need to handle another message, just add it to the list:

#define foreach_vpe_api_reply_msg                               \
        _(CONTROL_PING_REPLY, control_ping_reply)               \
        _(ANOTHER_MSG_REPLY, another_msg_reply)                 \
        _(SOME_OTHER_MSG_REPLY, some_other_msg_reply)


Will the Real Message Id Please Stand Up?

I'd like to say it was all just that easy. But I'd be lying. There are two wrinkles swirling around the actual API message numbers that you have to understand. First, the enumerated list as found in the *.api.h files can lie to you unless you have some conditional knowledge about exactly how the corresponding VPP binary code that implements the API calls was compiled. Hint, you don't. And furthermore, you can't.

The second complicating issue is that each plugin is optional. It may or may not be present in your application. And the order it is loaded may or may not correspond to how the plugin in VPP libraries are loaded. So you don't know what their message ids actually are. Yet.

So I'll explain how to get the real API message ids.

Message Ids for VPE Messages

There is no One True Global Message Id space that has all of the messages laid out once-and-for-all. Instead, the base messages from the VPE module are always loaded first. They occupy the message ids starting at 0, and end, well, wherever they want to. You don't know and I don't know.

In fact, there could be gaps in this message space or shifting of message ids around as parts are conditionally compiled in or out. The only way to know is to first call into VPP using a different C function to request the real message id. No, really. So save this returned value in a map.

Oh, and, to keep everyone honest, and to make sure that both your app and the vlib implementation are talking about the *same* message, with the same fields and contents, a CRC hash value of the message structure is constructed in place in the API file beside each message that it is willing to name. And you, the application writer, have to supply both the message name, and the CRC value to VPP when you request the *real* message id.

It goes like this. First declare a global message id map:

/*
 * VPP API requires a dynamic mapping for message ids
 * based on the message name and a CRC of the message structure.
 */
struct vpp_msg_map *vmid_map;

then modify the vpp_vpe_init() function, above, so that it constructs the message id map first, then registers it using the *real* id:

static void vpp_vpe_init(void)
{
        /*
         * Construct a mapping from <message-name_CRC> to msg ID.
         */
        vmid_map = calloc(VL_MSG_FIRST_AVAILABLE, sizeof(*vmid_map));

#define _(msg_id, name, crc)    \
        vpp_set_msg_id_map(msg_id, #name "_" #crc);

        foreach_vl_msg_name_crc_vpe;
#undef _

#define _(N,n)                                                  \
        vl_msg_api_set_handlers(vmid_map[VL_API_##N].id,                \
                                #n,                             \
                                vl_api_##n##_t_handler,         \
                                vl_noop_handler,                \
                                vl_api_##n##_t_endian,          \
                                vl_api_##n##_t_print,           \
                                sizeof(vl_api_##n##_t), 1);
        foreach_vpe_api_reply_msg;
#undef _
}

If we *think* we want to send message VL_API_<something>, we *really* have to set the message id to be vmid_map[VL_API_<something>] instead.

OK, that takes care of the base message ids from the VPE module. Let's turn to plugin message ids now.

Message Ids for a Plugin's Messages

Luckily, VPP remembered where all the VPE messages landed, and kept track of the last-used-message-id. Now when the first plugin is loaded, its messages get assigned a base id somewhere after the VPE messages, and are then linearly present for all of the plugin's messages, ending somewhere. Again, we can only surmise it to be "this module's base id" plus "the number of messages in this module."

So, as each plugin is loaded, it is assigned a base message id.

Ooo, we better keep track of the base message id for each plugin. So, here is the magic incantation, er, API call sequence to do that for the SNAT plugin. Others are similar!

        u8 *plugin_name;	/* Yeah, like "char *" */
        u16 base_msg_id;

        /*
         * Determine the base msg-id for SNAT plugin messages.
         */
        plugin_name = format(0, "snat_%08x%c", snat_api_version, 0);
	base_msg_id = vl_client_get_first_plugin_msg_id((char *)plugin_name);
	if (base_msg_id == (u16) ~0) {
                vec_free(plugin_name);
                return -1;
        }

Now if you are really following along at home, you, the astute reader and Application Builder, are hopefully asking yourself a question similar to this: "So, if we had to register the VPE messages using a mapped message id, what do we do for the plugin message ids?"

Good question. It is similar, naturally, but just a bit different. Again, we begin by declaring a list of the message replies that will be interesting to us:

#define foreach_snat_api_reply_msg                                      \
        _(SNAT_ADD_ADDRESS_RANGE_REPLY, snat_add_address_range_reply)   \
        _(SNAT_INTERFACE_ADD_DEL_FEATURE_REPLY,                         \
          snat_interface_add_del_feature_reply)                         \
        _(SNAT_ADD_STATIC_MAPPING_REPLY, snat_add_static_mapping_reply)
...

And then in a C function, determine the base address and call the message registration, as before-ish:

/*
 * SNAT Private Data
 */
struct snat_data {
        u16 base_msg_id;
};

struct snat_data snat_data;


int vpp_snat_init(void)
{
        u8 *plugin_name;
        u16 base_msg_id;

        fprintf(stderr, "%s: Starting\n", __func__);

        /*
         * Determine the base msg-id for SNAT plugin messages.
         */
        plugin_name = format(0, "snat_%08x%c", snat_api_version, 0);
        base_msg_id = vl_client_get_first_plugin_msg_id((char *)plugin_name);
        if (base_msg_id == (u16) ~0) {
                vec_free(plugin_name);
                return -1;
        }

        snat_data.base_msg_id = base_msg_id;
        fprintf(stderr, "%s: Base message_id is %d\n", __func__, base_msg_id);

#define _(N,n)                                                  \
        vl_msg_api_set_handlers(vmid_map_fn(VL_API_##N),        \
                                #n,                             \
                                vl_api_##n##_t_handler,         \
                                vl_noop_handler,                \
                                vl_api_##n##_t_endian,          \
                                vl_api_##n##_t_print,           \
                                sizeof(vl_api_##n##_t), 1);
        foreach_snat_api_reply_msg;
#undef _

        return 0;
}

So, notice two things here. I created a "snat_data" structure with a base_msg_id in it. And I captured and stored the base message id in that global. What you can't see yet, is that I also buried the use of that variable inside of the function called vmid_map_fn(), used as the first parameter to the message registration API call.

You might correctly surmise that the purpose of this vmid_map_fn() is to correctly map to a 0-based message set if it is a VPE message, and a snat_data.base_msg_id-based message set if it is a SNAT message.

You would be correct.

Enough talk. Let's build an application now.


Building Your C Application

A simple and direct application will likely need to use the following libraries during its link:

vlibmemoryclient vlibapi svm vppinfra pthread m rt

That is, use a linker line like this:

VPP_LIBS   = vlibmemoryclient vlibapi svm vppinfra pthread m rt
VPP_LDADD  = $(foreach lib,$(VPP_LIBS),-l$(lib) )

LDFLAGS    += $(VPP_LDADD)
LDFLAGS    += $(VPP_LDFLAGS)
LDFLAGS    += -Wl,-soname -Wl,$(LIBNAME).so.0

$(SOFILE): ${OBJS}
        $(QUIET_SLINK)$(CC) -o $(SOFILE) -shared $(CFLAGS) $(LDFLAGS) ${OBJS}

You recognize Makefile snippets when you see them, right? Good.

You will want both /usr/include and /usr/include/vpp_plugins on your list of INCLUDES.

INCLUDES += -I/usr/include/vpp_plugins
CFLAGS   += $(INCLUDES)


Initialization

To get an application started, let's construct a file where we will set up the main connection to the VPP library and start the process of registering the API messages. I did this in a file called api_setup.c.


The VPP library supplies a global you will need to reference but not declare:

api_main_t *api_main;

Not surprisingly, it comes from vlibapi/api.h!

These are several more globals that are needed; they are not really necessary to the VPP librariy itself, but they will be needed to hold some important information about the API connection with VLIB.

Here is the set I made up and used. Place these all in a structure, or leave them bare. Your call:

static int api_connected;
static char api_connection_name[API_NAME_LEN];
static unix_shared_memory_queue_t *api_vl_input_queue;
static u32 api_client_index;
static volatile int api_result_ready;
static volatile int api_result_retval;
static clib_time_t api_clib_time;

On the other hand, here are two globals that the VPP library must have, does not supply itself, and instead you must:

vlib_main_t vlib_global_main;
vlib_main_t **vlib_mains;


The easiest initialization and connection to the VPP library might look like this:

static int vpp_connect_to_vlib(char *name)
{
        api_main_t *am;

        if (vl_client_connect_to_vlib("/vpe-api",
                                      api_connection_name,
                                      API_RX_Q_SIZE) < 0) {
                svm_region_exit();
                return -1;
        }

        am = &api_main;
        api_vl_input_queue = am->shmem_hdr->vl_input_queue;
        api_client_index = am->my_client_index;
        api_connected = 1;

        return 0;
}

You might have something that disconnects, reconnects, needs a retry or whatever. So keep track of api_connected or not. Have your message sending ensure connection prior to launching a message. Whatever.


Sending and Receiving Messages

Using Message Fields

All data in the messages and in the replies are in "network byte order". That means when constructing a message to send to VPP, you must convert each field of host-byte-ordered data into network-byte-order like this:

   mp = allocate-some-message-structure();
   mp->sw_if_index = htonl(my_sw_if_index);
   send-some-message(mp);

Conversely, upon receiving a reply, do the inverse in a reply or details handler:

   my_sw_if_index = ntohl(mp->sw_if_index);

Naturally, use the operation of the correct size:

   ntohl(), htonl()	u32 or long data
   ntohs(), htons()	u16 or short data

Single-byte quantities need no byte swapping, right? Right.

Larger or bytes-stream like values need careful handling too. As strings are represented as an array of u8 values, simply supply a copy of the byte array. Be careful to honor a 0 termination (normal C string) or counted quantity (like an IP address).

Ooo, yeah, IP addresses. Let's talk about those for a minute. These days, there are both IPv4 and IPv6 addresses. The former are 4-bytes long, usually represented as a dotted quad (10.10.220.1) or as the actual string of a dotted quad ("10.10.220.1"). The latter is a 16-byte mess usually represented as a prefix and host portion separated by colons constructed in an obscure, closely guarded, and secret incantation.

I'm not going to describe how to convert from a string-form of an IP address into the equivalent byte-stream from first principles. Instead I will leave you with these code snippets.

You might use something like this on a request message:

	char *ip_str;
        u8 ip_address[16];
	u8 is_ipv6;

	mp = allocate-some-message-with-an-ip-address-field();
	mp->is_ipv6 = is_ipv6;

	if (is_ipv6) {
	        ip_str = "::1";
		inet_pton(AF_INET6, ip_str, &ip_address);
		cli_memcpy(mp->ip_address, ip_address, sizeof(ip6_address_t))
	} else {
	        ip_str = "192.168.7.13";
		inet_pton(AF_INET, ip_str, &ip_address);
		cli_memcpy(mp->ip_address, ip_address, sizeof(ip4_address_t))
	}

And you might have something like this in a reply or details handler:

        char addrbuf[40];

	if (is_ipv6) {
                inet_ntop(AF_INET6, mp->ip, addrbuf, sizeof(addrbuf));
        } else {
                inet_ntop(AF_INET, mp->ip, addrbuf, sizeof(addrbuf));
        }

If you don't want to use inet_ntop() and inet_pton(), feel free to write your own, of course.

Allocating, Sending and Waiting on Messages

- Allocation, S(), W()

- talk about cli_timer_start() too