]> git.proxmox.com Git - mirror_frr.git/blame - zebra/dplane_fpm_nl.c
python: add check-first-header tool
[mirror_frr.git] / zebra / dplane_fpm_nl.c
CommitLineData
d35f447d
RZ
1/*
2 * Zebra dataplane plugin for Forwarding Plane Manager (FPM) using netlink.
3 *
4 * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
5 * Rafael Zalamena
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; see the file COPYING; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include <arpa/inet.h>
23
24#include <sys/types.h>
25#include <sys/socket.h>
26
27#include <errno.h>
28#include <string.h>
29
30#include "config.h" /* Include this explicitly */
31#include "lib/zebra.h"
6cc059cd 32#include "lib/json.h"
d35f447d 33#include "lib/libfrr.h"
c871e6c9 34#include "lib/frratomic.h"
3bdd7fca 35#include "lib/command.h"
d35f447d
RZ
36#include "lib/memory.h"
37#include "lib/network.h"
38#include "lib/ns.h"
39#include "lib/frr_pthread.h"
e5e444d8 40#include "zebra/debug.h"
bda10adf 41#include "zebra/interface.h"
d35f447d 42#include "zebra/zebra_dplane.h"
018e77bc 43#include "zebra/zebra_router.h"
bda10adf 44#include "zebra/zebra_vxlan_private.h"
d35f447d
RZ
45#include "zebra/kernel_netlink.h"
46#include "zebra/rt_netlink.h"
47#include "zebra/debug.h"
48
49#define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK
50#define SOUTHBOUND_DEFAULT_PORT 2620
51
a179ba35
RZ
52/**
53 * FPM header:
54 * {
55 * version: 1 byte (always 1),
56 * type: 1 byte (1 for netlink, 2 protobuf),
57 * len: 2 bytes (network order),
58 * }
59 *
60 * This header is used with any format to tell the users how many bytes to
61 * expect.
62 */
63#define FPM_HEADER_SIZE 4
64
d35f447d
RZ
65static const char *prov_name = "dplane_fpm_nl";
66
67struct fpm_nl_ctx {
68 /* data plane connection. */
69 int socket;
3bdd7fca 70 bool disabled;
d35f447d 71 bool connecting;
018e77bc 72 bool rib_complete;
bda10adf 73 bool rmac_complete;
d35f447d
RZ
74 struct sockaddr_storage addr;
75
76 /* data plane buffers. */
77 struct stream *ibuf;
78 struct stream *obuf;
79 pthread_mutex_t obuf_mutex;
80
ba803a2f
RZ
81 /*
82 * data plane context queue:
83 * When a FPM server connection becomes a bottleneck, we must keep the
84 * data plane contexts until we get a chance to process them.
85 */
86 struct dplane_ctx_q ctxqueue;
87 pthread_mutex_t ctxqueue_mutex;
88
d35f447d 89 /* data plane events. */
ba803a2f 90 struct zebra_dplane_provider *prov;
d35f447d
RZ
91 struct frr_pthread *fthread;
92 struct thread *t_connect;
93 struct thread *t_read;
94 struct thread *t_write;
3bdd7fca 95 struct thread *t_event;
ba803a2f 96 struct thread *t_dequeue;
018e77bc
RZ
97
98 /* zebra events. */
99 struct thread *t_ribreset;
100 struct thread *t_ribwalk;
bda10adf
RZ
101 struct thread *t_rmacreset;
102 struct thread *t_rmacwalk;
6cc059cd
RZ
103
104 /* Statistic counters. */
105 struct {
106 /* Amount of bytes read into ibuf. */
770a8d28 107 _Atomic uint32_t bytes_read;
6cc059cd 108 /* Amount of bytes written from obuf. */
770a8d28 109 _Atomic uint32_t bytes_sent;
ad4d1022 110 /* Output buffer current usage. */
770a8d28 111 _Atomic uint32_t obuf_bytes;
ad4d1022 112 /* Output buffer peak usage. */
770a8d28 113 _Atomic uint32_t obuf_peak;
6cc059cd
RZ
114
115 /* Amount of connection closes. */
770a8d28 116 _Atomic uint32_t connection_closes;
6cc059cd 117 /* Amount of connection errors. */
770a8d28 118 _Atomic uint32_t connection_errors;
6cc059cd
RZ
119
120 /* Amount of user configurations: FNE_RECONNECT. */
770a8d28 121 _Atomic uint32_t user_configures;
6cc059cd 122 /* Amount of user disable requests: FNE_DISABLE. */
770a8d28 123 _Atomic uint32_t user_disables;
6cc059cd
RZ
124
125 /* Amount of data plane context processed. */
770a8d28 126 _Atomic uint32_t dplane_contexts;
ba803a2f 127 /* Amount of data plane contexts enqueued. */
770a8d28 128 _Atomic uint32_t ctxqueue_len;
ba803a2f 129 /* Peak amount of data plane contexts enqueued. */
770a8d28 130 _Atomic uint32_t ctxqueue_len_peak;
6cc059cd
RZ
131
132 /* Amount of buffer full events. */
770a8d28 133 _Atomic uint32_t buffer_full;
6cc059cd 134 } counters;
770a8d28 135} *gfnc;
3bdd7fca
RZ
136
137enum fpm_nl_events {
138 /* Ask for FPM to reconnect the external server. */
139 FNE_RECONNECT,
140 /* Disable FPM. */
141 FNE_DISABLE,
6cc059cd
RZ
142 /* Reset counters. */
143 FNE_RESET_COUNTERS,
d35f447d
RZ
144};
145
018e77bc
RZ
146/*
147 * Prototypes.
148 */
3bdd7fca 149static int fpm_process_event(struct thread *t);
018e77bc
RZ
150static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx);
151static int fpm_rib_send(struct thread *t);
152static int fpm_rib_reset(struct thread *t);
bda10adf
RZ
153static int fpm_rmac_send(struct thread *t);
154static int fpm_rmac_reset(struct thread *t);
018e77bc 155
ad4d1022
RZ
156/*
157 * Helper functions.
158 */
159
160/**
161 * Reorganizes the data on the buffer so it can fit more data.
162 *
163 * @param s stream pointer.
164 */
165static void stream_pulldown(struct stream *s)
166{
167 size_t rlen = STREAM_READABLE(s);
168
169 /* No more data, so just move the pointers. */
170 if (rlen == 0) {
171 stream_reset(s);
172 return;
173 }
174
175 /* Move the available data to the beginning. */
176 memmove(s->data, &s->data[s->getp], rlen);
177 s->getp = 0;
178 s->endp = rlen;
179}
180
3bdd7fca
RZ
181/*
182 * CLI.
183 */
6cc059cd
RZ
184#define FPM_STR "Forwarding Plane Manager configuration\n"
185
3bdd7fca
RZ
186DEFUN(fpm_set_address, fpm_set_address_cmd,
187 "fpm address <A.B.C.D|X:X::X:X> [port (1-65535)]",
6cc059cd 188 FPM_STR
3bdd7fca
RZ
189 "FPM remote listening server address\n"
190 "Remote IPv4 FPM server\n"
191 "Remote IPv6 FPM server\n"
192 "FPM remote listening server port\n"
193 "Remote FPM server port\n")
194{
195 struct sockaddr_in *sin;
196 struct sockaddr_in6 *sin6;
197 uint16_t port = 0;
198 uint8_t naddr[INET6_BUFSIZ];
199
200 if (argc == 5)
201 port = strtol(argv[4]->arg, NULL, 10);
202
203 /* Handle IPv4 addresses. */
204 if (inet_pton(AF_INET, argv[2]->arg, naddr) == 1) {
205 sin = (struct sockaddr_in *)&gfnc->addr;
206
207 memset(sin, 0, sizeof(*sin));
208 sin->sin_family = AF_INET;
209 sin->sin_port =
210 port ? htons(port) : htons(SOUTHBOUND_DEFAULT_PORT);
211#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
212 sin->sin_len = sizeof(*sin);
213#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
214 memcpy(&sin->sin_addr, naddr, sizeof(sin->sin_addr));
215
216 goto ask_reconnect;
217 }
218
219 /* Handle IPv6 addresses. */
220 if (inet_pton(AF_INET6, argv[2]->arg, naddr) != 1) {
221 vty_out(vty, "%% Invalid address: %s\n", argv[2]->arg);
222 return CMD_WARNING;
223 }
224
225 sin6 = (struct sockaddr_in6 *)&gfnc->addr;
226 memset(sin6, 0, sizeof(*sin6));
227 sin6->sin6_family = AF_INET6;
228 sin6->sin6_port = port ? htons(port) : htons(SOUTHBOUND_DEFAULT_PORT);
229#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
230 sin6->sin6_len = sizeof(*sin6);
231#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
232 memcpy(&sin6->sin6_addr, naddr, sizeof(sin6->sin6_addr));
233
234ask_reconnect:
235 thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc,
236 FNE_RECONNECT, &gfnc->t_event);
237 return CMD_SUCCESS;
238}
239
240DEFUN(no_fpm_set_address, no_fpm_set_address_cmd,
241 "no fpm address [<A.B.C.D|X:X::X:X> [port <1-65535>]]",
242 NO_STR
6cc059cd 243 FPM_STR
3bdd7fca
RZ
244 "FPM remote listening server address\n"
245 "Remote IPv4 FPM server\n"
246 "Remote IPv6 FPM server\n"
247 "FPM remote listening server port\n"
248 "Remote FPM server port\n")
249{
250 thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc,
251 FNE_DISABLE, &gfnc->t_event);
252 return CMD_SUCCESS;
253}
254
6cc059cd
RZ
255DEFUN(fpm_reset_counters, fpm_reset_counters_cmd,
256 "clear fpm counters",
257 CLEAR_STR
258 FPM_STR
259 "FPM statistic counters\n")
260{
261 thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc,
262 FNE_RESET_COUNTERS, &gfnc->t_event);
263 return CMD_SUCCESS;
264}
265
266DEFUN(fpm_show_counters, fpm_show_counters_cmd,
267 "show fpm counters",
268 SHOW_STR
269 FPM_STR
270 "FPM statistic counters\n")
271{
272 vty_out(vty, "%30s\n%30s\n", "FPM counters", "============");
273
274#define SHOW_COUNTER(label, counter) \
770a8d28 275 vty_out(vty, "%28s: %u\n", (label), (counter))
6cc059cd
RZ
276
277 SHOW_COUNTER("Input bytes", gfnc->counters.bytes_read);
278 SHOW_COUNTER("Output bytes", gfnc->counters.bytes_sent);
ad4d1022
RZ
279 SHOW_COUNTER("Output buffer current size", gfnc->counters.obuf_bytes);
280 SHOW_COUNTER("Output buffer peak size", gfnc->counters.obuf_peak);
6cc059cd
RZ
281 SHOW_COUNTER("Connection closes", gfnc->counters.connection_closes);
282 SHOW_COUNTER("Connection errors", gfnc->counters.connection_errors);
283 SHOW_COUNTER("Data plane items processed",
284 gfnc->counters.dplane_contexts);
ba803a2f
RZ
285 SHOW_COUNTER("Data plane items enqueued",
286 gfnc->counters.ctxqueue_len);
287 SHOW_COUNTER("Data plane items queue peak",
288 gfnc->counters.ctxqueue_len_peak);
6cc059cd
RZ
289 SHOW_COUNTER("Buffer full hits", gfnc->counters.buffer_full);
290 SHOW_COUNTER("User FPM configurations", gfnc->counters.user_configures);
291 SHOW_COUNTER("User FPM disable requests", gfnc->counters.user_disables);
292
293#undef SHOW_COUNTER
294
295 return CMD_SUCCESS;
296}
297
298DEFUN(fpm_show_counters_json, fpm_show_counters_json_cmd,
299 "show fpm counters json",
300 SHOW_STR
301 FPM_STR
302 "FPM statistic counters\n"
303 JSON_STR)
304{
305 struct json_object *jo;
306
307 jo = json_object_new_object();
308 json_object_int_add(jo, "bytes-read", gfnc->counters.bytes_read);
309 json_object_int_add(jo, "bytes-sent", gfnc->counters.bytes_sent);
ad4d1022
RZ
310 json_object_int_add(jo, "obuf-bytes", gfnc->counters.obuf_bytes);
311 json_object_int_add(jo, "obuf-bytes-peak", gfnc->counters.obuf_peak);
a50404aa
RZ
312 json_object_int_add(jo, "connection-closes",
313 gfnc->counters.connection_closes);
314 json_object_int_add(jo, "connection-errors",
315 gfnc->counters.connection_errors);
316 json_object_int_add(jo, "data-plane-contexts",
317 gfnc->counters.dplane_contexts);
ba803a2f
RZ
318 json_object_int_add(jo, "data-plane-contexts-queue",
319 gfnc->counters.ctxqueue_len);
320 json_object_int_add(jo, "data-plane-contexts-queue-peak",
321 gfnc->counters.ctxqueue_len_peak);
6cc059cd 322 json_object_int_add(jo, "buffer-full-hits", gfnc->counters.buffer_full);
a50404aa
RZ
323 json_object_int_add(jo, "user-configures",
324 gfnc->counters.user_configures);
6cc059cd
RZ
325 json_object_int_add(jo, "user-disables", gfnc->counters.user_disables);
326 vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0));
327 json_object_free(jo);
328
329 return CMD_SUCCESS;
330}
331
3bdd7fca
RZ
332static int fpm_write_config(struct vty *vty)
333{
334 struct sockaddr_in *sin;
335 struct sockaddr_in6 *sin6;
336 int written = 0;
337 char addrstr[INET6_ADDRSTRLEN];
338
339 if (gfnc->disabled)
340 return written;
341
342 switch (gfnc->addr.ss_family) {
343 case AF_INET:
344 written = 1;
345 sin = (struct sockaddr_in *)&gfnc->addr;
346 inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr));
347 vty_out(vty, "fpm address %s", addrstr);
348 if (sin->sin_port != htons(SOUTHBOUND_DEFAULT_PORT))
349 vty_out(vty, " port %d", ntohs(sin->sin_port));
350
351 vty_out(vty, "\n");
352 break;
353 case AF_INET6:
354 written = 1;
355 sin6 = (struct sockaddr_in6 *)&gfnc->addr;
356 inet_ntop(AF_INET, &sin6->sin6_addr, addrstr, sizeof(addrstr));
357 vty_out(vty, "fpm address %s", addrstr);
358 if (sin6->sin6_port != htons(SOUTHBOUND_DEFAULT_PORT))
359 vty_out(vty, " port %d", ntohs(sin6->sin6_port));
360
361 vty_out(vty, "\n");
362 break;
363
364 default:
365 break;
366 }
367
368 return written;
369}
370
612c2c15 371static struct cmd_node fpm_node = {
893d8beb
DL
372 .name = "fpm",
373 .node = FPM_NODE,
3bdd7fca 374 .prompt = "",
612c2c15 375 .config_write = fpm_write_config,
3bdd7fca
RZ
376};
377
d35f447d
RZ
378/*
379 * FPM functions.
380 */
381static int fpm_connect(struct thread *t);
382
383static void fpm_reconnect(struct fpm_nl_ctx *fnc)
384{
385 /* Grab the lock to empty the stream and stop the zebra thread. */
386 frr_mutex_lock_autounlock(&fnc->obuf_mutex);
387
3bdd7fca
RZ
388 /* Avoid calling close on `-1`. */
389 if (fnc->socket != -1) {
390 close(fnc->socket);
391 fnc->socket = -1;
392 }
393
d35f447d
RZ
394 stream_reset(fnc->ibuf);
395 stream_reset(fnc->obuf);
396 THREAD_OFF(fnc->t_read);
397 THREAD_OFF(fnc->t_write);
018e77bc
RZ
398
399 if (fnc->t_ribreset)
400 thread_cancel_async(zrouter.master, &fnc->t_ribreset, NULL);
401 if (fnc->t_ribwalk)
402 thread_cancel_async(zrouter.master, &fnc->t_ribwalk, NULL);
bda10adf
RZ
403 if (fnc->t_rmacreset)
404 thread_cancel_async(zrouter.master, &fnc->t_rmacreset, NULL);
405 if (fnc->t_rmacwalk)
406 thread_cancel_async(zrouter.master, &fnc->t_rmacwalk, NULL);
018e77bc 407
3bdd7fca
RZ
408 /* FPM is disabled, don't attempt to connect. */
409 if (fnc->disabled)
410 return;
411
d35f447d
RZ
412 thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3,
413 &fnc->t_connect);
414}
415
416static int fpm_read(struct thread *t)
417{
418 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
419 ssize_t rv;
420
421 /* Let's ignore the input at the moment. */
422 rv = stream_read_try(fnc->ibuf, fnc->socket,
423 STREAM_WRITEABLE(fnc->ibuf));
424 if (rv == 0) {
c871e6c9
RZ
425 atomic_fetch_add_explicit(&fnc->counters.connection_closes, 1,
426 memory_order_relaxed);
e5e444d8
RZ
427
428 if (IS_ZEBRA_DEBUG_FPM)
429 zlog_debug("%s: connection closed", __func__);
430
d35f447d
RZ
431 fpm_reconnect(fnc);
432 return 0;
433 }
434 if (rv == -1) {
435 if (errno == EAGAIN || errno == EWOULDBLOCK
436 || errno == EINTR)
437 return 0;
438
c871e6c9
RZ
439 atomic_fetch_add_explicit(&fnc->counters.connection_errors, 1,
440 memory_order_relaxed);
e5e444d8
RZ
441 zlog_warn("%s: connection failure: %s", __func__,
442 strerror(errno));
d35f447d
RZ
443 fpm_reconnect(fnc);
444 return 0;
445 }
446 stream_reset(fnc->ibuf);
447
6cc059cd 448 /* Account all bytes read. */
c871e6c9
RZ
449 atomic_fetch_add_explicit(&fnc->counters.bytes_read, rv,
450 memory_order_relaxed);
6cc059cd 451
d35f447d
RZ
452 thread_add_read(fnc->fthread->master, fpm_read, fnc, fnc->socket,
453 &fnc->t_read);
454
455 return 0;
456}
457
458static int fpm_write(struct thread *t)
459{
460 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
461 socklen_t statuslen;
462 ssize_t bwritten;
463 int rv, status;
464 size_t btotal;
465
466 if (fnc->connecting == true) {
467 status = 0;
468 statuslen = sizeof(status);
469
470 rv = getsockopt(fnc->socket, SOL_SOCKET, SO_ERROR, &status,
471 &statuslen);
472 if (rv == -1 || status != 0) {
473 if (rv != -1)
e5e444d8
RZ
474 zlog_warn("%s: connection failed: %s", __func__,
475 strerror(status));
d35f447d 476 else
e5e444d8
RZ
477 zlog_warn("%s: SO_ERROR failed: %s", __func__,
478 strerror(status));
d35f447d 479
c871e6c9
RZ
480 atomic_fetch_add_explicit(
481 &fnc->counters.connection_errors, 1,
482 memory_order_relaxed);
6cc059cd 483
d35f447d
RZ
484 fpm_reconnect(fnc);
485 return 0;
486 }
487
488 fnc->connecting = false;
018e77bc
RZ
489
490 /* Ask zebra main thread to start walking the RIB table. */
491 thread_add_timer(zrouter.master, fpm_rib_send, fnc, 0,
492 &fnc->t_ribwalk);
bda10adf
RZ
493 thread_add_timer(zrouter.master, fpm_rmac_send, fnc, 0,
494 &fnc->t_rmacwalk);
d35f447d
RZ
495 }
496
497 frr_mutex_lock_autounlock(&fnc->obuf_mutex);
498
499 while (true) {
500 /* Stream is empty: reset pointers and return. */
501 if (STREAM_READABLE(fnc->obuf) == 0) {
502 stream_reset(fnc->obuf);
503 break;
504 }
505
506 /* Try to write all at once. */
507 btotal = stream_get_endp(fnc->obuf) -
508 stream_get_getp(fnc->obuf);
509 bwritten = write(fnc->socket, stream_pnt(fnc->obuf), btotal);
510 if (bwritten == 0) {
c871e6c9
RZ
511 atomic_fetch_add_explicit(
512 &fnc->counters.connection_closes, 1,
513 memory_order_relaxed);
e5e444d8
RZ
514
515 if (IS_ZEBRA_DEBUG_FPM)
516 zlog_debug("%s: connection closed", __func__);
d35f447d
RZ
517 break;
518 }
519 if (bwritten == -1) {
ad4d1022
RZ
520 /* Attempt to continue if blocked by a signal. */
521 if (errno == EINTR)
522 continue;
523 /* Receiver is probably slow, lets give it some time. */
524 if (errno == EAGAIN || errno == EWOULDBLOCK)
d35f447d
RZ
525 break;
526
c871e6c9
RZ
527 atomic_fetch_add_explicit(
528 &fnc->counters.connection_errors, 1,
529 memory_order_relaxed);
e5e444d8
RZ
530 zlog_warn("%s: connection failure: %s", __func__,
531 strerror(errno));
d35f447d
RZ
532 fpm_reconnect(fnc);
533 break;
534 }
535
6cc059cd 536 /* Account all bytes sent. */
c871e6c9
RZ
537 atomic_fetch_add_explicit(&fnc->counters.bytes_sent, bwritten,
538 memory_order_relaxed);
6cc059cd 539
ad4d1022 540 /* Account number of bytes free. */
c871e6c9
RZ
541 atomic_fetch_sub_explicit(&fnc->counters.obuf_bytes, bwritten,
542 memory_order_relaxed);
ad4d1022 543
d35f447d
RZ
544 stream_forward_getp(fnc->obuf, (size_t)bwritten);
545 }
546
547 /* Stream is not empty yet, we must schedule more writes. */
548 if (STREAM_READABLE(fnc->obuf)) {
ad4d1022 549 stream_pulldown(fnc->obuf);
d35f447d
RZ
550 thread_add_write(fnc->fthread->master, fpm_write, fnc,
551 fnc->socket, &fnc->t_write);
552 return 0;
553 }
554
555 return 0;
556}
557
558static int fpm_connect(struct thread *t)
559{
560 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
3bdd7fca
RZ
561 struct sockaddr_in *sin = (struct sockaddr_in *)&fnc->addr;
562 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&fnc->addr;
563 socklen_t slen;
d35f447d
RZ
564 int rv, sock;
565 char addrstr[INET6_ADDRSTRLEN];
566
3bdd7fca 567 sock = socket(fnc->addr.ss_family, SOCK_STREAM, 0);
d35f447d 568 if (sock == -1) {
6cc059cd 569 zlog_err("%s: fpm socket failed: %s", __func__,
d35f447d
RZ
570 strerror(errno));
571 thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3,
572 &fnc->t_connect);
573 return 0;
574 }
575
576 set_nonblocking(sock);
577
3bdd7fca
RZ
578 if (fnc->addr.ss_family == AF_INET) {
579 inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr));
580 slen = sizeof(*sin);
581 } else {
582 inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, sizeof(addrstr));
583 slen = sizeof(*sin6);
584 }
d35f447d 585
e5e444d8
RZ
586 if (IS_ZEBRA_DEBUG_FPM)
587 zlog_debug("%s: attempting to connect to %s:%d", __func__,
588 addrstr, ntohs(sin->sin_port));
d35f447d 589
3bdd7fca 590 rv = connect(sock, (struct sockaddr *)&fnc->addr, slen);
d35f447d 591 if (rv == -1 && errno != EINPROGRESS) {
c871e6c9
RZ
592 atomic_fetch_add_explicit(&fnc->counters.connection_errors, 1,
593 memory_order_relaxed);
d35f447d
RZ
594 close(sock);
595 zlog_warn("%s: fpm connection failed: %s", __func__,
596 strerror(errno));
597 thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3,
598 &fnc->t_connect);
599 return 0;
600 }
601
602 fnc->connecting = (errno == EINPROGRESS);
603 fnc->socket = sock;
604 thread_add_read(fnc->fthread->master, fpm_read, fnc, sock,
605 &fnc->t_read);
606 thread_add_write(fnc->fthread->master, fpm_write, fnc, sock,
607 &fnc->t_write);
608
018e77bc
RZ
609 /* Mark all routes as unsent. */
610 thread_add_timer(zrouter.master, fpm_rib_reset, fnc, 0,
611 &fnc->t_ribreset);
bda10adf
RZ
612 thread_add_timer(zrouter.master, fpm_rmac_reset, fnc, 0,
613 &fnc->t_rmacreset);
018e77bc 614
d35f447d
RZ
615 return 0;
616}
617
618/**
619 * Encode data plane operation context into netlink and enqueue it in the FPM
620 * output buffer.
621 *
622 * @param fnc the netlink FPM context.
623 * @param ctx the data plane operation context data.
624 * @return 0 on success or -1 on not enough space.
625 */
626static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx)
627{
628 uint8_t nl_buf[NL_PKT_BUF_SIZE];
629 size_t nl_buf_len;
630 ssize_t rv;
edfeff42 631 uint64_t obytes, obytes_peak;
d35f447d
RZ
632
633 nl_buf_len = 0;
634
635 frr_mutex_lock_autounlock(&fnc->obuf_mutex);
636
637 switch (dplane_ctx_get_op(ctx)) {
638 case DPLANE_OP_ROUTE_UPDATE:
639 case DPLANE_OP_ROUTE_DELETE:
640 rv = netlink_route_multipath(RTM_DELROUTE, ctx, nl_buf,
f2a0ba3a 641 sizeof(nl_buf), true);
d35f447d 642 if (rv <= 0) {
e5e444d8
RZ
643 zlog_err("%s: netlink_route_multipath failed",
644 __func__);
d35f447d
RZ
645 return 0;
646 }
647
648 nl_buf_len = (size_t)rv;
d35f447d
RZ
649
650 /* UPDATE operations need a INSTALL, otherwise just quit. */
651 if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_DELETE)
652 break;
653
654 /* FALL THROUGH */
655 case DPLANE_OP_ROUTE_INSTALL:
656 rv = netlink_route_multipath(RTM_NEWROUTE, ctx,
657 &nl_buf[nl_buf_len],
f2a0ba3a 658 sizeof(nl_buf) - nl_buf_len, true);
d35f447d 659 if (rv <= 0) {
e5e444d8
RZ
660 zlog_err("%s: netlink_route_multipath failed",
661 __func__);
d35f447d
RZ
662 return 0;
663 }
664
665 nl_buf_len += (size_t)rv;
d35f447d
RZ
666 break;
667
bda10adf
RZ
668 case DPLANE_OP_MAC_INSTALL:
669 case DPLANE_OP_MAC_DELETE:
670 rv = netlink_macfdb_update_ctx(ctx, nl_buf, sizeof(nl_buf));
671 if (rv <= 0) {
e5e444d8
RZ
672 zlog_err("%s: netlink_macfdb_update_ctx failed",
673 __func__);
bda10adf
RZ
674 return 0;
675 }
676
677 nl_buf_len = (size_t)rv;
bda10adf
RZ
678 break;
679
d35f447d
RZ
680 case DPLANE_OP_NH_INSTALL:
681 case DPLANE_OP_NH_UPDATE:
682 case DPLANE_OP_NH_DELETE:
683 case DPLANE_OP_LSP_INSTALL:
684 case DPLANE_OP_LSP_UPDATE:
685 case DPLANE_OP_LSP_DELETE:
686 case DPLANE_OP_PW_INSTALL:
687 case DPLANE_OP_PW_UNINSTALL:
688 case DPLANE_OP_ADDR_INSTALL:
689 case DPLANE_OP_ADDR_UNINSTALL:
d35f447d
RZ
690 case DPLANE_OP_NEIGH_INSTALL:
691 case DPLANE_OP_NEIGH_UPDATE:
692 case DPLANE_OP_NEIGH_DELETE:
693 case DPLANE_OP_VTEP_ADD:
694 case DPLANE_OP_VTEP_DELETE:
695 case DPLANE_OP_SYS_ROUTE_ADD:
696 case DPLANE_OP_SYS_ROUTE_DELETE:
697 case DPLANE_OP_ROUTE_NOTIFY:
698 case DPLANE_OP_LSP_NOTIFY:
699 case DPLANE_OP_NONE:
700 break;
701
702 default:
e5e444d8
RZ
703 if (IS_ZEBRA_DEBUG_FPM)
704 zlog_debug("%s: unhandled data plane message (%d) %s",
705 __func__, dplane_ctx_get_op(ctx),
706 dplane_op2str(dplane_ctx_get_op(ctx)));
d35f447d
RZ
707 break;
708 }
709
710 /* Skip empty enqueues. */
711 if (nl_buf_len == 0)
712 return 0;
713
a179ba35
RZ
714 /* We must know if someday a message goes beyond 65KiB. */
715 assert((nl_buf_len + FPM_HEADER_SIZE) <= UINT16_MAX);
716
717 /* Check if we have enough buffer space. */
718 if (STREAM_WRITEABLE(fnc->obuf) < (nl_buf_len + FPM_HEADER_SIZE)) {
c871e6c9
RZ
719 atomic_fetch_add_explicit(&fnc->counters.buffer_full, 1,
720 memory_order_relaxed);
e5e444d8
RZ
721
722 if (IS_ZEBRA_DEBUG_FPM)
723 zlog_debug(
724 "%s: buffer full: wants to write %zu but has %zu",
725 __func__, nl_buf_len + FPM_HEADER_SIZE,
726 STREAM_WRITEABLE(fnc->obuf));
727
a179ba35
RZ
728 return -1;
729 }
730
d35f447d 731 /*
a179ba35
RZ
732 * Fill in the FPM header information.
733 *
734 * See FPM_HEADER_SIZE definition for more information.
d35f447d
RZ
735 */
736 stream_putc(fnc->obuf, 1);
737 stream_putc(fnc->obuf, 1);
a179ba35 738 stream_putw(fnc->obuf, nl_buf_len + FPM_HEADER_SIZE);
d35f447d
RZ
739
740 /* Write current data. */
741 stream_write(fnc->obuf, nl_buf, (size_t)nl_buf_len);
742
ad4d1022 743 /* Account number of bytes waiting to be written. */
c871e6c9
RZ
744 atomic_fetch_add_explicit(&fnc->counters.obuf_bytes,
745 nl_buf_len + FPM_HEADER_SIZE,
746 memory_order_relaxed);
edfeff42
RZ
747 obytes = atomic_load_explicit(&fnc->counters.obuf_bytes,
748 memory_order_relaxed);
749 obytes_peak = atomic_load_explicit(&fnc->counters.obuf_peak,
750 memory_order_relaxed);
751 if (obytes_peak < obytes)
c871e6c9
RZ
752 atomic_store_explicit(&fnc->counters.obuf_peak, obytes,
753 memory_order_relaxed);
ad4d1022 754
d35f447d
RZ
755 /* Tell the thread to start writing. */
756 thread_add_write(fnc->fthread->master, fpm_write, fnc, fnc->socket,
757 &fnc->t_write);
758
759 return 0;
760}
761
018e77bc
RZ
762/**
763 * Send all RIB installed routes to the connected data plane.
764 */
765static int fpm_rib_send(struct thread *t)
766{
767 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
768 rib_dest_t *dest;
769 struct route_node *rn;
770 struct route_table *rt;
771 struct zebra_dplane_ctx *ctx;
772 rib_tables_iter_t rt_iter;
773
774 /* Allocate temporary context for all transactions. */
775 ctx = dplane_ctx_alloc();
776
777 rt_iter.state = RIB_TABLES_ITER_S_INIT;
778 while ((rt = rib_tables_iter_next(&rt_iter))) {
779 for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) {
780 dest = rib_dest_from_rnode(rn);
781 /* Skip bad route entries. */
a50404aa 782 if (dest == NULL || dest->selected_fib == NULL)
018e77bc 783 continue;
018e77bc
RZ
784
785 /* Check for already sent routes. */
a50404aa 786 if (CHECK_FLAG(dest->flags, RIB_DEST_UPDATE_FPM))
018e77bc 787 continue;
018e77bc
RZ
788
789 /* Enqueue route install. */
790 dplane_ctx_reset(ctx);
791 dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_INSTALL, rn,
792 dest->selected_fib);
793 if (fpm_nl_enqueue(fnc, ctx) == -1) {
794 /* Free the temporary allocated context. */
795 dplane_ctx_fini(&ctx);
796
018e77bc
RZ
797 thread_add_timer(zrouter.master, fpm_rib_send,
798 fnc, 1, &fnc->t_ribwalk);
799 return 0;
800 }
801
802 /* Mark as sent. */
803 SET_FLAG(dest->flags, RIB_DEST_UPDATE_FPM);
804 }
805 }
806
807 /* Free the temporary allocated context. */
808 dplane_ctx_fini(&ctx);
809
810 /* All RIB routes sent! */
811 fnc->rib_complete = true;
812
813 return 0;
814}
815
bda10adf
RZ
816/*
817 * The next three functions will handle RMAC enqueue.
818 */
819struct fpm_rmac_arg {
820 struct zebra_dplane_ctx *ctx;
821 struct fpm_nl_ctx *fnc;
822 zebra_l3vni_t *zl3vni;
823};
824
9d5c3268 825static void fpm_enqueue_rmac_table(struct hash_bucket *backet, void *arg)
bda10adf
RZ
826{
827 struct fpm_rmac_arg *fra = arg;
828 zebra_mac_t *zrmac = backet->data;
829 struct zebra_if *zif = fra->zl3vni->vxlan_if->info;
830 const struct zebra_l2info_vxlan *vxl = &zif->l2info.vxl;
831 struct zebra_if *br_zif;
832 vlanid_t vid;
833 bool sticky;
834
835 /* Entry already sent. */
836 if (CHECK_FLAG(zrmac->flags, ZEBRA_MAC_FPM_SENT))
837 return;
838
839 sticky = !!CHECK_FLAG(zrmac->flags,
840 (ZEBRA_MAC_STICKY | ZEBRA_MAC_REMOTE_DEF_GW));
841 br_zif = (struct zebra_if *)(zif->brslave_info.br_if->info);
842 vid = IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif) ? vxl->access_vlan : 0;
843
844 dplane_ctx_reset(fra->ctx);
845 dplane_ctx_set_op(fra->ctx, DPLANE_OP_MAC_INSTALL);
846 dplane_mac_init(fra->ctx, fra->zl3vni->vxlan_if,
f2a0ba3a
RZ
847 zif->brslave_info.br_if, vid,
848 &zrmac->macaddr, zrmac->fwd_info.r_vtep_ip, sticky);
bda10adf 849 if (fpm_nl_enqueue(fra->fnc, fra->ctx) == -1) {
bda10adf
RZ
850 thread_add_timer(zrouter.master, fpm_rmac_send,
851 fra->fnc, 1, &fra->fnc->t_rmacwalk);
852 }
853}
854
9d5c3268 855static void fpm_enqueue_l3vni_table(struct hash_bucket *backet, void *arg)
bda10adf
RZ
856{
857 struct fpm_rmac_arg *fra = arg;
858 zebra_l3vni_t *zl3vni = backet->data;
859
860 fra->zl3vni = zl3vni;
861 hash_iterate(zl3vni->rmac_table, fpm_enqueue_rmac_table, zl3vni);
862}
863
864static int fpm_rmac_send(struct thread *t)
865{
866 struct fpm_rmac_arg fra;
867
868 fra.fnc = THREAD_ARG(t);
869 fra.ctx = dplane_ctx_alloc();
870 hash_iterate(zrouter.l3vni_table, fpm_enqueue_l3vni_table, &fra);
871 dplane_ctx_fini(&fra.ctx);
872
873 return 0;
874}
875
018e77bc
RZ
876/**
877 * Resets the RIB FPM flags so we send all routes again.
878 */
879static int fpm_rib_reset(struct thread *t)
880{
881 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
882 rib_dest_t *dest;
883 struct route_node *rn;
884 struct route_table *rt;
885 rib_tables_iter_t rt_iter;
886
887 fnc->rib_complete = false;
888
889 rt_iter.state = RIB_TABLES_ITER_S_INIT;
890 while ((rt = rib_tables_iter_next(&rt_iter))) {
891 for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) {
892 dest = rib_dest_from_rnode(rn);
893 /* Skip bad route entries. */
894 if (dest == NULL)
895 continue;
896
897 UNSET_FLAG(dest->flags, RIB_DEST_UPDATE_FPM);
898 }
899 }
900
901 return 0;
902}
903
bda10adf
RZ
904/*
905 * The next three function will handle RMAC table reset.
906 */
9d5c3268 907static void fpm_unset_rmac_table(struct hash_bucket *backet, void *arg)
bda10adf
RZ
908{
909 zebra_mac_t *zrmac = backet->data;
910
911 UNSET_FLAG(zrmac->flags, ZEBRA_MAC_FPM_SENT);
912}
913
9d5c3268 914static void fpm_unset_l3vni_table(struct hash_bucket *backet, void *arg)
bda10adf
RZ
915{
916 zebra_l3vni_t *zl3vni = backet->data;
917
918 hash_iterate(zl3vni->rmac_table, fpm_unset_rmac_table, zl3vni);
919}
920
921static int fpm_rmac_reset(struct thread *t)
922{
923 hash_iterate(zrouter.l3vni_table, fpm_unset_l3vni_table, NULL);
924
925 return 0;
926}
927
ba803a2f
RZ
928static int fpm_process_queue(struct thread *t)
929{
930 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
931 struct zebra_dplane_ctx *ctx;
932
933 frr_mutex_lock_autounlock(&fnc->ctxqueue_mutex);
934
935 while (true) {
936 /* No space available yet. */
937 if (STREAM_WRITEABLE(fnc->obuf) < NL_PKT_BUF_SIZE)
938 break;
939
940 /* Dequeue next item or quit processing. */
941 ctx = dplane_ctx_dequeue(&fnc->ctxqueue);
942 if (ctx == NULL)
943 break;
944
945 fpm_nl_enqueue(fnc, ctx);
946
947 /* Account the processed entries. */
c871e6c9
RZ
948 atomic_fetch_add_explicit(&fnc->counters.dplane_contexts, 1,
949 memory_order_relaxed);
950 atomic_fetch_sub_explicit(&fnc->counters.ctxqueue_len, 1,
951 memory_order_relaxed);
ba803a2f
RZ
952
953 dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS);
954 dplane_provider_enqueue_out_ctx(fnc->prov, ctx);
955 }
956
957 /* Check for more items in the queue. */
c871e6c9
RZ
958 if (atomic_load_explicit(&fnc->counters.ctxqueue_len,
959 memory_order_relaxed)
960 > 0)
ba803a2f
RZ
961 thread_add_timer(fnc->fthread->master, fpm_process_queue,
962 fnc, 0, &fnc->t_dequeue);
963
964 return 0;
965}
966
3bdd7fca
RZ
967/**
968 * Handles external (e.g. CLI, data plane or others) events.
969 */
970static int fpm_process_event(struct thread *t)
971{
972 struct fpm_nl_ctx *fnc = THREAD_ARG(t);
973 int event = THREAD_VAL(t);
974
975 switch (event) {
976 case FNE_DISABLE:
e5e444d8 977 zlog_info("%s: manual FPM disable event", __func__);
3bdd7fca 978 fnc->disabled = true;
c871e6c9
RZ
979 atomic_fetch_add_explicit(&fnc->counters.user_disables, 1,
980 memory_order_relaxed);
3bdd7fca
RZ
981
982 /* Call reconnect to disable timers and clean up context. */
983 fpm_reconnect(fnc);
984 break;
985
986 case FNE_RECONNECT:
e5e444d8 987 zlog_info("%s: manual FPM reconnect event", __func__);
3bdd7fca 988 fnc->disabled = false;
c871e6c9
RZ
989 atomic_fetch_add_explicit(&fnc->counters.user_configures, 1,
990 memory_order_relaxed);
3bdd7fca
RZ
991 fpm_reconnect(fnc);
992 break;
993
6cc059cd 994 case FNE_RESET_COUNTERS:
e5e444d8 995 zlog_info("%s: manual FPM counters reset event", __func__);
6cc059cd
RZ
996 memset(&fnc->counters, 0, sizeof(fnc->counters));
997 break;
998
3bdd7fca 999 default:
e5e444d8
RZ
1000 if (IS_ZEBRA_DEBUG_FPM)
1001 zlog_debug("%s: unhandled event %d", __func__, event);
3bdd7fca
RZ
1002 break;
1003 }
1004
1005 return 0;
1006}
1007
d35f447d
RZ
1008/*
1009 * Data plane functions.
1010 */
1011static int fpm_nl_start(struct zebra_dplane_provider *prov)
1012{
1013 struct fpm_nl_ctx *fnc;
1014
1015 fnc = dplane_provider_get_data(prov);
1016 fnc->fthread = frr_pthread_new(NULL, prov_name, prov_name);
1017 assert(frr_pthread_run(fnc->fthread, NULL) == 0);
1018 fnc->ibuf = stream_new(NL_PKT_BUF_SIZE);
1019 fnc->obuf = stream_new(NL_PKT_BUF_SIZE * 128);
1020 pthread_mutex_init(&fnc->obuf_mutex, NULL);
1021 fnc->socket = -1;
3bdd7fca 1022 fnc->disabled = true;
ba803a2f
RZ
1023 fnc->prov = prov;
1024 TAILQ_INIT(&fnc->ctxqueue);
1025 pthread_mutex_init(&fnc->ctxqueue_mutex, NULL);
d35f447d
RZ
1026
1027 return 0;
1028}
1029
98a87504 1030static int fpm_nl_finish_early(struct fpm_nl_ctx *fnc)
d35f447d 1031{
98a87504
RZ
1032 /* Disable all events and close socket. */
1033 THREAD_OFF(fnc->t_ribreset);
1034 THREAD_OFF(fnc->t_ribwalk);
1035 THREAD_OFF(fnc->t_rmacreset);
1036 THREAD_OFF(fnc->t_rmacwalk);
1037 thread_cancel_async(fnc->fthread->master, &fnc->t_read, NULL);
1038 thread_cancel_async(fnc->fthread->master, &fnc->t_write, NULL);
1039 thread_cancel_async(fnc->fthread->master, &fnc->t_connect, NULL);
d35f447d 1040
98a87504
RZ
1041 if (fnc->socket != -1) {
1042 close(fnc->socket);
1043 fnc->socket = -1;
1044 }
1045
1046 return 0;
1047}
1048
1049static int fpm_nl_finish_late(struct fpm_nl_ctx *fnc)
1050{
1051 /* Stop the running thread. */
1052 frr_pthread_stop(fnc->fthread, NULL);
1053
1054 /* Free all allocated resources. */
1055 pthread_mutex_destroy(&fnc->obuf_mutex);
1056 pthread_mutex_destroy(&fnc->ctxqueue_mutex);
d35f447d
RZ
1057 stream_free(fnc->ibuf);
1058 stream_free(fnc->obuf);
98a87504
RZ
1059 free(gfnc);
1060 gfnc = NULL;
d35f447d
RZ
1061
1062 return 0;
1063}
1064
98a87504
RZ
1065static int fpm_nl_finish(struct zebra_dplane_provider *prov, bool early)
1066{
1067 struct fpm_nl_ctx *fnc;
1068
1069 fnc = dplane_provider_get_data(prov);
1070 if (early)
1071 return fpm_nl_finish_early(fnc);
1072
1073 return fpm_nl_finish_late(fnc);
1074}
1075
d35f447d
RZ
1076static int fpm_nl_process(struct zebra_dplane_provider *prov)
1077{
1078 struct zebra_dplane_ctx *ctx;
1079 struct fpm_nl_ctx *fnc;
1080 int counter, limit;
edfeff42 1081 uint64_t cur_queue, peak_queue;
d35f447d
RZ
1082
1083 fnc = dplane_provider_get_data(prov);
1084 limit = dplane_provider_get_work_limit(prov);
1085 for (counter = 0; counter < limit; counter++) {
1086 ctx = dplane_provider_dequeue_in_ctx(prov);
1087 if (ctx == NULL)
1088 break;
1089
1090 /*
1091 * Skip all notifications if not connected, we'll walk the RIB
1092 * anyway.
1093 */
6cc059cd 1094 if (fnc->socket != -1 && fnc->connecting == false) {
ba803a2f
RZ
1095 frr_mutex_lock_autounlock(&fnc->ctxqueue_mutex);
1096 dplane_ctx_enqueue_tail(&fnc->ctxqueue, ctx);
1097
1098 /* Account the number of contexts. */
c871e6c9
RZ
1099 atomic_fetch_add_explicit(&fnc->counters.ctxqueue_len,
1100 1, memory_order_relaxed);
1101 cur_queue = atomic_load_explicit(
1102 &fnc->counters.ctxqueue_len,
1103 memory_order_relaxed);
1104 peak_queue = atomic_load_explicit(
1105 &fnc->counters.ctxqueue_len_peak,
1106 memory_order_relaxed);
edfeff42 1107 if (peak_queue < cur_queue)
c871e6c9
RZ
1108 atomic_store_explicit(
1109 &fnc->counters.ctxqueue_len_peak,
1110 peak_queue, memory_order_relaxed);
ba803a2f 1111 continue;
6cc059cd
RZ
1112 }
1113
d35f447d
RZ
1114 dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS);
1115 dplane_provider_enqueue_out_ctx(prov, ctx);
1116 }
1117
c871e6c9
RZ
1118 if (atomic_load_explicit(&fnc->counters.ctxqueue_len,
1119 memory_order_relaxed)
1120 > 0)
ba803a2f
RZ
1121 thread_add_timer(fnc->fthread->master, fpm_process_queue,
1122 fnc, 0, &fnc->t_dequeue);
1123
d35f447d
RZ
1124 return 0;
1125}
1126
1127static int fpm_nl_new(struct thread_master *tm)
1128{
1129 struct zebra_dplane_provider *prov = NULL;
d35f447d
RZ
1130 int rv;
1131
3bdd7fca 1132 gfnc = calloc(1, sizeof(*gfnc));
d35f447d
RZ
1133 rv = dplane_provider_register(prov_name, DPLANE_PRIO_POSTPROCESS,
1134 DPLANE_PROV_FLAG_THREADED, fpm_nl_start,
3bdd7fca 1135 fpm_nl_process, fpm_nl_finish, gfnc,
d35f447d
RZ
1136 &prov);
1137
1138 if (IS_ZEBRA_DEBUG_DPLANE)
1139 zlog_debug("%s register status: %d", prov_name, rv);
1140
612c2c15 1141 install_node(&fpm_node);
6cc059cd
RZ
1142 install_element(ENABLE_NODE, &fpm_show_counters_cmd);
1143 install_element(ENABLE_NODE, &fpm_show_counters_json_cmd);
1144 install_element(ENABLE_NODE, &fpm_reset_counters_cmd);
3bdd7fca
RZ
1145 install_element(CONFIG_NODE, &fpm_set_address_cmd);
1146 install_element(CONFIG_NODE, &no_fpm_set_address_cmd);
1147
d35f447d
RZ
1148 return 0;
1149}
1150
1151static int fpm_nl_init(void)
1152{
1153 hook_register(frr_late_init, fpm_nl_new);
1154 return 0;
1155}
1156
1157FRR_MODULE_SETUP(
1158 .name = "dplane_fpm_nl",
1159 .version = "0.0.1",
1160 .description = "Data plane plugin for FPM using netlink.",
1161 .init = fpm_nl_init,
1162 )