目录

引言 - Introduction

C语言API概述 - C API Overview

运行环境 - The Runtime Environment

API回顾 - Message API Review

响应处理的重要信息 - Important Information about Reply Handlers

头文件 - API Include Files

基本模块和插件 - VPE vs. Plugin Messages

消息注册 - Message Registration

Will the Real Message Id Please Stand Up?

基本模块的消息ID - Message Ids for VPE Messages

插件的消息ID - Message Ids for a Plugin's Messages

建立C应用 - Building Your C Application

初始化 - Initialization

构造,发送和接收API消息 - Constructing, Sending, and Receiving API Messages

分配内存 - Allocating Messages

使用消息字段 - Using Message Fields

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

发送、等待消息 - Sending and Waiting on Messages


翻译自:https://wiki.fd.io/view/VPP/How_To_Use_The_C_API

引言 - 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!

因此,VPP非常酷,您想使用它来制作自己的路由器。 我也是! 您可以使用Python,Java或Lua API到VPP编写它。 但是,当您可以过时并使用C API时,为什么要使用那些经典的流行语言之一?

现在我们在同一页面上,让我们深入探讨!

C语言API概述 - 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.
  • 所有API函数均在C头文件中描述。
  • 与VPP API的所有交互都是通过直接调用C函数来实现的。

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.一些基本的基础设施。 向量,哈希,bitops等

  • 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.
    • 首先,从接口接收网络数据包并将其放置在内存中方便的向量中。
    • 其次,从内存中获取网络数据包并将其发送到接口上。 该库尽管是VPP的一部分,但在这里有其自己的项目和开发:FIXME:DPDK URL引用。
  • jvpp-common
  • pneum
  • svm
  • vat
  • vnet

运行环境 - The Runtime Environment


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

该应用程序的运行时环境是显式多线程的。

API回顾 - Message API Review


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

本节是API概念Wiki页面上的材料的快速回顾。

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

VPP库可以有两种基本的消息交换类型。

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.

首先,有一个简单的“请求和答复”类型。 这些消息是由您(由应用程序编写器)发起的简单数据交换,并导致一条答复消息从VPP返回给您。 您构造一条消息,然后得到回复。

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

这种形式的发起请求消息都具有不以“ DUMP”结尾的名称。 例如VL_API_SW_INTERFACE_ADD_DEL_ADDRESS。 其对应的回复消息是完全相同的名称,后缀为“ _REPLY”。 您应该为此回复编写一个名为sw_interface_add_del_address_reply()和原型的处理程序。

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:

消息交换的第二种类型是“转储”请求和多个“详细信息”形式的重复答复消息。 请求消息的名称以后缀“ _DUMP”结尾,回复消息以后缀“ _DETAILS”结尾。 例如,消息VL_API_IP_ADDRESS_DUMP将具有带有以下原型的回复处理程序功能:

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.

想一想。 您提出一些API消息请求。 将其发射到库中,然后将其遗忘。 然后什么? 好吧,有人告诉您,回复处理程序将运行。 确切的时间是什么时候? 答案很简单:只要有可能。 实际上,它可能是在不同的执行线程中被调用的,而您的主线应用只是一直在跟踪它想要进行的下一个API调用,甚至可能会进行。

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.

其次,转储/细节样式消息需要使用附加的最终CONTROL_PING消息来完成。

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.

因为单个转储请求的结果可能会多次调用详细信息处理程序,所以必须使用CONTROL_PING消息将其终止。 控制ping消息的目的是确保发送方消息队列在允许其继续发送新的消息之前,等待所有已发送详细信息的完成。

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.

与所有程序开发人员一样,懒惰被视为一种美德。 VPP的发展也不例外! VPP库的每个模块都有一个“ API描述文件”,它是该模块使用的消息定义的唯一来源。

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.

然后脚本将处理该API文件,并将其转换为具有类型,消息名称,打印功能等的各种C头定义。这些API文件将以“ vpe.api.h”或“ snat.api.h”之类的名称导出和安装。 在/ usr / include / vpp / api或/ 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.

在各种api.h中,有不同的部分,它们提供了关于该组API消息中的每个消息的大量定义或数据。

As of this writing, the various sections are:

  vl_msg_idvl_union_idvl_printfunvl_endianfunvl_api_versionvl_typedefsvl_msg_namevl_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.

我将详细讨论其中的三个(vl_msg_id,vl_typedefs和vl_msg_name_crc_list),然后盲目地参考其他两个,挥手示意,并隐约建议您做类似的事情。

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:

如果您打开vpe.api.h,在其中的某个地方,您将看到一段代码,全部被#ifdef包围,如下所示:

/****** Typedefs *****/#ifdef vl_typedefstypedef 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 vpe.api.h文件,然后才能使用任何这些消息typedef。 像这样:

#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:

实际上,您将需要对每个* .api.h文件的每个部分执行类似的操作。 因此,您可以这样做:

#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:

这可以简化,以便只发生一个#include:

#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.

很好,但是我们在这里必须小心。 不加选择地将这种方法应用于各种* .api.h的所有部分是不正确的。

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.

有些部分期望在C函数中进行处理的上下文,而不仅仅是C文件范围内的声明上下文。 实际上,某些部分期望将#define功能定义应用于列表中的每个* something *。 我知道这听起来有些含糊。 因此,让我们来看一下它的作用。

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:

假设您想要(而且确实需要!)用于调试目的的消息特定的printf()函数,您不必全部编写它们:

#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().

请注意,这里我们都必须定义一个vl_print()函数,以说明对* .api.h文件的bl_printfun部分中的每个成员执行什么操作。 在这种情况下,对vl_print()的隐式调用被重写为VLIB中称为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.

虽然此惯用语在C文件级生成声明,但它显示了在处理* .api.h文件的整个部分期间可能定义的其他处理宏的用法。

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:

经常使用的另一种习惯用法允许您对列表的每个成员应用#define操作。 例如,处理消息名称和CRC列表的部分如下所示:

/****** 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.

在上面的所有讨论中,我都精心引用了“ vpe.api.h”或诸如“各种* .api.h”文件之类的含糊不清的内容。 那是因为我在躲避一个问题是为了简化上面的讨论。

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.

VPP库为您(应用程序开发人员)提供了两种类型的模块:基本模块(又称“ vpe”)和插件。 实际上有几个插件。 您必须使用基本模块vpe,但是您可以选择使用或不使用每个插件。

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.

在撰写本文时,具有API模块级别支持的插件为ACL和SNAT。 DPDK作为插件正在开发中,并将很快推出(现在“2020年9月1日18:23:35”已经开发完了)。 通过库名称,还有flowerpkt,ioam,ila和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。

为了使用* .api.h文件提供的消息(是,vpe.api.h或任何插件api文件,如snat.api.h),您必须首先注册使用意图 通过说明将导致结果(或详细信息)的消息的处理程序来处理消息

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);

/** vl_msg_api_set_handlers* preserve the old API for a while*/
void
vl_msg_api_set_handlers (int id, char *name, void *handler, void *cleanup,void *endian, void *print, int size, int traced)
{vl_msg_api_msg_config_t cfg;vl_msg_api_msg_config_t *c = &cfg;clib_memset (c, 0, sizeof (*c));c->id = id;c->name = name;c->handler = handler;c->cleanup = cleanup;c->endian = endian;c->print = print;c->traced = traced;c->replay = 1;c->message_bounce = 0;c->is_mp_safe = 0;vl_msg_api_config (c);
}

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

可以在“ 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.

尽管即使从原型也很难分辨,但是所有参数处理程序,清除,字节序和打印都是函数名。 可以分别使用#define vl_endianfun和vl_printfun从* .api.h文件自动生成字节序和打印处理程序。 清除功能已在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.

另一方面,此处的重要功能是名为vl_api_control_ping_reply_t_handler()的功能。 与您打算作为API调用发送到VPP库的所有消息一样,您必须提供答复(通用)处理程序。 该处理程序必须与每个消息类型匹配,并且如果它是简单的答复处理程序,则必须命名为<msg_name> _reply_t_handler(),如果它是“转储”样式的答复处理程序,则必须命名为<msg_name> _details_t_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.

您将编写一堆回复处理程序。 每个VPP消息一个。 可能会很乏味。 为了简化某些繁琐的工作,同时又使某些C令牌后面的代码变得晦涩难懂,以下成语已成为公认的规范。

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":

首先,列出您要回复的邮件。 这包括简单答复和详细答复。 对于VPE模块,我们仅用一条消息(即所谓的"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.

我想说的就是这么简单。 但是我会撒谎。 您必须了解实际的API消息编号周围有两道皱纹。 首先,在* .api.h文件中找到的枚举列表可能对您不利,除非您对确切地了解了如何编译实现API调用的相应VPP二进制代码有一定的条件知识。 提示,你不知道。 而且,您不能。

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.

第二个复杂的问题是每个插件都是可选的。 它可能会或可能不会出现在您的应用程序中。 并且其加载顺序可能与VPP库中插件的加载方式相对应,也可能与之不符。 因此,您不知道他们的消息ID实际是什么。 然而。

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

基本模块的消息ID - 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.

没有一个True Global Message Id空间可以一次全部放置所有消息。 相反,始终会首先加载来自VPE模块的基本消息。 它们占用从0开始的消息ID,并在任意位置结束。 你不知道,我也不知道。

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.

实际上,在有条件地编译部分或部分编译时,此消息空间中可能会存在空白,或者消息ID会四处移动。 唯一知道的方法是,首先使用其他C函数调用VPP以请求真实消息ID。 不完全是。 因此,将此返回值保存在地图中。

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.

哦,并且,为了使每个人都诚实,并确保您的应用程序和vlib实现都在谈论* same *消息,并且具有相同的字段和内容,该消息结构的CRC哈希值构造在 愿意命名的每条消息旁边的API文件。 当您请求* real *消息ID时,作为应用程序编写者的您必须向VPP提供消息名称和CRC值。

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

首先声明一个全局消息ID映射:

/** 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:

然后修改上面的vpp_vpe_init()函数,以便它首先构造消息ID映射,然后使用* 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.

如果我们*想*我们想发送消息VL_API_ <something>,我们*确实*必须将消息ID设置为vmid_map [VL_API_ <something>]。

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

好的,这将处理来自VPE模块的基本消息ID。 现在让我们转到插件消息ID。

插件的消息ID - 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."

幸运的是,VPP记住了所有VPE消息到达的位置,并跟踪了上次使用的消息ID。 现在,当第一个插件被加载时,将在VPE消息之后的某个位置为它的消息分配一个基本ID,然后线性呈现所有插件消息,并在某个地方结束。 同样,我们只能将其推测为“该模块的基本ID”加上“该模块中的消息数”。

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

因此,在加载每个插件时,会为其分配一个基本消息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!

噢,我们最好跟踪每个插件的基本消息ID。 因此,这是为SNAT插件执行此操作的神奇方法,即API调用序列。 其他都差不多!

        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?"

现在,如果您真的在家中跟随,那么精明的读者和Application Builder希望问自己一个类似的问题:“因此,如果我们必须使用映射的消息ID注册VPE消息,我们该怎么办? 插件消息ID?”

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:

然后在C函数中,确定基地址并调用消息注册,如前所述:

/** 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.

因此,请注意此处的两件事。 我创建了一个带有base_msg_id的“ snat_data”结构。 然后,我捕获了基本消息ID并将其存储在该全局消息中。 您还看不到的是,我还将该变量的使用隐藏在名为vmid_map_fn()的函数内,该函数用作消息注册API调用的第一个参数。

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.

您可能正确地猜想,如果vmid_map_fn()是VPE消息,则此vmid_map_fn()的目的是正确映射到基于0的消息集;如果是SNAT消息,则正确映射到基于snat_data.base_msg_id的消息集。

You would be correct.

Enough talk. Let's build an application now.

建立C应用 - Building Your C Application


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

  • vlibmemoryclient
  • vlibapi
  • svm
  • vppinfra
  • vlib

And the following standard libraries as well:

  • pthread
  • m
  • rt
  • dl
  • crypto

That is, use a linker line like this:

VPP_LIBS        += -lvlibmemoryclient
VPP_LIBS        += -lsvm
VPP_LIBS        += -lvppinfra
VPP_LIBS        += -lvlib
VPP_LIBS        += -lvatpluginLDFLAGS         += $(VPP_LIBS)
LDFLAGS         += -lpthread -lm -lrt -ldl -lcrypto$(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.

要启动应用程序,让我们构造一个文件,在其中建立与VPP库的主连接,并开始注册API消息的过程。 我在名为api_setup.c的文件中进行了此操作。

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

VPP库提供了您需要引用但不声明的全局变量:

api_main_t *api_main;

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


/** API main structure, used by both vpp and binary API clients */
typedef struct
{/** Message handler vector  */void (**msg_handlers) (void *);/** Plaform-dependent (aka hardware) message handler vector */int (**pd_msg_handlers) (void *, int);/** non-default message cleanup handler vector */void (**msg_cleanup_handlers) (void *);/** Message endian handler vector */void (**msg_endian_handlers) (void *);/** Message print function vector */void (**msg_print_handlers) (void *, void *);/** Message name vector */const char **msg_names;/** Don't automatically free message buffer vetor */u8 *message_bounce;/** Message is mp safe vector */u8 *is_mp_safe;/** Allocator ring vectors (in shared memory) */struct ring_alloc_ *arings;/** Number of times that the ring allocator failed */u32 ring_misses;/** Number of garbage-collected message buffers */u32 garbage_collects;/** Number of missing clients / failed message sends */u32 missing_clients;/** Received message trace configuration */vl_api_trace_t *rx_trace;/** Sent message trace configuration */vl_api_trace_t *tx_trace;/** Print every received message */int msg_print_flag;/** Current trace configuration */trace_cfg_t *api_trace_cfg;/** Current process PID */int our_pid;/** Current binary api segment descriptor */svm_region_t *vlib_rp;/** Primary api segment descriptor */svm_region_t *vlib_primary_rp;/** Vector of all mapped shared-VM segments */svm_region_t **vlib_private_rps;svm_region_t **mapped_shmem_regions;/** Binary API shared-memory segment header pointer */struct vl_shmem_hdr_ *shmem_hdr;/** vlib/vpp only: vector of client registrations */vl_api_registration_t **vl_clients;/** vlib/vpp only: serialized (message, name, crc) table */u8 *serialized_message_table_in_shmem;/** First available message ID, for theplugin msg allocator */u16 first_available_msg_id;/** Message range by name hash */uword *msg_range_by_name;/** vector of message ranges */vl_api_msg_range_t *msg_ranges;/** uid for the api shared memory region */int api_uid;/** gid for the api shared memory region */int api_gid;/** base virtual address for global VM region */u64 global_baseva;/** size of the global VM region */u64 global_size;/** size of the API region */u64 api_size;/** size of the global VM private mheap */u64 global_pvt_heap_size;/** size of the api private mheap */u64 api_pvt_heap_size;/** Peer input queue pointer */svm_queue_t *vl_input_queue;/*** All VLIB-side message handlers use my_client_index to identify* the queue / client. This works in sim replay.*/int my_client_index;/*** This is the (shared VM) address of the registration,* don't use it to id the connection since it can't possibly* work in simulator replay.*/vl_api_registration_t *my_registration;/** vpp/vlib input queue length */u32 vlib_input_queue_length;/** client message index hash table */uword *msg_index_by_name_and_crc;/** api version list */api_version_t *api_version_list;/** Shared VM binary API region name */const char *region_name;/** Chroot path to the shared memory API files */const char *root_path;/** Replay in progress? */int replay_in_progress;/** Dump (msg-name, crc) snapshot here at startup */u8 *save_msg_table_filename;/** List of API client reaper functions */_vl_msg_api_function_list_elt_t *reaper_function_registrations;/** Bin API thread handle */pthread_t rx_thread_handle;/** event log */elog_main_t *elog_main;int elog_trace_api_messages;} api_main_t;

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.

这些是还需要的几个全局变量。 它们对于VPP库本身并不是真正必需的,但是将需要它们来保存有关与VLIB的API连接的一些重要信息。

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:

另一方面,这是VPP库必须具有的两个全局变量,它们不提供自身,而是必须:

vlib_main_t vlib_global_main;
vlib_main_t **vlib_mains;

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

最简单的初始化和与VPP库的连接可能如下所示:

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.

您可能会断开,重新连接,需要重试之类的东西。 因此,请跟踪api_connected与否。 在发送消息之前,请先发送消息以确保已连接。 随你。

构造,发送和接收API消息 - Constructing, Sending, and Receiving API Messages


分配内存 - Allocating Messages


Here's the first dirty little tid-bit that must be part of your message allocation scheme: all messages are strongly typed. That is, each individual message has a type definition that is specific to that particular message. There is a C typedef for each and every message. Now that may seem like a fine idea, and it is, but it has ramifications on the allocation scheme. Specifically, it means there is also a per-message size.

这是您的消息分配方案中必须包含的第一个肮脏的小tid位:所有消息都是强类型的。 即,每个单独的消息都具有特定于该特定消息的类型定义。 每条消息都有一个C typedef。 现在,这似乎是个好主意,但它对分配方案有影响。 具体来说,这意味着还存在每个邮件的大小。

There is not, however, an allocation function for each message and each message type. Instead, there is a single message allocator function, vl_msg_api_alloc(), that accepts the size, in bytes, of a message to be generically allocated. LIke this:

但是,没有每种消息和每种消息类型的分配功能。 而是有一个消息分配器函数vl_msg_api_alloc(),它接受要一般分配的消息的大小(以字节为单位)。 像这样:

         mp = vl_msg_api_alloc(size);

Here, mp, is the accepted "message pointer" variable for any message. Any message. Which message? And how do you know the size of its type? Good questions. In fact, the answer to those questions lead to some seriously grody, not-even-inline, macro definitions for the main message allocation "function".

mp是任何消息的可接受的“消息指针”变量。 任何消息。 哪个讯息? 您怎么知道其类型的大小? 好问题。 实际上,对这些问题的答案导致对主消息分配“功能”的某些严重的,不均匀的,内联的宏定义。

Perhaps something like this:

 

使用消息字段 - 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:

消息和答复中的所有数据均按“网络字节顺序”排列。 这意味着在构造要发送到VPP的消息时,必须将主机字节排序数据的每个字段转换为网络字节顺序,如下所示:

   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 datantohs(), 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.

较大或字节流之类的值也需要仔细处理。 由于字符串表示为u8值的数组,因此只需提供字节数组的副本即可。 注意遵守0终止符(普通C字符串)或计数数量(如IP地址)。

哦,是的,IP地址。 让我们谈论一下。 这些天,既有IPv4地址又有IPv6地址。 前者为4个字节长,通常表示为点分四边形(10.10.220.1)或点分四边形的实际字符串(“ 10.10.220.1”)。 后者是一个16字节的混乱,通常表示为前缀和主体部分,由隐晦,严密保护和秘密咒语中构造的冒号分隔。

我不会描述如何从IP地址的字符串形式转换为第一原理的等效字节流。 相反,我将为您提供这些代码段。

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.

发送、等待消息 - Sending and Waiting on Messages


- S(), W()

- talk about cli_timer_start() too

  • This page was last modified on 16 August 2017, at 08:09.
  • This page has been accessed 1,494 times.
  • 链接:https://wiki.fd.io/view/VPP/How_To_Use_The_C_API

FD.io VPP的C语言接口如何使用:FD.io VPP: How To Use The C API相关推荐

  1. FD.io VPP基本介绍:理解向量包处理(VPP)

    FD.io VPP:用户文档 向量包处理器 RToax 2020年9月 1. 什么是向量包处理器(VPP) FD.io的矢量包处理器(VPP)是一个快速,可扩展的2-4层多平台网络协议栈.它在Linu ...

  2. C语言网络编程:多路IO select实现多客户端

    文章目录 阻塞式的服务器程序 多线程服务器程序 非阻塞式服务器程序 基于事件响应的服务器程序 事件响应服务器程序的实现`select` 阻塞式的服务器程序 我们接触过最多的最基础的网络通信模型为TCP ...

  3. 两万字深入解密 Go 语言接口的那些事儿 | 技术头条

    Go 语言的接口的原理是什么?是如何使用?它和C++接口有什么异同呢?本文作者用两万多字深入一一为你讲述Go语言interface的那些事儿. 作者 | 饶全成 责编 | 伍杏玲 这篇文章的架构比较简 ...

  4. linux嵌入式 控制io口,基于ARM9嵌入式的RS485总线接口设计,自动控制IO口实现通信方向控制...

    描述 随着ARM处理器应用的范围的不断深入,根据需求的不同ARM提供的外设也越来越丰富,常用的通信接口有RS232.RS485.CAN.以太网等.RS485总线凭其传输距离远.抗干扰能力强.价格低廉等 ...

  5. Android系统JNI使用(JAVA调用C语言接口)一

    目录 第一篇:Android系统JNI使用(JAVA调用C语言接口)一 什么是JNI JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信主要是 ...

  6. 《C语言接口与实现:创建可重用软件的技术》一2.6 扩展阅读

    本节书摘来自异步社区<C语言接口与实现:创建可重用软件的技术>一书中的第2章,第2.6节,作者 傅道坤,更多章节内容可以访问云栖社区"异步社区"公众号查看 2.6 扩展 ...

  7. C语言接口的封装和设计专题

    C语言接口的封装和设计专题 Win32环境下动态链接库(DLL)编程原理 导出和导入函数的匹配 与DLL模块建立链接 使用符号名链接与标识号链接 编写DllMain函数 模块句柄 应用程序怎样找到DL ...

  8. c语言接口作用是什么,C语言接口与实现之异常处理try-except

    前言 最近在学习<C语言接口与实现>,目前阅读到第四章,关于如何实现C语言异常捕获和断言处理,其中的异常捕获的栈和收尾处理有点不大明白,直到从网上查找到一篇文章才明白栈和结尾触发异常的作用 ...

  9. c语言调用labview方法,LabVIEW与C语言接口的方法

    摘要介绍了一种LabVIEW与C 语言接口的方法,由实例证明,该方法高效.易行,是增强LabVIEW整体功能的一条有效的途径. 关键词虚拟仪器LabVIEW 动态链接库 LabVIEW是一种方便灵活的 ...

最新文章

  1. 项目中用到的三个绿色自动备份方法
  2. 如何知道现在是否单用户模式_新手运营Shopee现在是否来得及,商品的转化如何提高?...
  3. 阿里云 centos 远程可视化桌面部署
  4. web3.js(三)查询智能合约币数量(erc20)
  5. python是个啥-初识python: 面向对象是个啥?
  6. 主nginx linux,Linux-实现双主模型的nginx的高可用
  7. BZOJ-1053-反素数ant
  8. 初步认识Volatile-CPU高速缓存
  9. java反向映射_opencv 直方图和直方图反向映射
  10. YUV格式学习:Y转换成RGB24
  11. 使用k8s安装minio
  12. .Net MVC中设置默认启动为某区域的视图
  13. C#固高运动控制卡PT模式使用
  14. 【saas公司案例】易快报与浦发云资金
  15. 银博进销存 v2.21.1 医疗器械版 下载
  16. 【仿人机器人】双足机器人行走碰撞模型:Passive g walking of a compass robot
  17. 3D博物馆虚拟纪念馆数字博览厅的“另类”展现方式
  18. 菜鸟日记(yzy):opencMS系统-XML内容管理文件开发
  19. sql导出的身份证后几位是000
  20. foxmail发邮件时总提示接收密码错误是怎么回事

热门文章

  1. yml读取环境变量_读取yml配置文件中的值
  2. 推荐系统和搜索引擎的关系
  3. 021-PHP常用的数值类型判断函数
  4. Spring的消息 Java Message Service (JMS)
  5. 【hihocoder 1499】A Box of Coins
  6. virtualBox文件共享
  7. BZOJ 3390: [Usaco2004 Dec]Bad Cowtractors牛的报复(最大生成树)
  8. MSP430程序库二UART异步串口
  9. AD 角色夺取传送注意事项
  10. linux 取消nologin_Linux中nologin的应用 转