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