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