VPP除了使用命令行進行配置外,還可使用API進行配置。VPP不只支持c語言的API,還支持python,java,lua等高級語言的API,很是適合自動化部署。java
VPP提供了一個VAT客戶端程序,用於進行簡單的API測試。可執行文件位於:node
sudo systemctl start vpp cd vpp源碼路徑/build-root/build-vpp-native/vpp/bin/ sudo vpp_api_test load_one_plugin:68: Loaded plugin: /usr/lib/x86_64-linux-gnu/vpp_api_test_plugins/memif_test_plugin.so ...... load_one_plugin:68: Loaded plugin: /usr/lib/x86_64-linux-gnu/vpp_api_test_plugins/mactime_test_plugin.so vat# vat#
vat# vat# help Help is available for the following: acl_add_replace ......
vat# help acl_del usage: acl_del <acl-idx> vat#
vat# quit admin@ubuntu:~/vpp/build-root/build-vpp-native/vpp/bin$
vat# show_version program: vpe version: 19.08-rc0~65-g3b62e29c3 build date: Sat Apr 20 13:38:27 CST 2019 build directory: /home/admin/vpp vat#
vpp的api是CS模型。VPP是服務器端,監聽客戶端鏈接請求,處理客戶端發送的api請求,返回應答。以下圖所示:python
如上圖所示,客戶端VAT向VPP請求鏈接,VPP返回新鏈接。鏈接創建以後,VAT發送API請求,VPP處理請求返回應答。VPP支持unix套接字或者inet套接字。VPP支持多個客戶端同時進行請求。VPP支持使用共享內存進行數據交換,當客戶端和服務器端都在同一個節點上的時候,能夠選擇使用共享內存進行message交換(3,和4交互過程能夠選擇共享內存交換也能夠選擇套接字交換),提升通訊速率。linux
vpp一共支持三種類型的API:shell
這種類型的規則,客戶端發送一個請求,VPP發送一個應答。應答消息命名爲:method_name + _reply。ubuntu
客戶端發送一個bulk請求,VPP發送多個details消息。一個dump/detail請求最終會以一個"control ping block "結束。請求方法命名規則爲: method + _dump。 應答方法命名規則爲method + _details。該類型消息主要用來請求一個表消息,好比FIB表包含多個表項,可使用一個該消息獲取全部的FIB表項信息。api
客戶端能夠經過該類型API向服務器註冊一些獲取異步消息通知。好比獲取interface的狀態變化消息,週期性統計消息等。這類API一般以"want_"字段做爲前綴,好比:"want_interface_events"。服務器
客戶端發送的消息會包含一個'client_index'字段,該字段對服務器端是透明的,至關於一個‘cookie’,用於區分不一樣的客戶端。cookie
[ ] 命名網絡
注:這部份內容來自於網絡 [](https://blog.51cto.com/zhangc...
添加一個新的二進制控制層的API,涉及兩部份內容,一部分是客戶端,另外一個就是服務器端。咱們以acl插件的api爲例進行說明。
添加一個api須要修改三個文件。代碼路徑是vpp/src/plugins/acl下
acl.api -- vat 與vpp 通訊的結構體定義
acl_test.c -- vat使用(客戶端)
acl.c -- vpp使用(服務器端)
acl.api中定義vat與vpp通訊的結構體,而後由vppapigen文件處理,最終生成acl.api.h的頭文件。兩邊都包含這個頭文件,這樣vat與vpp就使用了相同的結構體通訊了。 咱們看一下acl.api中的定義:
/** \brief Delete an ACL @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param acl_index - ACL index to delete */ autoreply manual_print define acl_del { u32 client_index; //系統使用 u32 context; //系統使用 u32 acl_index; //經過命令acl_del <acl-idx>輸入的acl-idx };
這個結構體的定義由3個關鍵字(autoreply 、manual_print、define )加上名稱再加成員構成,最終會被轉化爲:
typedef VL_API_PACKED(struct _vl_api_acl_del { u16 _vl_msg_id; u32 client_index; u32 context; u32 acl_index; }) vl_api_acl_del_t; typedef VL_API_PACKED(struct _vl_api_acl_del_reply { u16 _vl_msg_id; u32 context; i32 retval; }) vl_api_acl_del_reply_t;
這樣就能夠用使用vl_api_acl_del_t與vl_api_acl_del_reply這個結構體通訊了。
具體說一下每一個部分:
void autoreply (void *np_arg) { static u8 *s; node_t *np = (node_t *)np_arg; int i; vec_reset_length (s); s = format (0, " define %s_reply\n", (char *)(np->data[0])); s = format (s, "{\n"); s = format (s, " u32 context;\n"); s = format (s, " i32 retval;\n"); s = format (s, "};\n"); for (i = 0; i < vec_len (s); i++) clib_fifo_add1 (push_input_fifo, s[i]); }
static inline void * vl_api_acl_del_t_print (vl_api_macip_acl_del_t * a, void *handle) { u8 *s; s = format (0, "SCRIPT: acl_del %d ", clib_host_to_net_u32 (a->acl_index)); PRINT_S; return handle; }
這個文件是vat使用。
有三件事要作,1.寫cli的help 2.寫函數 3.函數加載
#define foreach_vpe_api_msg \ _(acl_del, "<acl-idx>")
咱們須要寫兩個函數
api_acl_del與vl_api_acl_del_reply_t_handler
這兩個函數是配合使用的,來一個一個看
static int api_acl_del (vat_main_t * vam) { unformat_input_t * i = vam->input; //這個結構體就是在acl.api中定義的消息傳遞結構體 vl_api_acl_del_t * mp; u32 acl_index = ~0; int ret; //解析字符串,跟vpp的命令行解析同樣 if (!unformat (i, "%d", &acl_index)) { errmsg ("missing acl index\n"); return -99; } //給mp分配內存,而後填寫要傳遞的值 /* Construct the API message */ M(ACL_DEL, mp); mp->acl_index = ntohl(acl_index); /* send it... */ S(mp); /* Wait for a reply... */ W (ret); return ret; }
在這裏把這幾個宏的實現也貼一下。對應一下,就能看明白了。
/* M: construct, but don't yet send a message */ #define M(T, mp) \ do { \ vam->result_ready = 0; \ mp = vl_msg_api_alloc_as_if_client(sizeof(*mp)); \ memset (mp, 0, sizeof (*mp)); \ mp->_vl_msg_id = ntohs (VL_API_##T+__plugin_msg_base); \ mp->client_index = vam->my_client_index; \ } while(0); /* S: send a message */ #define S(mp) (vl_msg_api_send_shmem (vam->vl_input_queue, (u8 *)&mp)) /* W: wait for results, with timeout */ #define W(ret) \ do { \ f64 timeout = vat_time_now (vam) + 1.0; \ ret = -99; \ \ while (vat_time_now (vam) < timeout) { \ if (vam->result_ready == 1) { \ ret = vam->retval; \ break; \ } \ vat_suspend (vam->vlib_main, 1e-5); \ } \ } while(0);
#define foreach_standard_reply_retval_handler \ _(acl_del_reply) #define _(n) \ static void vl_api_##n##_t_handler \ (vl_api_##n##_t * mp) \ { \ vat_main_t * vam = acl_test_main.vat_main; \ i32 retval = ntohl(mp->retval); \ if (vam->async_mode) { \ vam->async_errors += (retval < 0); \ } else { \ vam->retval = retval; \ vam->result_ready = 1; \ } \ } foreach_standard_reply_retval_handler; #undef _
註上面這個宏只是定義多個xxx_reply_retval_handler函數
從下面信息能夠看出VAT有兩個線程:
admin@ubuntu:~/vpp$ pstree -p `pidof vpp_api_test` vpp_api_test(25810)───{vpp_api_test}(25811) admin@ubuntu:~/vpp$
也能夠從以下的gdb信息能夠看出vat有一個線程進行應答消息的處理。
(gdb) info thread Id Target Id Frame 1 Thread 0x7f3bb05d2b80 (LWP 25810) "vpp_api_test" vat_time_now ( vam=vam@entry=0x5575ebcdc800 <vat_main>) at /home/jd/vpp/src/vat/api_format.c:141 * 2 Thread 0x7f3ba6d09700 (LWP 25811) "vpp_api_test" 0x00007f3ba5cec470 in vl_api_acl_del_reply_t_handler (mp=0x13004dee8) at /home/jd/vpp/src/plugins/acl/acl_test.c:91 (gdb) bt #0 0x00007f3ba5cec470 in vl_api_acl_del_reply_t_handler (mp=0x13004dee8) at /home/jd/vpp/src/plugins/acl/acl_test.c:91 #1 0x00007f3bb01c2251 in msg_handler_internal (free_it=1, do_it=1, trace_it=<optimized out>, the_msg=0x13004dee8, am=0x7f3bb03ca420 <api_main>) at /home/jd/vpp/src/vlibapi/api_shared.c:425 #2 vl_msg_api_handler (the_msg=0x13004dee8) at /home/jd/vpp/src/vlibapi/api_shared.c:559 #3 0x00007f3bb01c321a in vl_msg_api_queue_handler ( q=q@entry=0x1301c69c0) at /home/jd/vpp/src/vlibapi/api_shared.c:770 #4 0x00007f3bb01bc07e in rx_thread_fn (arg=<optimized out>) at /home/jd/vpp/src/vlibmemory/memory_client.c:94 #5 0x00007f3baf6b86db in start_thread (arg=0x7f3ba6d09700) at pthread_create.c:463 #6 0x00007f3baf3e188f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb)
須要把寫的兩個函數掛載上。只須要在對應的宏下按格式寫就行了。
在foreach_vpe_api_msg宏下定義
其實這個在「2.1寫cli的help」中已經寫過,就不用再寫了
/* * List of messages that the api test plugin sends, * and that the data plane plugin processes */ #define foreach_vpe_api_msg _(acl_del, "<acl-idx>") \
在foreach_vpe_api_reply_msg宏下定義
/* * Table of message reply handlers, must include boilerplate handlers * we just generated */ #define foreach_vpe_api_reply_msg \ _(ACL_DEL_REPLY, acl_del_reply)
上一節提到的宏,都是在acl_vat_api_hookup這個函數中使用的,咱們不須要作任何修改。
static void acl_vat_api_hookup (vat_main_t *vam) { acl_test_main_t * sm = &acl_test_main; /* Hook up handlers for replies from the data plane plug-in */ #define _(N,n) \ vl_msg_api_set_handlers((VL_API_##N + sm->msg_id_base), \ #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 _ /* API messages we can send */ #define _(n,h) hash_set_mem (vam->function_by_name, #n, api_##n); foreach_vpe_api_msg; #undef _ /* Help strings */ #define _(n,h) hash_set_mem (vam->help_by_name, #n, h); foreach_vpe_api_msg; #undef _ }
這個文件是vpp使用,它用來接收vat(vpp-api-test)發送的消息,而後處理,最後迴應給vat。
咱們須要寫對應的函數,而後掛上就能夠了
/* List of message types that this plugin understands */ #define foreach_acl_plugin_api_msg \ _(ACL_DEL, acl_del)
static void vl_api_acl_del_t_handler (vl_api_acl_del_t * mp) { acl_main_t *am = &acl_main; //這個結構體就是在acl.api中定義的消息應答傳遞結構體,用於給VAT發送應答消息 vl_api_acl_del_reply_t *rmp; int rv; //mp中就是VAT發送來的結構體,咱們能夠從中取得配置的acl_index使用。而後調用相應的處理函數。 rv = acl_del_list (ntohl (mp->acl_index)); //這裏是消息處理完畢後的應答消息,VAT會在那裏等待迴應。也是經過共享內存的方式來通訊。 //若是須要在迴應消息裏傳遞參數,可使用另外一個宏 --- REPLY_MACRO2 REPLY_MACRO (VL_API_ACL_DEL_REPLY); }
/* Set up the API message handling tables */ static clib_error_t * acl_plugin_api_hookup (vlib_main_t * vm) { acl_main_t *am = &acl_main; #define _(N,n) \ vl_msg_api_set_handlers((VL_API_##N + am->msg_id_base), \ #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_acl_plugin_api_msg; #undef _ return 0; }