]> git.proxmox.com Git - mirror_frr.git/blame - bfdd/dplane.c
Merge pull request #10405 from kanaya516/feature/adj-sid-yang
[mirror_frr.git] / bfdd / dplane.c
CommitLineData
230aefe2
RZ
1/*
2 * BFD data plane implementation (distributed BFD).
3 *
4 * Copyright (C) 2020 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 <zebra.h>
23
24#include <netinet/in.h>
6655b43d 25#include <netinet/tcp.h>
230aefe2
RZ
26#include <sys/socket.h>
27#include <sys/un.h>
28
29#ifdef __FreeBSD__
30#include <sys/endian.h>
31#else
32#include <endian.h>
33#endif /* __FreeBSD__ */
34
35#include <errno.h>
36#include <time.h>
37
38#include "lib/hook.h"
6655b43d 39#include "lib/network.h"
230aefe2
RZ
40#include "lib/printfrr.h"
41#include "lib/stream.h"
42#include "lib/thread.h"
43
44#include "bfd.h"
45#include "bfddp_packet.h"
46
47#include "lib/openbsd-queue.h"
48
bf8d3d6a
DL
49DEFINE_MTYPE_STATIC(BFDD, BFDD_DPLANE_CTX,
50 "Data plane client allocated memory");
230aefe2
RZ
51
52/** Data plane client socket buffer size. */
53#define BFD_DPLANE_CLIENT_BUF_SIZE 8192
54
55struct bfd_dplane_ctx {
56 /** Client file descriptor. */
57 int sock;
6655b43d
RZ
58 /** Is this a connected or accepted? */
59 bool client;
60 /** Is the socket still connecting? */
61 bool connecting;
62 /** Client/server address. */
63 union {
64 struct sockaddr sa;
65 struct sockaddr_in sin;
66 struct sockaddr_in6 sin6;
67 struct sockaddr_un sun;
68 } addr;
69 /** Address length. */
70 socklen_t addrlen;
230aefe2
RZ
71 /** Data plane current last used ID. */
72 uint16_t last_id;
73
74 /** Input buffer data. */
75 struct stream *inbuf;
76 /** Output buffer data. */
77 struct stream *outbuf;
78 /** Input event data. */
79 struct thread *inbufev;
80 /** Output event data. */
81 struct thread *outbufev;
6655b43d
RZ
82 /** Connection event. */
83 struct thread *connectev;
230aefe2
RZ
84
85 /** Amount of bytes read. */
86 uint64_t in_bytes;
87 /** Amount of bytes read peak. */
88 uint64_t in_bytes_peak;
89 /** Amount of bytes written. */
90 uint64_t out_bytes;
91 /** Amount of bytes written peak. */
92 uint64_t out_bytes_peak;
93 /** Amount of output buffer full events (`bfd_dplane_enqueue` failed).
94 */
95 uint64_t out_fullev;
96
97 /** Amount of messages read (full messages). */
98 uint64_t in_msgs;
99 /** Amount of messages enqueued (maybe written). */
100 uint64_t out_msgs;
101
102 TAILQ_ENTRY(bfd_dplane_ctx) entry;
103};
104
105/**
106 * Callback type for `bfd_dplane_expect`. \see bfd_dplane_expect.
107 */
108typedef void (*bfd_dplane_expect_cb)(struct bfddp_message *msg, void *arg);
109
cc9f21da 110static void bfd_dplane_client_connect(struct thread *t);
6655b43d 111static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc);
230aefe2 112static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc);
efd04d60
RZ
113static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
114 struct bfd_session *bs);
230aefe2
RZ
115
116/*
117 * BFD data plane helper functions.
118 */
119static const char *bfd_dplane_messagetype2str(enum bfddp_message_type bmt)
120{
121 switch (bmt) {
122 case ECHO_REQUEST:
123 return "ECHO_REQUEST";
124 case ECHO_REPLY:
125 return "ECHO_REPLY";
126 case DP_ADD_SESSION:
127 return "DP_ADD_SESSION";
128 case DP_DELETE_SESSION:
129 return "DP_DELETE_SESSION";
130 case BFD_STATE_CHANGE:
131 return "BFD_STATE_CHANGE";
132 case DP_REQUEST_SESSION_COUNTERS:
133 return "DP_REQUEST_SESSION_COUNTERS";
134 case BFD_SESSION_COUNTERS:
135 return "BFD_SESSION_COUNTERS";
136 default:
137 return "UNKNOWN";
138 }
139}
140
141static void bfd_dplane_debug_message(const struct bfddp_message *msg)
142{
143 enum bfddp_message_type bmt;
144 char buf[256], addrs[256];
145 uint32_t flags;
146 int rv;
147
148 if (!bglobal.debug_dplane)
149 return;
150
151 bmt = ntohs(msg->header.type);
152 zlog_debug("dplane-packet: [version=%d length=%d type=%s (%d)]",
153 msg->header.version, ntohs(msg->header.length),
154 bfd_dplane_messagetype2str(bmt), bmt);
155
156 switch (bmt) {
157 case ECHO_REPLY:
158 case ECHO_REQUEST:
159 zlog_debug(" [dp_time=%" PRIu64 " bfdd_time=%" PRIu64 "]",
f62de63c
DL
160 be64toh(msg->data.echo.dp_time),
161 be64toh(msg->data.echo.bfdd_time));
230aefe2
RZ
162 break;
163
164 case DP_ADD_SESSION:
165 case DP_DELETE_SESSION:
166 flags = ntohl(msg->data.session.flags);
167 if (flags & SESSION_IPV6)
168 snprintfrr(addrs, sizeof(addrs), "src=%pI6 dst=%pI6",
169 &msg->data.session.src,
170 &msg->data.session.dst);
171 else
172 snprintfrr(addrs, sizeof(addrs), "src=%pI4 dst=%pI4",
f1db813d
DL
173 (struct in_addr *)&msg->data.session.src,
174 (struct in_addr *)&msg->data.session.dst);
230aefe2
RZ
175
176 buf[0] = 0;
177 if (flags & SESSION_CBIT)
178 strlcat(buf, "cpi ", sizeof(buf));
179 if (flags & SESSION_ECHO)
180 strlcat(buf, "echo ", sizeof(buf));
181 if (flags & SESSION_IPV6)
182 strlcat(buf, "ipv6 ", sizeof(buf));
183 if (flags & SESSION_DEMAND)
184 strlcat(buf, "demand ", sizeof(buf));
185 if (flags & SESSION_PASSIVE)
186 strlcat(buf, "passive ", sizeof(buf));
187 if (flags & SESSION_MULTIHOP)
188 strlcat(buf, "multihop ", sizeof(buf));
189 if (flags & SESSION_SHUTDOWN)
190 strlcat(buf, "shutdown ", sizeof(buf));
191
192 /* Remove the last space to make things prettier. */
193 rv = (int)strlen(buf);
194 if (rv > 0)
195 buf[rv - 1] = 0;
196
197 zlog_debug(
198 " [flags=0x%08x{%s} %s ttl=%d detect_mult=%d "
199 "ifindex=%d ifname=%s]",
200 flags, buf, addrs, msg->data.session.ttl,
201 msg->data.session.detect_mult,
202 ntohl(msg->data.session.ifindex),
203 msg->data.session.ifname);
204 break;
205
206 case BFD_STATE_CHANGE:
207 buf[0] = 0;
208 flags = ntohl(msg->data.state.remote_flags);
209 if (flags & RBIT_CPI)
210 strlcat(buf, "cbit ", sizeof(buf));
211 if (flags & RBIT_DEMAND)
212 strlcat(buf, "demand ", sizeof(buf));
213 if (flags & RBIT_MP)
214 strlcat(buf, "mp ", sizeof(buf));
215
216 /* Remove the last space to make things prettier. */
217 rv = (int)strlen(buf);
218 if (rv > 0)
219 buf[rv - 1] = 0;
220
221 zlog_debug(
222 " [lid=%u rid=%u flags=0x%02x{%s} state=%s "
223 "diagnostics=%s mult=%d tx=%u rx=%u erx=%u]",
224 ntohl(msg->data.state.lid), ntohl(msg->data.state.rid),
225 flags, buf, state_list[msg->data.state.state].str,
226 diag2str(msg->data.state.diagnostics),
227 msg->data.state.detection_multiplier,
228 ntohl(msg->data.state.desired_tx),
229 ntohl(msg->data.state.required_rx),
230 ntohl(msg->data.state.required_echo_rx));
231 break;
232
233 case DP_REQUEST_SESSION_COUNTERS:
234 zlog_debug(" [lid=%u]", ntohl(msg->data.counters_req.lid));
235 break;
236
237 case BFD_SESSION_COUNTERS:
238 zlog_debug(
239 " [lid=%u "
240 "control{in %" PRIu64 " bytes (%" PRIu64
241 " packets), "
242 "out %" PRIu64 " bytes (%" PRIu64
243 " packets)} "
244 "echo{in %" PRIu64 " bytes (%" PRIu64
245 " packets), "
246 "out %" PRIu64 " bytes (%" PRIu64 " packets)}]",
247 ntohl(msg->data.session_counters.lid),
f62de63c
DL
248 be64toh(msg->data.session_counters.control_input_bytes),
249 be64toh(msg->data.session_counters
250 .control_input_packets),
251 be64toh(msg->data.session_counters
252 .control_output_bytes),
253 be64toh(msg->data.session_counters
254 .control_output_packets),
255 be64toh(msg->data.session_counters.echo_input_bytes),
256 be64toh(msg->data.session_counters.echo_input_packets),
257 be64toh(msg->data.session_counters.echo_output_bytes),
258 be64toh(msg->data.session_counters
259 .echo_output_packets));
230aefe2
RZ
260 break;
261 }
262}
263
400632a9
RZ
264/**
265 * Gets the next unused non zero identification.
266 *
267 * \param bdc the data plane context.
268 *
269 * \returns next usable id.
270 */
271static uint16_t bfd_dplane_next_id(struct bfd_dplane_ctx *bdc)
272{
273 bdc->last_id++;
274
275 /* Don't use reserved id `0`. */
276 if (bdc->last_id == 0)
277 bdc->last_id = 1;
278
279 return bdc->last_id;
280}
281
230aefe2
RZ
282static ssize_t bfd_dplane_flush(struct bfd_dplane_ctx *bdc)
283{
284 ssize_t total = 0;
285 int rv;
286
287 while (STREAM_READABLE(bdc->outbuf)) {
288 /* Flush buffer contents to socket. */
289 rv = stream_flush(bdc->outbuf, bdc->sock);
290 if (rv == -1) {
291 /* Interruption: try again. */
292 if (errno == EAGAIN || errno == EWOULDBLOCK
293 || errno == EINTR)
294 continue;
295
296 zlog_warn("%s: socket failed: %s", __func__,
297 strerror(errno));
298 bfd_dplane_ctx_free(bdc);
299 return 0;
300 }
301 if (rv == 0) {
302 if (bglobal.debug_dplane)
303 zlog_info("%s: connection closed", __func__);
304
305 bfd_dplane_ctx_free(bdc);
306 return 0;
307 }
308
309 /* Account total written. */
310 total += rv;
311
312 /* Account output bytes. */
313 bdc->out_bytes += (uint64_t)rv;
314
315 /* Forward pointer. */
316 stream_forward_getp(bdc->outbuf, (size_t)rv);
317 }
318
319 /* Make more space for new data. */
320 stream_pulldown(bdc->outbuf);
321
322 /* Disable write ready events. */
323 THREAD_OFF(bdc->outbufev);
324
325 return total;
326}
327
cc9f21da 328static void bfd_dplane_write(struct thread *t)
230aefe2 329{
6655b43d
RZ
330 struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
331
332 /* Handle connection stage. */
333 if (bdc->connecting && bfd_dplane_client_connecting(bdc))
cc9f21da 334 return;
6655b43d
RZ
335
336 bfd_dplane_flush(bdc);
230aefe2
RZ
337}
338
339static void
340bfd_dplane_session_state_change(struct bfd_dplane_ctx *bdc,
341 const struct bfddp_state_change *state)
342{
343 struct bfd_session *bs;
344 uint32_t flags;
345 int old_state;
346
347 /* Look up session. */
348 bs = bfd_id_lookup(ntohl(state->lid));
349 if (bs == NULL) {
350 if (bglobal.debug_dplane)
351 zlog_debug("%s: failed to find session to update",
352 __func__);
353 return;
354 }
355
356 flags = ntohl(state->remote_flags);
357 old_state = bs->ses_state;
358
359 /* Update session state. */
360 bs->ses_state = state->state;
361 bs->remote_diag = state->diagnostics;
362 bs->discrs.remote_discr = ntohl(state->rid);
363 bs->remote_cbit = !!(flags & RBIT_CPI);
364 bs->remote_detect_mult = state->detection_multiplier;
365 bs->remote_timers.desired_min_tx = ntohl(state->desired_tx);
366 bs->remote_timers.required_min_rx = ntohl(state->required_rx);
367 bs->remote_timers.required_min_echo = ntohl(state->required_echo_rx);
368
369 /* Notify and update counters. */
370 control_notify(bs, bs->ses_state);
371
372 /* No state change. */
373 if (old_state == bs->ses_state)
374 return;
375
376 switch (bs->ses_state) {
377 case PTM_BFD_ADM_DOWN:
378 case PTM_BFD_DOWN:
379 /* Both states mean down. */
380 if (old_state == PTM_BFD_ADM_DOWN || old_state == PTM_BFD_DOWN)
381 break;
382
383 monotime(&bs->downtime);
384 bs->stats.session_down++;
385 break;
386 case PTM_BFD_UP:
387 monotime(&bs->uptime);
388 bs->stats.session_up++;
389 break;
390 case PTM_BFD_INIT:
391 /* NOTHING */
392 break;
393
394 default:
395 zlog_warn("%s: unhandled new state %d", __func__,
396 bs->ses_state);
397 break;
398 }
399
400 if (bglobal.debug_peer_event)
401 zlog_debug("state-change: [data plane: %s] %s -> %s",
402 bs_to_string(bs), state_list[old_state].str,
403 state_list[bs->ses_state].str);
404}
405
406/**
407 * Enqueue message in output buffer.
408 *
409 * \param[in,out] bdc data plane client context.
410 * \param[in] buf the message to buffer.
411 * \param[in] buflen the amount of bytes to buffer.
412 *
413 * \returns `-1` on failure (buffer full) or `0` on success.
414 */
415static int bfd_dplane_enqueue(struct bfd_dplane_ctx *bdc, const void *buf,
416 size_t buflen)
417{
418 size_t rlen;
419
6655b43d
RZ
420 /* Handle not connected yet client. */
421 if (bdc->client && bdc->sock == -1)
422 return -1;
423
230aefe2
RZ
424 /* Not enough space. */
425 if (buflen > STREAM_WRITEABLE(bdc->outbuf)) {
426 bdc->out_fullev++;
427 return -1;
428 }
429
430 /* Show debug message if active. */
431 bfd_dplane_debug_message((struct bfddp_message *)buf);
432
433 /* Buffer the message. */
434 stream_write(bdc->outbuf, buf, buflen);
435
436 /* Account message as sent. */
437 bdc->out_msgs++;
438 /* Register peak buffered bytes. */
439 rlen = STREAM_READABLE(bdc->outbuf);
440 if (bdc->out_bytes_peak < rlen)
441 bdc->out_bytes_peak = rlen;
442
443 /* Schedule if it is not yet. */
444 if (bdc->outbufev == NULL)
445 thread_add_write(master, bfd_dplane_write, bdc, bdc->sock,
446 &bdc->outbufev);
447
448 return 0;
449}
450
451static void bfd_dplane_echo_request_handle(struct bfd_dplane_ctx *bdc,
452 const struct bfddp_message *bm)
453{
454 struct bfddp_message msg = {};
455 uint16_t msglen = sizeof(msg.header) + sizeof(msg.data.echo);
456 struct timeval tv;
457
458 gettimeofday(&tv, NULL);
459
460 /* Prepare header. */
461 msg.header.version = BFD_DP_VERSION;
462 msg.header.type = htons(ECHO_REPLY);
463 msg.header.length = htons(msglen);
464
465 /* Prepare payload. */
466 msg.data.echo.dp_time = bm->data.echo.dp_time;
467 msg.data.echo.bfdd_time =
468 htobe64((uint64_t)((tv.tv_sec * 1000000) + tv.tv_usec));
469
470 /* Enqueue for output. */
471 bfd_dplane_enqueue(bdc, &msg, msglen);
472}
473
474static void bfd_dplane_handle_message(struct bfddp_message *msg, void *arg)
475{
476 enum bfddp_message_type bmt;
477 struct bfd_dplane_ctx *bdc = arg;
478
479 /* Call the appropriated handler. */
480 bmt = ntohs(msg->header.type);
481 switch (bmt) {
482 case ECHO_REQUEST:
483 bfd_dplane_echo_request_handle(bdc, msg);
484 break;
485 case BFD_STATE_CHANGE:
486 bfd_dplane_session_state_change(bdc, &msg->data.state);
487 break;
488 case ECHO_REPLY:
489 /* NOTHING: we don't do anything with this information. */
490 break;
491 case DP_ADD_SESSION:
492 case DP_DELETE_SESSION:
493 case DP_REQUEST_SESSION_COUNTERS:
494 /* NOTHING: we are not supposed to receive this. */
495 break;
496 case BFD_SESSION_COUNTERS:
497 /*
498 * NOTHING: caller of DP_REQUEST_SESSION_COUNTERS should
499 * handle this with `bfd_dplane_expect`.
500 */
501 break;
502
503 default:
504 zlog_debug("%s: unhandled message type %d", __func__, bmt);
505 break;
506 }
507}
508
509/**
510 * Reads the socket immediately to receive data plane answer to query.
511 *
512 * \param bdc the data plane context.
513 * \param id the message ID waiting response.
514 * \param cb the callback to call when ready.
515 * \param arg the callback argument.
516 *
517 * \return
518 * `-2` on unavailability (try again), `-1` on failure or `0` on success.
519 */
520static int bfd_dplane_expect(struct bfd_dplane_ctx *bdc, uint16_t id,
521 bfd_dplane_expect_cb cb, void *arg)
522{
523 struct bfddp_message_header *bh;
524 size_t rlen = 0, reads = 0;
525 ssize_t rv;
526
527 /*
528 * Don't attempt to read if buffer is full, otherwise we'll get a
529 * bogus 'connection closed' signal (rv == 0).
530 */
531 if (bdc->inbuf->endp == bdc->inbuf->size)
532 goto skip_read;
533
534read_again:
535 /* Attempt to read message from client. */
536 rv = stream_read_try(bdc->inbuf, bdc->sock,
537 STREAM_WRITEABLE(bdc->inbuf));
538 if (rv == 0) {
539 if (bglobal.debug_dplane)
540 zlog_info("%s: socket closed", __func__);
541
542 bfd_dplane_ctx_free(bdc);
543 return -1;
544 }
545 if (rv == -1) {
546 zlog_warn("%s: socket failed: %s", __func__, strerror(errno));
547 bfd_dplane_ctx_free(bdc);
548 return -1;
549 }
550
551 /* We got interrupted, reschedule read. */
552 if (rv == -2)
553 return -2;
554
555 /* Account read bytes. */
556 bdc->in_bytes += (uint64_t)rv;
557 /* Register peak buffered bytes. */
558 rlen = STREAM_READABLE(bdc->inbuf);
559 if (bdc->in_bytes_peak < rlen)
560 bdc->in_bytes_peak = rlen;
561
562skip_read:
563 while (rlen > 0) {
564 bh = (struct bfddp_message_header *)stream_pnt(bdc->inbuf);
565 /* Not enough data read. */
566 if (ntohs(bh->length) > rlen)
567 goto read_again;
568
569 /* Account full message read. */
570 bdc->in_msgs++;
571
572 /* Account this message as whole read for buffer reorganize. */
573 reads++;
574
575 /* Check for bad version. */
576 if (bh->version != BFD_DP_VERSION) {
577 zlog_err("%s: bad data plane client version: %d",
578 __func__, bh->version);
579 return -1;
580 }
581
582 /* Show debug message if active. */
583 bfd_dplane_debug_message((struct bfddp_message *)bh);
584
585 /*
586 * Handle incoming message with callback if the ID matches,
587 * otherwise fallback to default handler.
588 */
589 if (id && ntohs(bh->id) == id)
590 cb((struct bfddp_message *)bh, arg);
591 else
592 bfd_dplane_handle_message((struct bfddp_message *)bh,
593 bdc);
594
595 /* Advance current read pointer. */
596 stream_forward_getp(bdc->inbuf, ntohs(bh->length));
597
598 /* Reduce the buffer available bytes. */
599 rlen -= ntohs(bh->length);
600
601 /* Reorganize buffer to handle more bytes read. */
602 if (reads >= 3) {
603 stream_pulldown(bdc->inbuf);
604 reads = 0;
605 }
606
607 /* We found the message, return to caller. */
608 if (id && ntohs(bh->id) == id)
609 break;
610 }
611
612 return 0;
613}
614
cc9f21da 615static void bfd_dplane_read(struct thread *t)
230aefe2
RZ
616{
617 struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
618 int rv;
619
620 rv = bfd_dplane_expect(bdc, 0, bfd_dplane_handle_message, NULL);
621 if (rv == -1)
cc9f21da 622 return;
230aefe2
RZ
623
624 stream_pulldown(bdc->inbuf);
625 thread_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
230aefe2
RZ
626}
627
efd04d60
RZ
628static void _bfd_session_register_dplane(struct hash_bucket *hb, void *arg)
629{
630 struct bfd_session *bs = hb->data;
631 struct bfd_dplane_ctx *bdc = arg;
632
633 if (bs->bdc != NULL)
634 return;
635
636 /* Disable software session. */
637 bfd_session_disable(bs);
638
639 /* Move session to data plane. */
640 _bfd_dplane_add_session(bdc, bs);
641}
642
230aefe2
RZ
643static struct bfd_dplane_ctx *bfd_dplane_ctx_new(int sock)
644{
645 struct bfd_dplane_ctx *bdc;
646
647 bdc = XCALLOC(MTYPE_BFDD_DPLANE_CTX, sizeof(*bdc));
230aefe2
RZ
648
649 bdc->sock = sock;
650 bdc->inbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
651 bdc->outbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
6655b43d
RZ
652
653 /* If not socket ready, skip read and session registration. */
654 if (sock == -1)
655 return bdc;
656
230aefe2
RZ
657 thread_add_read(master, bfd_dplane_read, bdc, sock, &bdc->inbufev);
658
efd04d60
RZ
659 /* Register all unattached sessions. */
660 bfd_key_iterate(_bfd_session_register_dplane, bdc);
661
230aefe2
RZ
662 return bdc;
663}
664
665static void _bfd_session_unregister_dplane(struct hash_bucket *hb, void *arg)
666{
667 struct bfd_session *bs = hb->data;
668 struct bfd_dplane_ctx *bdc = arg;
669
670 if (bs->bdc != bdc)
671 return;
672
673 bs->bdc = NULL;
efd04d60
RZ
674
675 /* Fallback to software. */
676 bfd_session_enable(bs);
230aefe2
RZ
677}
678
679static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc)
680{
681 if (bglobal.debug_dplane)
682 zlog_debug("%s: terminating data plane client %d", __func__,
683 bdc->sock);
684
6655b43d
RZ
685 /* Client mode has special treatment. */
686 if (bdc->client) {
687 /* Disable connection event if any. */
688 THREAD_OFF(bdc->connectev);
689
690 /* Normal treatment on shutdown. */
691 if (bglobal.bg_shutdown)
692 goto free_resources;
693
694 /* Attempt reconnection. */
695 socket_close(&bdc->sock);
696 THREAD_OFF(bdc->inbufev);
697 THREAD_OFF(bdc->outbufev);
698 thread_add_timer(master, bfd_dplane_client_connect, bdc, 3,
699 &bdc->connectev);
700 return;
701 }
702
703free_resources:
230aefe2
RZ
704 /* Remove from the list of attached data planes. */
705 TAILQ_REMOVE(&bglobal.bg_dplaneq, bdc, entry);
706
707 /* Detach all associated sessions. */
708 if (bglobal.bg_shutdown == false)
709 bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
710
711 /* Free resources. */
712 socket_close(&bdc->sock);
713 stream_free(bdc->inbuf);
714 stream_free(bdc->outbuf);
715 THREAD_OFF(bdc->inbufev);
716 THREAD_OFF(bdc->outbufev);
717 XFREE(MTYPE_BFDD_DPLANE_CTX, bdc);
718}
719
efd04d60
RZ
720static void _bfd_dplane_session_fill(const struct bfd_session *bs,
721 struct bfddp_message *msg)
722{
723 uint16_t msglen = sizeof(msg->header) + sizeof(msg->data.session);
724
725 /* Message header. */
726 msg->header.version = BFD_DP_VERSION;
727 msg->header.length = ntohs(msglen);
728 msg->header.type = ntohs(DP_ADD_SESSION);
729
730 /* Message payload. */
731 msg->data.session.dst = bs->key.peer;
732 msg->data.session.src = bs->key.local;
733 msg->data.session.detect_mult = bs->detect_mult;
734
735 if (bs->ifp) {
736 msg->data.session.ifindex = htonl(bs->ifp->ifindex);
737 strlcpy(msg->data.session.ifname, bs->ifp->name,
738 sizeof(msg->data.session.ifname));
739 }
740 if (bs->flags & BFD_SESS_FLAG_MH) {
741 msg->data.session.flags |= SESSION_MULTIHOP;
742 msg->data.session.ttl = bs->mh_ttl;
743 } else
744 msg->data.session.ttl = BFD_TTL_VAL;
745
746 if (bs->flags & BFD_SESS_FLAG_IPV6)
747 msg->data.session.flags |= SESSION_IPV6;
748 if (bs->flags & BFD_SESS_FLAG_ECHO)
749 msg->data.session.flags |= SESSION_ECHO;
750 if (bs->flags & BFD_SESS_FLAG_CBIT)
751 msg->data.session.flags |= SESSION_CBIT;
752 if (bs->flags & BFD_SESS_FLAG_PASSIVE)
753 msg->data.session.flags |= SESSION_PASSIVE;
754 if (bs->flags & BFD_SESS_FLAG_SHUTDOWN)
755 msg->data.session.flags |= SESSION_SHUTDOWN;
756
757 msg->data.session.flags = htonl(msg->data.session.flags);
758 msg->data.session.lid = htonl(bs->discrs.my_discr);
759 msg->data.session.min_tx = htonl(bs->timers.desired_min_tx);
760 msg->data.session.min_rx = htonl(bs->timers.required_min_rx);
4df3e31c
IR
761 msg->data.session.min_echo_tx = htonl(bs->timers.desired_min_echo_tx);
762 msg->data.session.min_echo_rx = htonl(bs->timers.required_min_echo_rx);
efd04d60
RZ
763}
764
765static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
766 struct bfd_session *bs)
767{
768 int rv;
769
770 /* Associate session. */
771 bs->bdc = bdc;
772
773 /* Reset previous state. */
774 bs->remote_diag = 0;
775 bs->local_diag = 0;
776 bs->ses_state = PTM_BFD_DOWN;
777
778 /* Enqueue message to data plane client. */
779 rv = bfd_dplane_update_session(bs);
780 if (rv != 0)
781 bs->bdc = NULL;
782
783 return rv;
784}
785
400632a9
RZ
786static void _bfd_dplane_update_session_counters(struct bfddp_message *msg,
787 void *arg)
788{
789 struct bfd_session *bs = arg;
790
791 bs->stats.rx_ctrl_pkt =
792 be64toh(msg->data.session_counters.control_input_packets);
793 bs->stats.tx_ctrl_pkt =
794 be64toh(msg->data.session_counters.control_output_packets);
795 bs->stats.rx_echo_pkt =
796 be64toh(msg->data.session_counters.echo_input_packets);
797 bs->stats.tx_echo_pkt =
798 be64toh(msg->data.session_counters.echo_output_bytes);
799}
800
801/**
802 * Send message to data plane requesting the session counters.
803 *
804 * \param bs the BFD session.
805 *
806 * \returns `0` on failure or the request id.
807 */
808static uint16_t bfd_dplane_request_counters(const struct bfd_session *bs)
809{
810 struct bfddp_message msg = {};
811 size_t msglen = sizeof(msg.header) + sizeof(msg.data.counters_req);
812
813 /* Fill header information. */
814 msg.header.version = BFD_DP_VERSION;
815 msg.header.length = htons(msglen);
816 msg.header.type = htons(DP_REQUEST_SESSION_COUNTERS);
817 msg.header.id = htons(bfd_dplane_next_id(bs->bdc));
818
819 /* Session to get counters. */
820 msg.data.counters_req.lid = htonl(bs->discrs.my_discr);
821
822 /* If enqueue failed, let caller know. */
823 if (bfd_dplane_enqueue(bs->bdc, &msg, msglen) == -1)
824 return 0;
825
826 /* Flush socket. */
827 bfd_dplane_flush(bs->bdc);
828
829 return ntohs(msg.header.id);
830}
831
230aefe2
RZ
832/*
833 * Data plane listening socket.
834 */
cc9f21da 835static void bfd_dplane_accept(struct thread *t)
230aefe2
RZ
836{
837 struct bfd_global *bg = THREAD_ARG(t);
838 struct bfd_dplane_ctx *bdc;
839 int sock;
840
841 /* Accept new connection. */
842 sock = accept(bg->bg_dplane_sock, NULL, 0);
843 if (sock == -1) {
844 zlog_warn("%s: accept failed: %s", __func__, strerror(errno));
845 goto reschedule_and_return;
846 }
847
848 /* Create and handle new connection. */
849 bdc = bfd_dplane_ctx_new(sock);
850 TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
851
852 if (bglobal.debug_dplane)
853 zlog_debug("%s: new data plane client connected", __func__);
854
855reschedule_and_return:
856 thread_add_read(master, bfd_dplane_accept, bg, bg->bg_dplane_sock,
857 &bglobal.bg_dplane_sockev);
230aefe2
RZ
858}
859
6655b43d
RZ
860/*
861 * Data plane connecting socket.
862 */
863static void _bfd_dplane_client_bootstrap(struct bfd_dplane_ctx *bdc)
864{
865 bdc->connecting = false;
866
867 /* Clean up buffers. */
868 stream_reset(bdc->inbuf);
869 stream_reset(bdc->outbuf);
870
871 /* Ask for read notifications. */
872 thread_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
873
874 /* Remove all sessions then register again to send them all. */
875 bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
876 bfd_key_iterate(_bfd_session_register_dplane, bdc);
877}
878
879static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc)
880{
881 int rv;
882 socklen_t rvlen = sizeof(rv);
883
884 /* Make sure `errno` is reset, then test `getsockopt` success. */
885 errno = 0;
886 if (getsockopt(bdc->sock, SOL_SOCKET, SO_ERROR, &rv, &rvlen) == -1)
887 rv = -1;
888
889 /* Connection successful. */
890 if (rv == 0) {
891 if (bglobal.debug_dplane)
892 zlog_debug("%s: connected to server: %d", __func__,
893 bdc->sock);
894
895 _bfd_dplane_client_bootstrap(bdc);
896 return false;
897 }
898
899 switch (rv) {
900 case EINTR:
901 case EAGAIN:
902 case EALREADY:
903 case EINPROGRESS:
904 /* non error, wait more. */
905 return true;
906
907 default:
908 zlog_warn("%s: connection failed: %s", __func__,
909 strerror(errno));
910 bfd_dplane_ctx_free(bdc);
911 return true;
912 }
913}
914
cc9f21da 915static void bfd_dplane_client_connect(struct thread *t)
6655b43d
RZ
916{
917 struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
918 int rv, sock;
919 socklen_t rvlen = sizeof(rv);
920
921 /* Allocate new socket. */
922 sock = socket(bdc->addr.sa.sa_family, SOCK_STREAM, 0);
923 if (sock == -1) {
924 zlog_warn("%s: failed to initialize socket: %s", __func__,
925 strerror(errno));
926 goto reschedule_connect;
927 }
928
929 /* Set non blocking socket. */
930 set_nonblocking(sock);
931
932 /* Set 'no delay' (disables nagle algorithm) for IPv4/IPv6. */
933 rv = 1;
934 if (bdc->addr.sa.sa_family != AF_UNIX
935 && setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &rv, rvlen) == -1)
936 zlog_warn("%s: TCP_NODELAY: %s", __func__, strerror(errno));
937
938 /* Attempt to connect. */
939 rv = connect(sock, &bdc->addr.sa, bdc->addrlen);
940 if (rv == -1 && (errno != EINPROGRESS && errno != EAGAIN)) {
941 zlog_warn("%s: data plane connection failed: %s", __func__,
942 strerror(errno));
943 goto reschedule_connect;
944 }
945
946 bdc->sock = sock;
947 if (rv == -1) {
948 if (bglobal.debug_dplane)
949 zlog_debug("%s: server connection in progress: %d",
950 __func__, sock);
951
952 /* If we are not connected yet, ask for write notifications. */
953 bdc->connecting = true;
954 thread_add_write(master, bfd_dplane_write, bdc, bdc->sock,
955 &bdc->outbufev);
956 } else {
957 if (bglobal.debug_dplane)
958 zlog_debug("%s: server connection: %d", __func__, sock);
959
960 /* Otherwise just start accepting data. */
961 _bfd_dplane_client_bootstrap(bdc);
962 }
963
6655b43d
RZ
964reschedule_connect:
965 THREAD_OFF(bdc->inbufev);
966 THREAD_OFF(bdc->outbufev);
967 socket_close(&sock);
968 thread_add_timer(master, bfd_dplane_client_connect, bdc, 3,
969 &bdc->connectev);
6655b43d
RZ
970}
971
972static void bfd_dplane_client_init(const struct sockaddr *sa, socklen_t salen)
973{
974 struct bfd_dplane_ctx *bdc;
975
976 /* Allocate context and copy address for reconnection. */
977 bdc = bfd_dplane_ctx_new(-1);
978 if (salen <= sizeof(bdc->addr)) {
979 memcpy(&bdc->addr, sa, salen);
980 bdc->addrlen = sizeof(bdc->addr);
981 } else {
982 memcpy(&bdc->addr, sa, sizeof(bdc->addr));
983 bdc->addrlen = sizeof(bdc->addr);
984 zlog_warn("%s: server address truncated (from %d to %d)",
985 __func__, salen, bdc->addrlen);
986 }
987
988 bdc->client = true;
989
990 thread_add_timer(master, bfd_dplane_client_connect, bdc, 0,
991 &bdc->connectev);
992
993 /* Insert into data plane lists. */
994 TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
995}
996
230aefe2
RZ
997/**
998 * Termination phase of the distributed BFD infrastructure: free all allocated
999 * resources.
1000 */
1001static int bfd_dplane_finish_late(void)
1002{
1003 struct bfd_dplane_ctx *bdc;
1004
1005 if (bglobal.debug_dplane)
1006 zlog_debug("%s: terminating distributed BFD", __func__);
1007
1008 /* Free all data plane client contexts. */
1009 while ((bdc = TAILQ_FIRST(&bglobal.bg_dplaneq)) != NULL)
1010 bfd_dplane_ctx_free(bdc);
1011
1012 /* Cancel accept thread and close socket. */
1013 THREAD_OFF(bglobal.bg_dplane_sockev);
1014 close(bglobal.bg_dplane_sock);
1015
1016 return 0;
1017}
1018
1019/*
1020 * Data plane exported functions.
1021 */
6655b43d 1022void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client)
230aefe2
RZ
1023{
1024 int sock;
1025
1026 zlog_info("initializing distributed BFD");
1027
6655b43d
RZ
1028 /* Initialize queue header. */
1029 TAILQ_INIT(&bglobal.bg_dplaneq);
1030
1031 /* Initialize listening socket. */
1032 bglobal.bg_dplane_sock = -1;
1033
1034 /* Observe shutdown events. */
1035 hook_register(frr_fini, bfd_dplane_finish_late);
1036
1037 /* Handle client mode. */
1038 if (client) {
1039 bfd_dplane_client_init(sa, salen);
1040 return;
1041 }
1042
230aefe2
RZ
1043 /*
1044 * Data plane socket creation:
1045 * - Set REUSEADDR option for taking over previously open socket.
1046 * - Bind to address requested (maybe IPv4, IPv6, UNIX etc...).
1047 * - Listen on that address for new connections.
1048 * - Ask to be waken up when a new connection comes.
1049 */
1050 sock = socket(sa->sa_family, SOCK_STREAM, 0);
1051 if (sock == -1) {
1052 zlog_warn("%s: failed to initialize socket: %s", __func__,
1053 strerror(errno));
1054 return;
1055 }
1056
1057 if (sockopt_reuseaddr(sock) == -1) {
1058 zlog_warn("%s: failed to set reuseaddr: %s", __func__,
1059 strerror(errno));
1060 close(sock);
1061 return;
1062 }
1063
1064 /* Handle UNIX socket: delete previous socket if any. */
1065 if (sa->sa_family == AF_UNIX)
1066 unlink(((struct sockaddr_un *)sa)->sun_path);
1067
1068 if (bind(sock, sa, salen) == -1) {
1069 zlog_warn("%s: failed to bind socket: %s", __func__,
1070 strerror(errno));
1071 close(sock);
1072 return;
1073 }
1074
1075 if (listen(sock, SOMAXCONN) == -1) {
1076 zlog_warn("%s: failed to put socket on listen: %s", __func__,
1077 strerror(errno));
1078 close(sock);
1079 return;
1080 }
1081
1082 bglobal.bg_dplane_sock = sock;
1083 thread_add_read(master, bfd_dplane_accept, &bglobal, sock,
1084 &bglobal.bg_dplane_sockev);
230aefe2 1085}
efd04d60
RZ
1086
1087int bfd_dplane_add_session(struct bfd_session *bs)
1088{
1089 struct bfd_dplane_ctx *bdc;
1090
1091 /* Select a data plane client to install session. */
1092 TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
1093 if (_bfd_dplane_add_session(bdc, bs) == 0)
1094 return 0;
1095 }
1096
1097 return -1;
1098}
1099
1100int bfd_dplane_update_session(const struct bfd_session *bs)
1101{
1102 struct bfddp_message msg = {};
1103
1104 if (bs->bdc == NULL)
1105 return 0;
1106
1107 _bfd_dplane_session_fill(bs, &msg);
1108
1109 /* Enqueue message to data plane client. */
1110 return bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
1111}
1112
1113int bfd_dplane_delete_session(struct bfd_session *bs)
1114{
1115 struct bfddp_message msg = {};
1116 int rv;
1117
1118 /* Not using data plane, just return success. */
1119 if (bs->bdc == NULL)
1120 return 0;
1121
1122 /* Fill most of the common fields. */
1123 _bfd_dplane_session_fill(bs, &msg);
1124
1125 /* Change the message type. */
1126 msg.header.type = ntohs(DP_DELETE_SESSION);
1127
1128 /* Enqueue message to data plane client. */
1129 rv = bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
1130
1131 /* Remove association. */
1132 bs->bdc = NULL;
1133
1134 return rv;
1135}
400632a9
RZ
1136
1137/*
1138 * Data plane CLI.
1139 */
1140void bfd_dplane_show_counters(struct vty *vty)
1141{
1142 struct bfd_dplane_ctx *bdc;
1143
1144#define SHOW_COUNTER(label, counter, formatter) \
1145 vty_out(vty, "%28s: %" formatter "\n", (label), (counter))
1146
1147 vty_out(vty, "%28s\n%28s\n", "Data plane", "==========");
1148 TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
1149 SHOW_COUNTER("File descriptor", bdc->sock, "d");
1150 SHOW_COUNTER("Input bytes", bdc->in_bytes, PRIu64);
1151 SHOW_COUNTER("Input bytes peak", bdc->in_bytes_peak, PRIu64);
1152 SHOW_COUNTER("Input messages", bdc->in_msgs, PRIu64);
1153 SHOW_COUNTER("Input current usage", STREAM_READABLE(bdc->inbuf),
1154 "zu");
1155 SHOW_COUNTER("Output bytes", bdc->out_bytes, PRIu64);
1156 SHOW_COUNTER("Output bytes peak", bdc->out_bytes_peak, PRIu64);
1157 SHOW_COUNTER("Output messages", bdc->out_msgs, PRIu64);
1158 SHOW_COUNTER("Output full events", bdc->out_fullev, PRIu64);
1159 SHOW_COUNTER("Output current usage",
1160 STREAM_READABLE(bdc->inbuf), "zu");
1161 vty_out(vty, "\n");
1162 }
1163#undef SHOW_COUNTER
1164}
1165
1166int bfd_dplane_update_session_counters(struct bfd_session *bs)
1167{
1168 uint16_t id;
1169 int rv;
1170
1171 /* If session is not using data plane, then just return success. */
1172 if (bs->bdc == NULL)
1173 return 0;
1174
1175 /* Make the request. */
1176 id = bfd_dplane_request_counters(bs);
1177 if (id == 0) {
1178 zlog_debug("%s: counters request failed", __func__);
1179 return -1;
1180 }
1181
1182 /* Handle interruptions. */
1183 do {
1184 rv = bfd_dplane_expect(bs->bdc, id,
1185 _bfd_dplane_update_session_counters, bs);
1186 } while (rv == -2);
1187
1188 return rv;
1189}