]>
Commit | Line | Data |
---|---|---|
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" |
3bdd7fca | 34 | #include "lib/command.h" |
d35f447d RZ |
35 | #include "lib/memory.h" |
36 | #include "lib/network.h" | |
37 | #include "lib/ns.h" | |
38 | #include "lib/frr_pthread.h" | |
bda10adf | 39 | #include "zebra/interface.h" |
d35f447d | 40 | #include "zebra/zebra_dplane.h" |
018e77bc | 41 | #include "zebra/zebra_router.h" |
bda10adf | 42 | #include "zebra/zebra_vxlan_private.h" |
d35f447d RZ |
43 | #include "zebra/kernel_netlink.h" |
44 | #include "zebra/rt_netlink.h" | |
45 | #include "zebra/debug.h" | |
46 | ||
47 | #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK | |
48 | #define SOUTHBOUND_DEFAULT_PORT 2620 | |
49 | ||
a179ba35 RZ |
50 | /** |
51 | * FPM header: | |
52 | * { | |
53 | * version: 1 byte (always 1), | |
54 | * type: 1 byte (1 for netlink, 2 protobuf), | |
55 | * len: 2 bytes (network order), | |
56 | * } | |
57 | * | |
58 | * This header is used with any format to tell the users how many bytes to | |
59 | * expect. | |
60 | */ | |
61 | #define FPM_HEADER_SIZE 4 | |
62 | ||
d35f447d RZ |
63 | static const char *prov_name = "dplane_fpm_nl"; |
64 | ||
65 | struct fpm_nl_ctx { | |
66 | /* data plane connection. */ | |
67 | int socket; | |
3bdd7fca | 68 | bool disabled; |
d35f447d | 69 | bool connecting; |
018e77bc | 70 | bool rib_complete; |
bda10adf | 71 | bool rmac_complete; |
d35f447d RZ |
72 | struct sockaddr_storage addr; |
73 | ||
74 | /* data plane buffers. */ | |
75 | struct stream *ibuf; | |
76 | struct stream *obuf; | |
77 | pthread_mutex_t obuf_mutex; | |
78 | ||
79 | /* data plane events. */ | |
80 | struct frr_pthread *fthread; | |
81 | struct thread *t_connect; | |
82 | struct thread *t_read; | |
83 | struct thread *t_write; | |
3bdd7fca | 84 | struct thread *t_event; |
018e77bc RZ |
85 | |
86 | /* zebra events. */ | |
87 | struct thread *t_ribreset; | |
88 | struct thread *t_ribwalk; | |
bda10adf RZ |
89 | struct thread *t_rmacreset; |
90 | struct thread *t_rmacwalk; | |
6cc059cd RZ |
91 | |
92 | /* Statistic counters. */ | |
93 | struct { | |
94 | /* Amount of bytes read into ibuf. */ | |
95 | uint64_t bytes_read; | |
96 | /* Amount of bytes written from obuf. */ | |
97 | uint64_t bytes_sent; | |
98 | ||
99 | /* Amount of connection closes. */ | |
100 | uint64_t connection_closes; | |
101 | /* Amount of connection errors. */ | |
102 | uint64_t connection_errors; | |
103 | ||
104 | /* Amount of user configurations: FNE_RECONNECT. */ | |
105 | uint64_t user_configures; | |
106 | /* Amount of user disable requests: FNE_DISABLE. */ | |
107 | uint64_t user_disables; | |
108 | ||
109 | /* Amount of data plane context processed. */ | |
110 | uint64_t dplane_contexts; | |
111 | ||
112 | /* Amount of buffer full events. */ | |
113 | uint64_t buffer_full; | |
114 | } counters; | |
3bdd7fca RZ |
115 | } * gfnc; |
116 | ||
117 | enum fpm_nl_events { | |
118 | /* Ask for FPM to reconnect the external server. */ | |
119 | FNE_RECONNECT, | |
120 | /* Disable FPM. */ | |
121 | FNE_DISABLE, | |
6cc059cd RZ |
122 | /* Reset counters. */ |
123 | FNE_RESET_COUNTERS, | |
d35f447d RZ |
124 | }; |
125 | ||
018e77bc RZ |
126 | /* |
127 | * Prototypes. | |
128 | */ | |
3bdd7fca | 129 | static int fpm_process_event(struct thread *t); |
018e77bc RZ |
130 | static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx); |
131 | static int fpm_rib_send(struct thread *t); | |
132 | static int fpm_rib_reset(struct thread *t); | |
bda10adf RZ |
133 | static int fpm_rmac_send(struct thread *t); |
134 | static int fpm_rmac_reset(struct thread *t); | |
018e77bc | 135 | |
3bdd7fca RZ |
136 | /* |
137 | * CLI. | |
138 | */ | |
6cc059cd RZ |
139 | #define FPM_STR "Forwarding Plane Manager configuration\n" |
140 | ||
3bdd7fca RZ |
141 | DEFUN(fpm_set_address, fpm_set_address_cmd, |
142 | "fpm address <A.B.C.D|X:X::X:X> [port (1-65535)]", | |
6cc059cd | 143 | FPM_STR |
3bdd7fca RZ |
144 | "FPM remote listening server address\n" |
145 | "Remote IPv4 FPM server\n" | |
146 | "Remote IPv6 FPM server\n" | |
147 | "FPM remote listening server port\n" | |
148 | "Remote FPM server port\n") | |
149 | { | |
150 | struct sockaddr_in *sin; | |
151 | struct sockaddr_in6 *sin6; | |
152 | uint16_t port = 0; | |
153 | uint8_t naddr[INET6_BUFSIZ]; | |
154 | ||
155 | if (argc == 5) | |
156 | port = strtol(argv[4]->arg, NULL, 10); | |
157 | ||
158 | /* Handle IPv4 addresses. */ | |
159 | if (inet_pton(AF_INET, argv[2]->arg, naddr) == 1) { | |
160 | sin = (struct sockaddr_in *)&gfnc->addr; | |
161 | ||
162 | memset(sin, 0, sizeof(*sin)); | |
163 | sin->sin_family = AF_INET; | |
164 | sin->sin_port = | |
165 | port ? htons(port) : htons(SOUTHBOUND_DEFAULT_PORT); | |
166 | #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN | |
167 | sin->sin_len = sizeof(*sin); | |
168 | #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ | |
169 | memcpy(&sin->sin_addr, naddr, sizeof(sin->sin_addr)); | |
170 | ||
171 | goto ask_reconnect; | |
172 | } | |
173 | ||
174 | /* Handle IPv6 addresses. */ | |
175 | if (inet_pton(AF_INET6, argv[2]->arg, naddr) != 1) { | |
176 | vty_out(vty, "%% Invalid address: %s\n", argv[2]->arg); | |
177 | return CMD_WARNING; | |
178 | } | |
179 | ||
180 | sin6 = (struct sockaddr_in6 *)&gfnc->addr; | |
181 | memset(sin6, 0, sizeof(*sin6)); | |
182 | sin6->sin6_family = AF_INET6; | |
183 | sin6->sin6_port = port ? htons(port) : htons(SOUTHBOUND_DEFAULT_PORT); | |
184 | #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN | |
185 | sin6->sin6_len = sizeof(*sin6); | |
186 | #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ | |
187 | memcpy(&sin6->sin6_addr, naddr, sizeof(sin6->sin6_addr)); | |
188 | ||
189 | ask_reconnect: | |
190 | thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc, | |
191 | FNE_RECONNECT, &gfnc->t_event); | |
192 | return CMD_SUCCESS; | |
193 | } | |
194 | ||
195 | DEFUN(no_fpm_set_address, no_fpm_set_address_cmd, | |
196 | "no fpm address [<A.B.C.D|X:X::X:X> [port <1-65535>]]", | |
197 | NO_STR | |
6cc059cd | 198 | FPM_STR |
3bdd7fca RZ |
199 | "FPM remote listening server address\n" |
200 | "Remote IPv4 FPM server\n" | |
201 | "Remote IPv6 FPM server\n" | |
202 | "FPM remote listening server port\n" | |
203 | "Remote FPM server port\n") | |
204 | { | |
205 | thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc, | |
206 | FNE_DISABLE, &gfnc->t_event); | |
207 | return CMD_SUCCESS; | |
208 | } | |
209 | ||
6cc059cd RZ |
210 | DEFUN(fpm_reset_counters, fpm_reset_counters_cmd, |
211 | "clear fpm counters", | |
212 | CLEAR_STR | |
213 | FPM_STR | |
214 | "FPM statistic counters\n") | |
215 | { | |
216 | thread_add_event(gfnc->fthread->master, fpm_process_event, gfnc, | |
217 | FNE_RESET_COUNTERS, &gfnc->t_event); | |
218 | return CMD_SUCCESS; | |
219 | } | |
220 | ||
221 | DEFUN(fpm_show_counters, fpm_show_counters_cmd, | |
222 | "show fpm counters", | |
223 | SHOW_STR | |
224 | FPM_STR | |
225 | "FPM statistic counters\n") | |
226 | { | |
227 | vty_out(vty, "%30s\n%30s\n", "FPM counters", "============"); | |
228 | ||
229 | #define SHOW_COUNTER(label, counter) \ | |
230 | vty_out(vty, "%28s: %Lu\n", (label), (counter)); | |
231 | ||
232 | SHOW_COUNTER("Input bytes", gfnc->counters.bytes_read); | |
233 | SHOW_COUNTER("Output bytes", gfnc->counters.bytes_sent); | |
234 | SHOW_COUNTER("Connection closes", gfnc->counters.connection_closes); | |
235 | SHOW_COUNTER("Connection errors", gfnc->counters.connection_errors); | |
236 | SHOW_COUNTER("Data plane items processed", | |
237 | gfnc->counters.dplane_contexts); | |
238 | SHOW_COUNTER("Buffer full hits", gfnc->counters.buffer_full); | |
239 | SHOW_COUNTER("User FPM configurations", gfnc->counters.user_configures); | |
240 | SHOW_COUNTER("User FPM disable requests", gfnc->counters.user_disables); | |
241 | ||
242 | #undef SHOW_COUNTER | |
243 | ||
244 | return CMD_SUCCESS; | |
245 | } | |
246 | ||
247 | DEFUN(fpm_show_counters_json, fpm_show_counters_json_cmd, | |
248 | "show fpm counters json", | |
249 | SHOW_STR | |
250 | FPM_STR | |
251 | "FPM statistic counters\n" | |
252 | JSON_STR) | |
253 | { | |
254 | struct json_object *jo; | |
255 | ||
256 | jo = json_object_new_object(); | |
257 | json_object_int_add(jo, "bytes-read", gfnc->counters.bytes_read); | |
258 | json_object_int_add(jo, "bytes-sent", gfnc->counters.bytes_sent); | |
259 | json_object_int_add(jo, "connection-closes", gfnc->counters.connection_closes); | |
260 | json_object_int_add(jo, "connection-errors", gfnc->counters.connection_errors); | |
261 | json_object_int_add(jo, "data-plane-contexts", gfnc->counters.dplane_contexts); | |
262 | json_object_int_add(jo, "buffer-full-hits", gfnc->counters.buffer_full); | |
263 | json_object_int_add(jo, "user-configures", gfnc->counters.user_configures); | |
264 | json_object_int_add(jo, "user-disables", gfnc->counters.user_disables); | |
265 | vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); | |
266 | json_object_free(jo); | |
267 | ||
268 | return CMD_SUCCESS; | |
269 | } | |
270 | ||
3bdd7fca RZ |
271 | static int fpm_write_config(struct vty *vty) |
272 | { | |
273 | struct sockaddr_in *sin; | |
274 | struct sockaddr_in6 *sin6; | |
275 | int written = 0; | |
276 | char addrstr[INET6_ADDRSTRLEN]; | |
277 | ||
278 | if (gfnc->disabled) | |
279 | return written; | |
280 | ||
281 | switch (gfnc->addr.ss_family) { | |
282 | case AF_INET: | |
283 | written = 1; | |
284 | sin = (struct sockaddr_in *)&gfnc->addr; | |
285 | inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr)); | |
286 | vty_out(vty, "fpm address %s", addrstr); | |
287 | if (sin->sin_port != htons(SOUTHBOUND_DEFAULT_PORT)) | |
288 | vty_out(vty, " port %d", ntohs(sin->sin_port)); | |
289 | ||
290 | vty_out(vty, "\n"); | |
291 | break; | |
292 | case AF_INET6: | |
293 | written = 1; | |
294 | sin6 = (struct sockaddr_in6 *)&gfnc->addr; | |
295 | inet_ntop(AF_INET, &sin6->sin6_addr, addrstr, sizeof(addrstr)); | |
296 | vty_out(vty, "fpm address %s", addrstr); | |
297 | if (sin6->sin6_port != htons(SOUTHBOUND_DEFAULT_PORT)) | |
298 | vty_out(vty, " port %d", ntohs(sin6->sin6_port)); | |
299 | ||
300 | vty_out(vty, "\n"); | |
301 | break; | |
302 | ||
303 | default: | |
304 | break; | |
305 | } | |
306 | ||
307 | return written; | |
308 | } | |
309 | ||
310 | struct cmd_node fpm_node = { | |
311 | .node = VTY_NODE, | |
312 | .prompt = "", | |
313 | .vtysh = 1, | |
314 | }; | |
315 | ||
d35f447d RZ |
316 | /* |
317 | * FPM functions. | |
318 | */ | |
319 | static int fpm_connect(struct thread *t); | |
320 | ||
321 | static void fpm_reconnect(struct fpm_nl_ctx *fnc) | |
322 | { | |
323 | /* Grab the lock to empty the stream and stop the zebra thread. */ | |
324 | frr_mutex_lock_autounlock(&fnc->obuf_mutex); | |
325 | ||
3bdd7fca RZ |
326 | /* Avoid calling close on `-1`. */ |
327 | if (fnc->socket != -1) { | |
328 | close(fnc->socket); | |
329 | fnc->socket = -1; | |
330 | } | |
331 | ||
d35f447d RZ |
332 | stream_reset(fnc->ibuf); |
333 | stream_reset(fnc->obuf); | |
334 | THREAD_OFF(fnc->t_read); | |
335 | THREAD_OFF(fnc->t_write); | |
018e77bc RZ |
336 | |
337 | if (fnc->t_ribreset) | |
338 | thread_cancel_async(zrouter.master, &fnc->t_ribreset, NULL); | |
339 | if (fnc->t_ribwalk) | |
340 | thread_cancel_async(zrouter.master, &fnc->t_ribwalk, NULL); | |
bda10adf RZ |
341 | if (fnc->t_rmacreset) |
342 | thread_cancel_async(zrouter.master, &fnc->t_rmacreset, NULL); | |
343 | if (fnc->t_rmacwalk) | |
344 | thread_cancel_async(zrouter.master, &fnc->t_rmacwalk, NULL); | |
018e77bc | 345 | |
3bdd7fca RZ |
346 | /* FPM is disabled, don't attempt to connect. */ |
347 | if (fnc->disabled) | |
348 | return; | |
349 | ||
d35f447d RZ |
350 | thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3, |
351 | &fnc->t_connect); | |
352 | } | |
353 | ||
354 | static int fpm_read(struct thread *t) | |
355 | { | |
356 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
357 | ssize_t rv; | |
358 | ||
359 | /* Let's ignore the input at the moment. */ | |
360 | rv = stream_read_try(fnc->ibuf, fnc->socket, | |
361 | STREAM_WRITEABLE(fnc->ibuf)); | |
362 | if (rv == 0) { | |
6cc059cd | 363 | fnc->counters.connection_closes++; |
d35f447d RZ |
364 | zlog_debug("%s: connection closed", __func__); |
365 | fpm_reconnect(fnc); | |
366 | return 0; | |
367 | } | |
368 | if (rv == -1) { | |
369 | if (errno == EAGAIN || errno == EWOULDBLOCK | |
370 | || errno == EINTR) | |
371 | return 0; | |
372 | ||
6cc059cd | 373 | fnc->counters.connection_errors++; |
d35f447d RZ |
374 | zlog_debug("%s: connection failure: %s", __func__, |
375 | strerror(errno)); | |
376 | fpm_reconnect(fnc); | |
377 | return 0; | |
378 | } | |
379 | stream_reset(fnc->ibuf); | |
380 | ||
6cc059cd RZ |
381 | /* Account all bytes read. */ |
382 | fnc->counters.bytes_read += rv; | |
383 | ||
d35f447d RZ |
384 | thread_add_read(fnc->fthread->master, fpm_read, fnc, fnc->socket, |
385 | &fnc->t_read); | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
390 | static int fpm_write(struct thread *t) | |
391 | { | |
392 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
393 | socklen_t statuslen; | |
394 | ssize_t bwritten; | |
395 | int rv, status; | |
396 | size_t btotal; | |
397 | ||
398 | if (fnc->connecting == true) { | |
399 | status = 0; | |
400 | statuslen = sizeof(status); | |
401 | ||
402 | rv = getsockopt(fnc->socket, SOL_SOCKET, SO_ERROR, &status, | |
403 | &statuslen); | |
404 | if (rv == -1 || status != 0) { | |
405 | if (rv != -1) | |
406 | zlog_debug("%s: connection failed: %s", | |
407 | __func__, strerror(status)); | |
408 | else | |
409 | zlog_debug("%s: SO_ERROR failed: %s", __func__, | |
410 | strerror(status)); | |
411 | ||
6cc059cd RZ |
412 | fnc->counters.connection_errors++; |
413 | ||
d35f447d RZ |
414 | fpm_reconnect(fnc); |
415 | return 0; | |
416 | } | |
417 | ||
418 | fnc->connecting = false; | |
018e77bc RZ |
419 | |
420 | /* Ask zebra main thread to start walking the RIB table. */ | |
421 | thread_add_timer(zrouter.master, fpm_rib_send, fnc, 0, | |
422 | &fnc->t_ribwalk); | |
bda10adf RZ |
423 | thread_add_timer(zrouter.master, fpm_rmac_send, fnc, 0, |
424 | &fnc->t_rmacwalk); | |
d35f447d RZ |
425 | } |
426 | ||
427 | frr_mutex_lock_autounlock(&fnc->obuf_mutex); | |
428 | ||
429 | while (true) { | |
430 | /* Stream is empty: reset pointers and return. */ | |
431 | if (STREAM_READABLE(fnc->obuf) == 0) { | |
432 | stream_reset(fnc->obuf); | |
433 | break; | |
434 | } | |
435 | ||
436 | /* Try to write all at once. */ | |
437 | btotal = stream_get_endp(fnc->obuf) - | |
438 | stream_get_getp(fnc->obuf); | |
439 | bwritten = write(fnc->socket, stream_pnt(fnc->obuf), btotal); | |
440 | if (bwritten == 0) { | |
6cc059cd | 441 | fnc->counters.connection_closes++; |
d35f447d RZ |
442 | zlog_debug("%s: connection closed", __func__); |
443 | break; | |
444 | } | |
445 | if (bwritten == -1) { | |
446 | if (errno == EAGAIN || errno == EWOULDBLOCK | |
447 | || errno == EINTR) | |
448 | break; | |
449 | ||
6cc059cd | 450 | fnc->counters.connection_errors++; |
d35f447d RZ |
451 | zlog_debug("%s: connection failure: %s", __func__, |
452 | strerror(errno)); | |
453 | fpm_reconnect(fnc); | |
454 | break; | |
455 | } | |
456 | ||
6cc059cd RZ |
457 | /* Account all bytes sent. */ |
458 | fnc->counters.bytes_sent += bwritten; | |
459 | ||
d35f447d RZ |
460 | stream_forward_getp(fnc->obuf, (size_t)bwritten); |
461 | } | |
462 | ||
463 | /* Stream is not empty yet, we must schedule more writes. */ | |
464 | if (STREAM_READABLE(fnc->obuf)) { | |
465 | thread_add_write(fnc->fthread->master, fpm_write, fnc, | |
466 | fnc->socket, &fnc->t_write); | |
467 | return 0; | |
468 | } | |
469 | ||
470 | return 0; | |
471 | } | |
472 | ||
473 | static int fpm_connect(struct thread *t) | |
474 | { | |
475 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
3bdd7fca RZ |
476 | struct sockaddr_in *sin = (struct sockaddr_in *)&fnc->addr; |
477 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&fnc->addr; | |
478 | socklen_t slen; | |
d35f447d RZ |
479 | int rv, sock; |
480 | char addrstr[INET6_ADDRSTRLEN]; | |
481 | ||
3bdd7fca | 482 | sock = socket(fnc->addr.ss_family, SOCK_STREAM, 0); |
d35f447d | 483 | if (sock == -1) { |
6cc059cd | 484 | zlog_err("%s: fpm socket failed: %s", __func__, |
d35f447d RZ |
485 | strerror(errno)); |
486 | thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3, | |
487 | &fnc->t_connect); | |
488 | return 0; | |
489 | } | |
490 | ||
491 | set_nonblocking(sock); | |
492 | ||
3bdd7fca RZ |
493 | if (fnc->addr.ss_family == AF_INET) { |
494 | inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr)); | |
495 | slen = sizeof(*sin); | |
496 | } else { | |
497 | inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, sizeof(addrstr)); | |
498 | slen = sizeof(*sin6); | |
499 | } | |
d35f447d | 500 | |
d35f447d RZ |
501 | zlog_debug("%s: attempting to connect to %s:%d", __func__, addrstr, |
502 | ntohs(sin->sin_port)); | |
503 | ||
3bdd7fca | 504 | rv = connect(sock, (struct sockaddr *)&fnc->addr, slen); |
d35f447d | 505 | if (rv == -1 && errno != EINPROGRESS) { |
6cc059cd | 506 | fnc->counters.connection_errors++; |
d35f447d RZ |
507 | close(sock); |
508 | zlog_warn("%s: fpm connection failed: %s", __func__, | |
509 | strerror(errno)); | |
510 | thread_add_timer(fnc->fthread->master, fpm_connect, fnc, 3, | |
511 | &fnc->t_connect); | |
512 | return 0; | |
513 | } | |
514 | ||
515 | fnc->connecting = (errno == EINPROGRESS); | |
516 | fnc->socket = sock; | |
517 | thread_add_read(fnc->fthread->master, fpm_read, fnc, sock, | |
518 | &fnc->t_read); | |
519 | thread_add_write(fnc->fthread->master, fpm_write, fnc, sock, | |
520 | &fnc->t_write); | |
521 | ||
018e77bc RZ |
522 | /* Mark all routes as unsent. */ |
523 | thread_add_timer(zrouter.master, fpm_rib_reset, fnc, 0, | |
524 | &fnc->t_ribreset); | |
bda10adf RZ |
525 | thread_add_timer(zrouter.master, fpm_rmac_reset, fnc, 0, |
526 | &fnc->t_rmacreset); | |
018e77bc | 527 | |
d35f447d RZ |
528 | return 0; |
529 | } | |
530 | ||
531 | /** | |
532 | * Encode data plane operation context into netlink and enqueue it in the FPM | |
533 | * output buffer. | |
534 | * | |
535 | * @param fnc the netlink FPM context. | |
536 | * @param ctx the data plane operation context data. | |
537 | * @return 0 on success or -1 on not enough space. | |
538 | */ | |
539 | static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx) | |
540 | { | |
541 | uint8_t nl_buf[NL_PKT_BUF_SIZE]; | |
542 | size_t nl_buf_len; | |
543 | ssize_t rv; | |
544 | ||
545 | nl_buf_len = 0; | |
546 | ||
547 | frr_mutex_lock_autounlock(&fnc->obuf_mutex); | |
548 | ||
549 | switch (dplane_ctx_get_op(ctx)) { | |
550 | case DPLANE_OP_ROUTE_UPDATE: | |
551 | case DPLANE_OP_ROUTE_DELETE: | |
552 | rv = netlink_route_multipath(RTM_DELROUTE, ctx, nl_buf, | |
553 | sizeof(nl_buf)); | |
554 | if (rv <= 0) { | |
555 | zlog_debug("%s: netlink_route_multipath failed", | |
556 | __func__); | |
557 | return 0; | |
558 | } | |
559 | ||
560 | nl_buf_len = (size_t)rv; | |
d35f447d RZ |
561 | |
562 | /* UPDATE operations need a INSTALL, otherwise just quit. */ | |
563 | if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_DELETE) | |
564 | break; | |
565 | ||
566 | /* FALL THROUGH */ | |
567 | case DPLANE_OP_ROUTE_INSTALL: | |
568 | rv = netlink_route_multipath(RTM_NEWROUTE, ctx, | |
569 | &nl_buf[nl_buf_len], | |
570 | sizeof(nl_buf) - nl_buf_len); | |
571 | if (rv <= 0) { | |
572 | zlog_debug("%s: netlink_route_multipath failed", | |
573 | __func__); | |
574 | return 0; | |
575 | } | |
576 | ||
577 | nl_buf_len += (size_t)rv; | |
d35f447d RZ |
578 | break; |
579 | ||
bda10adf RZ |
580 | case DPLANE_OP_MAC_INSTALL: |
581 | case DPLANE_OP_MAC_DELETE: | |
582 | rv = netlink_macfdb_update_ctx(ctx, nl_buf, sizeof(nl_buf)); | |
583 | if (rv <= 0) { | |
584 | zlog_debug("%s: netlink_macfdb_update_ctx failed", | |
585 | __func__); | |
586 | return 0; | |
587 | } | |
588 | ||
589 | nl_buf_len = (size_t)rv; | |
bda10adf RZ |
590 | break; |
591 | ||
d35f447d RZ |
592 | case DPLANE_OP_NH_INSTALL: |
593 | case DPLANE_OP_NH_UPDATE: | |
594 | case DPLANE_OP_NH_DELETE: | |
595 | case DPLANE_OP_LSP_INSTALL: | |
596 | case DPLANE_OP_LSP_UPDATE: | |
597 | case DPLANE_OP_LSP_DELETE: | |
598 | case DPLANE_OP_PW_INSTALL: | |
599 | case DPLANE_OP_PW_UNINSTALL: | |
600 | case DPLANE_OP_ADDR_INSTALL: | |
601 | case DPLANE_OP_ADDR_UNINSTALL: | |
d35f447d RZ |
602 | case DPLANE_OP_NEIGH_INSTALL: |
603 | case DPLANE_OP_NEIGH_UPDATE: | |
604 | case DPLANE_OP_NEIGH_DELETE: | |
605 | case DPLANE_OP_VTEP_ADD: | |
606 | case DPLANE_OP_VTEP_DELETE: | |
607 | case DPLANE_OP_SYS_ROUTE_ADD: | |
608 | case DPLANE_OP_SYS_ROUTE_DELETE: | |
609 | case DPLANE_OP_ROUTE_NOTIFY: | |
610 | case DPLANE_OP_LSP_NOTIFY: | |
611 | case DPLANE_OP_NONE: | |
612 | break; | |
613 | ||
614 | default: | |
615 | zlog_debug("%s: unhandled data plane message (%d) %s", | |
616 | __func__, dplane_ctx_get_op(ctx), | |
617 | dplane_op2str(dplane_ctx_get_op(ctx))); | |
618 | break; | |
619 | } | |
620 | ||
621 | /* Skip empty enqueues. */ | |
622 | if (nl_buf_len == 0) | |
623 | return 0; | |
624 | ||
a179ba35 RZ |
625 | /* We must know if someday a message goes beyond 65KiB. */ |
626 | assert((nl_buf_len + FPM_HEADER_SIZE) <= UINT16_MAX); | |
627 | ||
628 | /* Check if we have enough buffer space. */ | |
629 | if (STREAM_WRITEABLE(fnc->obuf) < (nl_buf_len + FPM_HEADER_SIZE)) { | |
630 | fnc->counters.buffer_full++; | |
631 | zlog_debug("%s: buffer full: wants to write %lu but has %ld", | |
632 | __func__, nl_buf_len + FPM_HEADER_SIZE, | |
633 | STREAM_WRITEABLE(fnc->obuf)); | |
634 | return -1; | |
635 | } | |
636 | ||
d35f447d | 637 | /* |
a179ba35 RZ |
638 | * Fill in the FPM header information. |
639 | * | |
640 | * See FPM_HEADER_SIZE definition for more information. | |
d35f447d RZ |
641 | */ |
642 | stream_putc(fnc->obuf, 1); | |
643 | stream_putc(fnc->obuf, 1); | |
a179ba35 | 644 | stream_putw(fnc->obuf, nl_buf_len + FPM_HEADER_SIZE); |
d35f447d RZ |
645 | |
646 | /* Write current data. */ | |
647 | stream_write(fnc->obuf, nl_buf, (size_t)nl_buf_len); | |
648 | ||
649 | /* Tell the thread to start writing. */ | |
650 | thread_add_write(fnc->fthread->master, fpm_write, fnc, fnc->socket, | |
651 | &fnc->t_write); | |
652 | ||
653 | return 0; | |
654 | } | |
655 | ||
018e77bc RZ |
656 | /** |
657 | * Send all RIB installed routes to the connected data plane. | |
658 | */ | |
659 | static int fpm_rib_send(struct thread *t) | |
660 | { | |
661 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
662 | rib_dest_t *dest; | |
663 | struct route_node *rn; | |
664 | struct route_table *rt; | |
665 | struct zebra_dplane_ctx *ctx; | |
666 | rib_tables_iter_t rt_iter; | |
667 | ||
668 | /* Allocate temporary context for all transactions. */ | |
669 | ctx = dplane_ctx_alloc(); | |
670 | ||
671 | rt_iter.state = RIB_TABLES_ITER_S_INIT; | |
672 | while ((rt = rib_tables_iter_next(&rt_iter))) { | |
673 | for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) { | |
674 | dest = rib_dest_from_rnode(rn); | |
675 | /* Skip bad route entries. */ | |
676 | if (dest == NULL || dest->selected_fib == NULL) { | |
677 | continue; | |
678 | } | |
679 | ||
680 | /* Check for already sent routes. */ | |
681 | if (CHECK_FLAG(dest->flags, RIB_DEST_UPDATE_FPM)) { | |
682 | continue; | |
683 | } | |
684 | ||
685 | /* Enqueue route install. */ | |
686 | dplane_ctx_reset(ctx); | |
687 | dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_INSTALL, rn, | |
688 | dest->selected_fib); | |
689 | if (fpm_nl_enqueue(fnc, ctx) == -1) { | |
690 | /* Free the temporary allocated context. */ | |
691 | dplane_ctx_fini(&ctx); | |
692 | ||
018e77bc RZ |
693 | thread_add_timer(zrouter.master, fpm_rib_send, |
694 | fnc, 1, &fnc->t_ribwalk); | |
695 | return 0; | |
696 | } | |
697 | ||
698 | /* Mark as sent. */ | |
699 | SET_FLAG(dest->flags, RIB_DEST_UPDATE_FPM); | |
700 | } | |
701 | } | |
702 | ||
703 | /* Free the temporary allocated context. */ | |
704 | dplane_ctx_fini(&ctx); | |
705 | ||
706 | /* All RIB routes sent! */ | |
707 | fnc->rib_complete = true; | |
708 | ||
709 | return 0; | |
710 | } | |
711 | ||
bda10adf RZ |
712 | /* |
713 | * The next three functions will handle RMAC enqueue. | |
714 | */ | |
715 | struct fpm_rmac_arg { | |
716 | struct zebra_dplane_ctx *ctx; | |
717 | struct fpm_nl_ctx *fnc; | |
718 | zebra_l3vni_t *zl3vni; | |
719 | }; | |
720 | ||
721 | static void fpm_enqueue_rmac_table(struct hash_backet *backet, void *arg) | |
722 | { | |
723 | struct fpm_rmac_arg *fra = arg; | |
724 | zebra_mac_t *zrmac = backet->data; | |
725 | struct zebra_if *zif = fra->zl3vni->vxlan_if->info; | |
726 | const struct zebra_l2info_vxlan *vxl = &zif->l2info.vxl; | |
727 | struct zebra_if *br_zif; | |
728 | vlanid_t vid; | |
729 | bool sticky; | |
730 | ||
731 | /* Entry already sent. */ | |
732 | if (CHECK_FLAG(zrmac->flags, ZEBRA_MAC_FPM_SENT)) | |
733 | return; | |
734 | ||
735 | sticky = !!CHECK_FLAG(zrmac->flags, | |
736 | (ZEBRA_MAC_STICKY | ZEBRA_MAC_REMOTE_DEF_GW)); | |
737 | br_zif = (struct zebra_if *)(zif->brslave_info.br_if->info); | |
738 | vid = IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif) ? vxl->access_vlan : 0; | |
739 | ||
740 | dplane_ctx_reset(fra->ctx); | |
741 | dplane_ctx_set_op(fra->ctx, DPLANE_OP_MAC_INSTALL); | |
742 | dplane_mac_init(fra->ctx, fra->zl3vni->vxlan_if, | |
743 | zif->brslave_info.br_if, vid, &zrmac->macaddr, | |
744 | zrmac->fwd_info.r_vtep_ip, sticky); | |
745 | if (fpm_nl_enqueue(fra->fnc, fra->ctx) == -1) { | |
bda10adf RZ |
746 | thread_add_timer(zrouter.master, fpm_rmac_send, |
747 | fra->fnc, 1, &fra->fnc->t_rmacwalk); | |
748 | } | |
749 | } | |
750 | ||
751 | static void fpm_enqueue_l3vni_table(struct hash_backet *backet, void *arg) | |
752 | { | |
753 | struct fpm_rmac_arg *fra = arg; | |
754 | zebra_l3vni_t *zl3vni = backet->data; | |
755 | ||
756 | fra->zl3vni = zl3vni; | |
757 | hash_iterate(zl3vni->rmac_table, fpm_enqueue_rmac_table, zl3vni); | |
758 | } | |
759 | ||
760 | static int fpm_rmac_send(struct thread *t) | |
761 | { | |
762 | struct fpm_rmac_arg fra; | |
763 | ||
764 | fra.fnc = THREAD_ARG(t); | |
765 | fra.ctx = dplane_ctx_alloc(); | |
766 | hash_iterate(zrouter.l3vni_table, fpm_enqueue_l3vni_table, &fra); | |
767 | dplane_ctx_fini(&fra.ctx); | |
768 | ||
769 | return 0; | |
770 | } | |
771 | ||
018e77bc RZ |
772 | /** |
773 | * Resets the RIB FPM flags so we send all routes again. | |
774 | */ | |
775 | static int fpm_rib_reset(struct thread *t) | |
776 | { | |
777 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
778 | rib_dest_t *dest; | |
779 | struct route_node *rn; | |
780 | struct route_table *rt; | |
781 | rib_tables_iter_t rt_iter; | |
782 | ||
783 | fnc->rib_complete = false; | |
784 | ||
785 | rt_iter.state = RIB_TABLES_ITER_S_INIT; | |
786 | while ((rt = rib_tables_iter_next(&rt_iter))) { | |
787 | for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) { | |
788 | dest = rib_dest_from_rnode(rn); | |
789 | /* Skip bad route entries. */ | |
790 | if (dest == NULL) | |
791 | continue; | |
792 | ||
793 | UNSET_FLAG(dest->flags, RIB_DEST_UPDATE_FPM); | |
794 | } | |
795 | } | |
796 | ||
797 | return 0; | |
798 | } | |
799 | ||
bda10adf RZ |
800 | /* |
801 | * The next three function will handle RMAC table reset. | |
802 | */ | |
803 | static void fpm_unset_rmac_table(struct hash_backet *backet, void *arg) | |
804 | { | |
805 | zebra_mac_t *zrmac = backet->data; | |
806 | ||
807 | UNSET_FLAG(zrmac->flags, ZEBRA_MAC_FPM_SENT); | |
808 | } | |
809 | ||
810 | static void fpm_unset_l3vni_table(struct hash_backet *backet, void *arg) | |
811 | { | |
812 | zebra_l3vni_t *zl3vni = backet->data; | |
813 | ||
814 | hash_iterate(zl3vni->rmac_table, fpm_unset_rmac_table, zl3vni); | |
815 | } | |
816 | ||
817 | static int fpm_rmac_reset(struct thread *t) | |
818 | { | |
819 | hash_iterate(zrouter.l3vni_table, fpm_unset_l3vni_table, NULL); | |
820 | ||
821 | return 0; | |
822 | } | |
823 | ||
3bdd7fca RZ |
824 | /** |
825 | * Handles external (e.g. CLI, data plane or others) events. | |
826 | */ | |
827 | static int fpm_process_event(struct thread *t) | |
828 | { | |
829 | struct fpm_nl_ctx *fnc = THREAD_ARG(t); | |
830 | int event = THREAD_VAL(t); | |
831 | ||
832 | switch (event) { | |
833 | case FNE_DISABLE: | |
834 | zlog_debug("%s: manual FPM disable event", __func__); | |
835 | fnc->disabled = true; | |
6cc059cd | 836 | fnc->counters.user_disables++; |
3bdd7fca RZ |
837 | |
838 | /* Call reconnect to disable timers and clean up context. */ | |
839 | fpm_reconnect(fnc); | |
840 | break; | |
841 | ||
842 | case FNE_RECONNECT: | |
843 | zlog_debug("%s: manual FPM reconnect event", __func__); | |
844 | fnc->disabled = false; | |
6cc059cd | 845 | fnc->counters.user_configures++; |
3bdd7fca RZ |
846 | fpm_reconnect(fnc); |
847 | break; | |
848 | ||
6cc059cd RZ |
849 | case FNE_RESET_COUNTERS: |
850 | zlog_debug("%s: manual FPM counters reset event", __func__); | |
851 | memset(&fnc->counters, 0, sizeof(fnc->counters)); | |
852 | break; | |
853 | ||
3bdd7fca RZ |
854 | default: |
855 | zlog_debug("%s: unhandled event %d", __func__, event); | |
856 | break; | |
857 | } | |
858 | ||
859 | return 0; | |
860 | } | |
861 | ||
d35f447d RZ |
862 | /* |
863 | * Data plane functions. | |
864 | */ | |
865 | static int fpm_nl_start(struct zebra_dplane_provider *prov) | |
866 | { | |
867 | struct fpm_nl_ctx *fnc; | |
868 | ||
869 | fnc = dplane_provider_get_data(prov); | |
870 | fnc->fthread = frr_pthread_new(NULL, prov_name, prov_name); | |
871 | assert(frr_pthread_run(fnc->fthread, NULL) == 0); | |
872 | fnc->ibuf = stream_new(NL_PKT_BUF_SIZE); | |
873 | fnc->obuf = stream_new(NL_PKT_BUF_SIZE * 128); | |
874 | pthread_mutex_init(&fnc->obuf_mutex, NULL); | |
875 | fnc->socket = -1; | |
3bdd7fca | 876 | fnc->disabled = true; |
d35f447d RZ |
877 | |
878 | return 0; | |
879 | } | |
880 | ||
881 | static int fpm_nl_finish(struct zebra_dplane_provider *prov, bool early) | |
882 | { | |
883 | struct fpm_nl_ctx *fnc; | |
884 | ||
885 | fnc = dplane_provider_get_data(prov); | |
886 | stream_free(fnc->ibuf); | |
887 | stream_free(fnc->obuf); | |
888 | close(fnc->socket); | |
889 | ||
890 | return 0; | |
891 | } | |
892 | ||
893 | static int fpm_nl_process(struct zebra_dplane_provider *prov) | |
894 | { | |
895 | struct zebra_dplane_ctx *ctx; | |
896 | struct fpm_nl_ctx *fnc; | |
897 | int counter, limit; | |
898 | ||
899 | fnc = dplane_provider_get_data(prov); | |
900 | limit = dplane_provider_get_work_limit(prov); | |
901 | for (counter = 0; counter < limit; counter++) { | |
902 | ctx = dplane_provider_dequeue_in_ctx(prov); | |
903 | if (ctx == NULL) | |
904 | break; | |
905 | ||
906 | /* | |
907 | * Skip all notifications if not connected, we'll walk the RIB | |
908 | * anyway. | |
909 | */ | |
6cc059cd | 910 | if (fnc->socket != -1 && fnc->connecting == false) { |
d35f447d RZ |
911 | fpm_nl_enqueue(fnc, ctx); |
912 | ||
6cc059cd RZ |
913 | fnc->counters.dplane_contexts++; |
914 | } | |
915 | ||
d35f447d RZ |
916 | dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS); |
917 | dplane_provider_enqueue_out_ctx(prov, ctx); | |
918 | } | |
919 | ||
920 | return 0; | |
921 | } | |
922 | ||
923 | static int fpm_nl_new(struct thread_master *tm) | |
924 | { | |
925 | struct zebra_dplane_provider *prov = NULL; | |
d35f447d RZ |
926 | int rv; |
927 | ||
3bdd7fca | 928 | gfnc = calloc(1, sizeof(*gfnc)); |
d35f447d RZ |
929 | rv = dplane_provider_register(prov_name, DPLANE_PRIO_POSTPROCESS, |
930 | DPLANE_PROV_FLAG_THREADED, fpm_nl_start, | |
3bdd7fca | 931 | fpm_nl_process, fpm_nl_finish, gfnc, |
d35f447d RZ |
932 | &prov); |
933 | ||
934 | if (IS_ZEBRA_DEBUG_DPLANE) | |
935 | zlog_debug("%s register status: %d", prov_name, rv); | |
936 | ||
3bdd7fca | 937 | install_node(&fpm_node, fpm_write_config); |
6cc059cd RZ |
938 | install_element(ENABLE_NODE, &fpm_show_counters_cmd); |
939 | install_element(ENABLE_NODE, &fpm_show_counters_json_cmd); | |
940 | install_element(ENABLE_NODE, &fpm_reset_counters_cmd); | |
3bdd7fca RZ |
941 | install_element(CONFIG_NODE, &fpm_set_address_cmd); |
942 | install_element(CONFIG_NODE, &no_fpm_set_address_cmd); | |
943 | ||
d35f447d RZ |
944 | return 0; |
945 | } | |
946 | ||
947 | static int fpm_nl_init(void) | |
948 | { | |
949 | hook_register(frr_late_init, fpm_nl_new); | |
950 | return 0; | |
951 | } | |
952 | ||
953 | FRR_MODULE_SETUP( | |
954 | .name = "dplane_fpm_nl", | |
955 | .version = "0.0.1", | |
956 | .description = "Data plane plugin for FPM using netlink.", | |
957 | .init = fpm_nl_init, | |
958 | ) |