Nathan Skrzypczak | d4a7064 | 2021-10-08 14:01:27 +0200 | [diff] [blame] | 1 | .. _api_doc: |
| 2 | |
| 3 | Writing API handlers |
| 4 | ==================== |
| 5 | |
| 6 | VPP provides a binary API scheme to allow a wide variety of client codes |
| 7 | to program data-plane tables. As of this writing, there are hundreds of |
| 8 | binary APIs. |
| 9 | |
| 10 | Messages are defined in ``*.api`` files. Today, there are about 50 api |
| 11 | files, with more arriving as folks add programmable features. The API |
| 12 | file compiler sources reside in @ref src/tools/vppapigen. |
| 13 | |
| 14 | From @ref src/vnet/interface.api, here’s a typical request/response |
| 15 | message definition: |
| 16 | |
| 17 | .. code:: c |
| 18 | |
| 19 | autoreply define sw_interface_set_flags |
| 20 | { |
| 21 | u32 client_index; |
| 22 | u32 context; |
| 23 | u32 sw_if_index; |
| 24 | /* 1 = up, 0 = down */ |
| 25 | u8 admin_up_down; |
| 26 | }; |
| 27 | |
| 28 | To a first approximation, the API compiler renders this definition into |
| 29 | ``build-root/.../vpp/include/vnet/interface.api.h`` as follows: |
| 30 | |
| 31 | .. code:: c |
| 32 | |
| 33 | /****** Message ID / handler enum ******/ |
| 34 | #ifdef vl_msg_id |
| 35 | vl_msg_id(VL_API_SW_INTERFACE_SET_FLAGS, vl_api_sw_interface_set_flags_t_handler) |
| 36 | vl_msg_id(VL_API_SW_INTERFACE_SET_FLAGS_REPLY, vl_api_sw_interface_set_flags_reply_t_handler) |
| 37 | #endif |
| 38 | |
| 39 | /****** Message names ******/ |
| 40 | #ifdef vl_msg_name |
| 41 | vl_msg_name(vl_api_sw_interface_set_flags_t, 1) |
| 42 | vl_msg_name(vl_api_sw_interface_set_flags_reply_t, 1) |
| 43 | #endif |
| 44 | |
| 45 | /****** Message name, crc list ******/ |
| 46 | #ifdef vl_msg_name_crc_list |
| 47 | #define foreach_vl_msg_name_crc_interface \ |
| 48 | _(VL_API_SW_INTERFACE_SET_FLAGS, sw_interface_set_flags, f890584a) \ |
| 49 | _(VL_API_SW_INTERFACE_SET_FLAGS_REPLY, sw_interface_set_flags_reply, dfbf3afa) \ |
| 50 | #endif |
| 51 | |
| 52 | /****** Typedefs *****/ |
| 53 | #ifdef vl_typedefs |
| 54 | typedef VL_API_PACKED(struct _vl_api_sw_interface_set_flags { |
| 55 | u16 _vl_msg_id; |
| 56 | u32 client_index; |
| 57 | u32 context; |
| 58 | u32 sw_if_index; |
| 59 | u8 admin_up_down; |
| 60 | }) vl_api_sw_interface_set_flags_t; |
| 61 | |
| 62 | typedef VL_API_PACKED(struct _vl_api_sw_interface_set_flags_reply { |
| 63 | u16 _vl_msg_id; |
| 64 | u32 context; |
| 65 | i32 retval; |
| 66 | }) vl_api_sw_interface_set_flags_reply_t; |
| 67 | |
| 68 | ... |
| 69 | #endif /* vl_typedefs */ |
| 70 | |
| 71 | To change the admin state of an interface, a binary api client sends a |
| 72 | @ref vl_api_sw_interface_set_flags_t to VPP, which will respond with a |
| 73 | @ref vl_api_sw_interface_set_flags_reply_t message. |
| 74 | |
| 75 | Multiple layers of software, transport types, and shared libraries |
| 76 | implement a variety of features: |
| 77 | |
| 78 | - API message allocation, tracing, pretty-printing, and replay. |
| 79 | - Message transport via global shared memory, pairwise/private shared |
| 80 | memory, and sockets. |
| 81 | - Barrier synchronization of worker threads across thread-unsafe |
| 82 | message handlers. |
| 83 | |
| 84 | Correctly-coded message handlers know nothing about the transport used |
| 85 | to deliver messages to/from VPP. It’s reasonably straightforward to use |
| 86 | multiple API message transport types simultaneously. |
| 87 | |
| 88 | For historical reasons, binary api messages are (putatively) sent in |
| 89 | network byte order. As of this writing, we’re seriously considering |
| 90 | whether that choice makes sense. |
| 91 | |
| 92 | Message Allocation |
| 93 | ------------------ |
| 94 | |
| 95 | Since binary API messages are always processed in order, we allocate |
| 96 | messages using a ring allocator whenever possible. This scheme is |
| 97 | extremely fast when compared with a traditional memory allocator, and |
| 98 | doesn’t cause heap fragmentation. See @ref |
| 99 | src/vlibmemory/memory_shared.c @ref vl_msg_api_alloc_internal(). |
| 100 | |
| 101 | Regardless of transport, binary api messages always follow a @ref |
| 102 | msgbuf_t header: |
| 103 | |
| 104 | .. code:: c |
| 105 | |
| 106 | typedef struct msgbuf_ |
| 107 | { |
| 108 | unix_shared_memory_queue_t *q; |
| 109 | u32 data_len; |
| 110 | u32 gc_mark_timestamp; |
| 111 | u8 data[0]; |
| 112 | } msgbuf_t; |
| 113 | |
| 114 | This structure makes it easy to trace messages without having to decode |
| 115 | them - simply save data_len bytes - and allows @ref vl_msg_api_free() to |
| 116 | rapidly dispose of message buffers: |
| 117 | |
| 118 | .. code:: c |
| 119 | |
| 120 | void |
| 121 | vl_msg_api_free (void *a) |
| 122 | { |
| 123 | msgbuf_t *rv; |
| 124 | api_main_t *am = &api_main; |
| 125 | |
| 126 | rv = (msgbuf_t *) (((u8 *) a) - offsetof (msgbuf_t, data)); |
| 127 | |
| 128 | /* |
| 129 | * Here's the beauty of the scheme. Only one proc/thread has |
| 130 | * control of a given message buffer. To free a buffer, we just |
| 131 | * clear the queue field, and leave. No locks, no hits, no errors... |
| 132 | */ |
| 133 | if (rv->q) |
| 134 | { |
| 135 | rv->q = 0; |
| 136 | rv->gc_mark_timestamp = 0; |
| 137 | return; |
| 138 | } |
| 139 | <snip> |
| 140 | } |
| 141 | |
| 142 | Message Tracing and Replay |
| 143 | -------------------------- |
| 144 | |
| 145 | It’s extremely important that VPP can capture and replay sizeable binary |
| 146 | API traces. System-level issues involving hundreds of thousands of API |
| 147 | transactions can be re-run in a second or less. Partial replay allows |
| 148 | one to binary-search for the point where the wheels fall off. One can |
| 149 | add scaffolding to the data plane, to trigger when complex conditions |
| 150 | obtain. |
| 151 | |
| 152 | With binary API trace, print, and replay, system-level bug reports of |
| 153 | the form “after 300,000 API transactions, the VPP data-plane stopped |
| 154 | forwarding traffic, FIX IT!” can be solved offline. |
| 155 | |
| 156 | More often than not, one discovers that a control-plane client |
| 157 | misprograms the data plane after a long time or under complex |
| 158 | circumstances. Without direct evidence, “it’s a data-plane problem!” |
| 159 | |
| 160 | See @ref src/vlibmemory/memory_vlib.c @ref vl_msg_api_process_file(), |
| 161 | and @ref src/vlibapi/api_shared.c. See also the debug CLI command “api |
| 162 | trace” |
| 163 | |
| 164 | Client connection details |
| 165 | ------------------------- |
| 166 | |
| 167 | Establishing a binary API connection to VPP from a C-language client is |
| 168 | easy: |
| 169 | |
| 170 | .. code:: c |
| 171 | |
| 172 | int |
| 173 | connect_to_vpe (char *client_name, int client_message_queue_length) |
| 174 | { |
| 175 | vat_main_t *vam = &vat_main; |
| 176 | api_main_t *am = &api_main; |
| 177 | |
| 178 | if (vl_client_connect_to_vlib ("/vpe-api", client_name, |
| 179 | client_message_queue_length) < 0) |
| 180 | return -1; |
| 181 | |
| 182 | /* Memorize vpp's binary API message input queue address */ |
| 183 | vam->vl_input_queue = am->shmem_hdr->vl_input_queue; |
| 184 | /* And our client index */ |
| 185 | vam->my_client_index = am->my_client_index; |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | 32 is a typical value for client_message_queue_length. VPP cannot block |
| 190 | when it needs to send an API message to a binary API client, and the |
| 191 | VPP-side binary API message handlers are very fast. When sending |
| 192 | asynchronous messages, make sure to scrape the binary API rx ring with |
| 193 | some enthusiasm. |
| 194 | |
| 195 | binary API message RX pthread |
| 196 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 197 | |
| 198 | Calling @ref vl_client_connect_to_vlib spins up a binary API message RX |
| 199 | pthread: |
| 200 | |
| 201 | .. code:: c |
| 202 | |
| 203 | static void * |
| 204 | rx_thread_fn (void *arg) |
| 205 | { |
| 206 | unix_shared_memory_queue_t *q; |
| 207 | memory_client_main_t *mm = &memory_client_main; |
| 208 | api_main_t *am = &api_main; |
| 209 | |
| 210 | q = am->vl_input_queue; |
| 211 | |
| 212 | /* So we can make the rx thread terminate cleanly */ |
| 213 | if (setjmp (mm->rx_thread_jmpbuf) == 0) |
| 214 | { |
| 215 | mm->rx_thread_jmpbuf_valid = 1; |
| 216 | while (1) |
| 217 | { |
| 218 | vl_msg_api_queue_handler (q); |
| 219 | } |
| 220 | } |
| 221 | pthread_exit (0); |
| 222 | } |
| 223 | |
| 224 | To handle the binary API message queue yourself, use @ref |
| 225 | vl_client_connect_to_vlib_no_rx_pthread. |
| 226 | |
| 227 | In turn, vl_msg_api_queue_handler(…) uses mutex/condvar signalling to |
| 228 | wake up, process VPP -> client traffic, then sleep. VPP supplies a |
| 229 | condvar broadcast when the VPP -> client API message queue transitions |
| 230 | from empty to nonempty. |
| 231 | |
| 232 | VPP checks its own binary API input queue at a very high rate. VPP |
| 233 | invokes message handlers in “process” context [aka cooperative |
| 234 | multitasking thread context] at a variable rate, depending on data-plane |
| 235 | packet processing requirements. |
| 236 | |
| 237 | Client disconnection details |
| 238 | ---------------------------- |
| 239 | |
| 240 | To disconnect from VPP, call @ref vl_client_disconnect_from_vlib. Please |
| 241 | arrange to call this function if the client application terminates |
| 242 | abnormally. VPP makes every effort to hold a decent funeral for dead |
| 243 | clients, but VPP can’t guarantee to free leaked memory in the shared |
| 244 | binary API segment. |
| 245 | |
| 246 | Sending binary API messages to VPP |
| 247 | ---------------------------------- |
| 248 | |
| 249 | The point of the exercise is to send binary API messages to VPP, and to |
| 250 | receive replies from VPP. Many VPP binary APIs comprise a client request |
| 251 | message, and a simple status reply. For example, to set the admin status |
| 252 | of an interface, one codes: |
| 253 | |
| 254 | .. code:: c |
| 255 | |
| 256 | vl_api_sw_interface_set_flags_t *mp; |
| 257 | |
| 258 | mp = vl_msg_api_alloc (sizeof (*mp)); |
| 259 | memset (mp, 0, sizeof (*mp)); |
| 260 | mp->_vl_msg_id = clib_host_to_net_u16 (VL_API_SW_INTERFACE_SET_FLAGS); |
| 261 | mp->client_index = api_main.my_client_index; |
| 262 | mp->sw_if_index = clib_host_to_net_u32 (<interface-sw-if-index>); |
| 263 | vl_msg_api_send (api_main.shmem_hdr->vl_input_queue, (u8 *)mp); |
| 264 | |
| 265 | Key points: |
| 266 | |
| 267 | - Use @ref vl_msg_api_alloc to allocate message buffers |
| 268 | |
| 269 | - Allocated message buffers are not initialized, and must be presumed |
| 270 | to contain trash. |
| 271 | |
| 272 | - Don’t forget to set the \_vl_msg_id field! |
| 273 | |
| 274 | - As of this writing, binary API message IDs and data are sent in |
| 275 | network byte order |
| 276 | |
| 277 | - The client-library global data structure @ref api_main keeps track of |
| 278 | sufficient pointers and handles used to communicate with VPP |
| 279 | |
| 280 | Receiving binary API messages from VPP |
| 281 | -------------------------------------- |
| 282 | |
| 283 | Unless you’ve made other arrangements (see @ref |
| 284 | vl_client_connect_to_vlib_no_rx_pthread), *messages are received on a |
| 285 | separate rx pthread*. Synchronization with the client application main |
| 286 | thread is the responsibility of the application! |
| 287 | |
| 288 | Set up message handlers about as follows: |
| 289 | |
| 290 | .. code:: c |
| 291 | |
| 292 | #define vl_typedefs /* define message structures */ |
| 293 | #include <vpp/api/vpe_all_api_h.h> |
| 294 | #undef vl_typedefs |
| 295 | |
| 296 | /* declare message handlers for each api */ |
| 297 | |
| 298 | #define vl_endianfun /* define message structures */ |
| 299 | #include <vpp/api/vpe_all_api_h.h> |
| 300 | #undef vl_endianfun |
| 301 | |
| 302 | /* instantiate all the print functions we know about */ |
| 303 | #define vl_print(handle, ...) |
| 304 | #define vl_printfun |
| 305 | #include <vpp/api/vpe_all_api_h.h> |
| 306 | #undef vl_printfun |
| 307 | |
| 308 | /* Define a list of all message that the client handles */ |
| 309 | #define foreach_vpe_api_reply_msg \ |
| 310 | _(SW_INTERFACE_SET_FLAGS_REPLY, sw_interface_set_flags_reply) |
| 311 | |
| 312 | static clib_error_t * |
| 313 | my_api_hookup (vlib_main_t * vm) |
| 314 | { |
| 315 | api_main_t *am = &api_main; |
| 316 | |
| 317 | #define _(N,n) \ |
| 318 | vl_msg_api_set_handlers(VL_API_##N, #n, \ |
| 319 | vl_api_##n##_t_handler, \ |
| 320 | vl_noop_handler, \ |
| 321 | vl_api_##n##_t_endian, \ |
| 322 | vl_api_##n##_t_print, \ |
| 323 | sizeof(vl_api_##n##_t), 1); |
| 324 | foreach_vpe_api_msg; |
| 325 | #undef _ |
| 326 | |
| 327 | return 0; |
| 328 | } |
| 329 | |
| 330 | The key API used to establish message handlers is @ref |
| 331 | vl_msg_api_set_handlers , which sets values in multiple parallel vectors |
| 332 | in the @ref api_main_t structure. As of this writing: not all vector |
| 333 | element values can be set through the API. You’ll see sporadic API |
| 334 | message registrations followed by minor adjustments of this form: |
| 335 | |
| 336 | .. code:: c |
| 337 | |
| 338 | /* |
| 339 | * Thread-safe API messages |
| 340 | */ |
| 341 | am->is_mp_safe[VL_API_IP_ADD_DEL_ROUTE] = 1; |
| 342 | am->is_mp_safe[VL_API_GET_NODE_GRAPH] = 1; |