FPN RPC¶
Overview¶
FPN RPC is a framework to define remote procedure calls to configure or monitor the fast path. The schema of the RPCs, as well as their binary encoding, are defined using Protobuf.
FPN RPC is composed of:
a library (
libfpn-rpc
), which implements the fpn-rpc framework, that is used on the server (fast path side), and can also be used on the client side.a test program (
fpn-rpc-test
), that demonstrates how to use the library, and providing basic examples and benchmarks.a python CLI (
fpn-rpc-cli
), which is a client that dynamically parses the protobuf schemas to build an interactive CLI.a control thread on the fast path side, that acts as a RPC server.
several fast path modules that register RPC and provide callback functions to process them.
RPC definition and organization¶
A protobuf file contains the definition of services, usually for a given
fast path module. The common service names are Config
(for all
configuration operations) and State
(for statistics or dumps).
Each service contains several RPCs. A RPC is composed of a request and a reply message. These messages can reference other sub-types (enum, messages) that are declared in this file, or in another one.
Some RPCs (like dumps) may receive several reply message for one
request. In this case, the rpc must be identified with the stream
keyword in the schema.
Example:
file
module-1.proto
(defines packagemodule_1
)service
Config
rpc
DoSomething
request message
DoSomethingRequest
reply message
DoSomethingReply
rpc
DoSomethingElse
request message
DoSomethingElseRequest
reply message
DoSomethingElseReply
service
State
rpc
DumpSomething
request message
DumpSomethingRequest
reply message
DumpSomethingReply
file
module-2.proto
(defines packagemodule_2
)service
Config
rpc
DoSomething
request message
DoSomethingRequest
reply message
DoSomethingReply
(…)
All the protobuf schemas are installed on the target in
/usr/share/fast-path/rpc
.
Transport¶
The RPCs are transported over a UNIX SEQPACKET
socket. The messages
are encoded in protobuf using the fpn_rpc.Message
message type,
defined in fpn-rpc.proto
.
This outer message carries another protobuf-encoded message, whose type is identified by the service name and the rpc name. The outer message contains additional metadata, like the message type (request, reply, error).
The messages are ordered on a connection. A client sends a
fpn_rpc.Message
whose type is REQUEST
, and the server replies with
either:
a
REPLY
message containing the inner reply rpczero to several
REPLY_STREAM
messages, each of them containing an inner reply rpc, followed by aREPLY_END
messagean
ERR_xxx
message
A client should not send another request until the the reply of previous one is not fully received. To send parallel requests or do parallel monitoring, several connections must be opened.
CLI¶
fpn-rpc-cli
is an interactive CLI written in Python that can be used
to configure the fast path manually. The RPC data in the CLI is formatted
in JSON, to make it human-readable.
Synopsis
# fpn-rpc-cli [-h] [-u PATH] [-i IGNORE_SCHEMA_LIST] [-v]
Parameters
- -h, --help¶
Show help.
- -u PATH, --unix-path PATH¶
Path to fpn-rpc unix server. Default: /run/fast-path/fpn-rpc.sock.
- -i IGNORE_SCHEMA_LIST, --ignore-schema-list IGNORE_SCHEMA_LIST¶
Comma-separated list of schemas files (without .proto extension) to be ignored by cli. Default: fpn-rpc-test.
- -c CMD, --command CMD¶
Execute the given command instead of running interactively.
- -v, --verbose¶
Display debug information.
Example
Example of an interactive CLI session.
root@dut-vm:~# fpn-rpc-cli
Welcome to fpn-rpc-cli. Type help or ? to list commands.
fpn-rpc> ?
Documented commands (type help <topic>):
========================================
help list_rpcs list_services rpc show_rpc
fpn-rpc> help list_services
List registered services.
list_services [detail]
fpn-rpc> list_services
fp_if.Config
fp_gtp.Config
fp_gtp.State
fp_ip.Config
fpn-rpc> help list_rpcs
List registered rpcs.
list_rpcs <service> [detail]
fpn-rpc> list_rpcs fp_ip.Config
AddRoute
DelRoute
fpn-rpc> list_rpcs fp_ip.Config detail
AddRoute
request: AddRouteRequest
reply: AddRouteReply
DelRoute
request: DelRouteRequest
reply: DelRouteReply
fpn-rpc> help show_rpc
Show the schemas of RPC request and reply
show_rpc <service> <rpc>
fpn-rpc> show_rpc fp_ip.Config AddRoute
=== request: AddRouteRequest
uint32 vrfid
string prefix
string nexthop
string ifname
uint32 metric
=== reply: AddRouteReply
enum result
ADD_RT_OK
ADD_RT_ERR_EXIST
ADD_RT_ERR_INVALID_PARAMS
ADD_RT_ERR_INTERNAL
string err_info
fpn-rpc> help rpc
Send a JSON rpc request to the fast path.
rpc <service> <rpc> <json>
fpn-rpc> rpc fp_ip.Config AddRoute {"vrfid": 0, "prefix": "10.100.0.0/16", "nexthop": "10.200.0.1" }
{ "result": "ADD_RT_OK" }
Test¶
Without arguments, fpn-rpc-test
does unit testing and benchmarks.
Synopsis
# fpn-rpc-test [<options>]
Parameters
- -h, --help¶
Show help.
- -t, --test¶
Run pack/unpack tests (default).
- -c, --simple-client¶
Run as simple client, using libfpn-rpc.
- -a, --standalone-client¶
Run as standalone client, without using libfpn-rpc.
- -e, --event-client¶
Run as event-driven client for benchmark.
- -d, --dump-client¶
Run as simple dump client.
- -m, --monitor-client¶
Run as simple monitoring client.
- -s, --server¶
Run as server.
- -u <path>, --unix-path <path>¶
Path to the unix socket (default: /tmp/fpn-rpc-test).
- -v, --verbose¶
Dump debug information.
Example
Start a server in one terminal:
# fpn-rpc-test -s
rx: 0 msgs tx: 0 msgs
rx: 0 msgs tx: 0 msgs
rx: 0 msgs tx: 0 msgs
(...)
Start a simple client in another terminal:
# fpn-rpc-test -c
result: ADD_OK
Example in C¶
The following code demonstrate how to send a rpc, without using
libfpn-rpc
. It requires to compile and link the user protobuf schemas as
well as fpn-rpc.proto
, which is needed for rpc transport.
/*
* Copyright 2021 6WIND S.A.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include "client-standalone.h"
#include "fpn-rpc-test.pb-c.h"
#include "fpn-rpc.pb-c.h"
/* connect to a unix server */
static int unix_connect(const char *path)
{
struct sockaddr_un addr;
int s = -1;
int ret;
s = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (s < 0) {
fprintf(stderr, "socket() failed: %s\n", strerror(errno));
goto fail;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
ret = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
if (ret < 0 || (size_t)ret >= sizeof(addr.sun_path)) {
fprintf(stderr, "unix path too long: %s\n", path);
errno = ENAMETOOLONG;
goto fail;
}
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "connect() failed: %s\n", strerror(errno));
goto fail;
}
return s;
fail:
if (s >= 0)
close(s);
return -1;
}
static int send_add_route4_req(int s)
{
FpnRpcTest__AddRoute4Request req = FPN_RPC_TEST__ADD_ROUTE4_REQUEST__INIT;
FpnRpc__Message outer = FPN_RPC__MESSAGE__INIT;
unsigned char inner_buf[65536];
unsigned char outer_buf[65536];
size_t inner_size;
size_t outer_size;
int ret;
/* the inner rpc, containing the user message */
req.vrfid = 0;
req.prefix = "10.0.0.0/8";
req.nexthop = "1.2.3.4";
inner_size = fpn_rpc_test__add_route4_request__get_packed_size(&req);
if (inner_size > sizeof(inner_buf)) {
fprintf(stderr, "failed to pack inner rpc: message too big\n");
return -1;
}
fpn_rpc_test__add_route4_request__pack(&req, inner_buf);
/* the type of the outer message is always fpn_rpc.Message */
outer.data.len = inner_size;
outer.data.data = (unsigned char *)inner_buf;
outer.msgtype = FPN_RPC__MSG_TYPE__REQUEST;
outer.service_name = (char *)"fpn_rpc_test.Config";
outer.rpc_name = (char *)"AddRoute4";
outer_size = fpn_rpc__message__get_packed_size(&outer);
if (outer_size > sizeof(outer_buf)) {
fprintf(stderr, "failed to pack outer rpc: message too big\n");
return -1;
}
fpn_rpc__message__pack(&outer, outer_buf);
/* send it on the unix socket */
ret = write(s, outer_buf, outer_size);
if (ret < 0) {
fprintf(stderr, "failed to write to unix socket: %s\n", strerror(errno));
return -1;
}
if ((size_t)ret != outer_size) {
fprintf(stderr, "unexpected write() return value: %d instead of %zd\n",
ret, outer_size);
return -1;
}
return 0;
}
static int recv_add_route4_rpl(int s)
{
const ProtobufCEnumValue *result_val;
FpnRpcTest__AddRoute4Reply *rpl = NULL;
FpnRpc__Message *outer = NULL;
unsigned char outer_buf[65536];
int ret;
ret = read(s, outer_buf, sizeof(outer_buf));
if (ret < 0) {
fprintf(stderr, "failed to read from unix socket: %s\n", strerror(errno));
goto fail;
}
if (ret == 0) {
fprintf(stderr, "failed to read from unix socket: short read\n");
goto fail;
}
outer = fpn_rpc__message__unpack(NULL, ret, outer_buf);
if (outer == NULL) {
fprintf(stderr, "failed to unpack outer rx message\n");
goto fail;
}
if (protobuf_c_message_check(&outer->base) == false) {
fprintf(stderr, "failed to validate outer rx message\n");
goto fail;
}
if (outer->msgtype != FPN_RPC__MSG_TYPE__REPLY) {
fprintf(stderr, "invalid message type\n");
goto fail;
}
if (outer->service_name == NULL || strcmp(outer->service_name, "fpn_rpc_test.Config")) {
fprintf(stderr, "invalid service name\n");
goto fail;
}
if (outer->rpc_name == NULL || strcmp(outer->rpc_name, "AddRoute4")) {
fprintf(stderr, "invalid rpc name\n");
goto fail;
}
rpl = fpn_rpc_test__add_route4_reply__unpack(NULL, outer->data.len, outer->data.data);
if (rpl == NULL) {
fprintf(stderr, "failed to unpack inner rx message\n");
goto fail;
}
if (protobuf_c_message_check(&rpl->base) == false) {
fprintf(stderr, "failed to validate inner rx message");
goto fail;
}
result_val = protobuf_c_enum_descriptor_get_value(
&fpn_rpc_test__add_route_reply_result__descriptor, rpl->result);
printf("result: %s\n", result_val ? result_val->name : NULL);
fpn_rpc_test__add_route4_reply__free_unpacked(rpl, NULL);
fpn_rpc__message__free_unpacked(outer, NULL);
return 0;
fail:
fpn_rpc_test__add_route4_reply__free_unpacked(rpl, NULL);
fpn_rpc__message__free_unpacked(outer, NULL);
return -1;
}
int client_standalone(const char *unix_path)
{
int ret = -1;
int s;
s = unix_connect(unix_path);
if (s < 0)
goto out;
if (send_add_route4_req(s) < 0)
goto out;
if (recv_add_route4_rpl(s) < 0)
goto out;
ret = 0;
out:
if (s >= 0)
close(s);
return ret;
}