2 * BFD data plane implementation (distributed BFD).
4 * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF")
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.
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.
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
24 #include <netinet/in.h>
25 #include <netinet/tcp.h>
26 #include <sys/socket.h>
30 #include <sys/endian.h>
33 #endif /* __FreeBSD__ */
39 #include "lib/network.h"
40 #include "lib/printfrr.h"
41 #include "lib/stream.h"
42 #include "lib/thread.h"
45 #include "bfddp_packet.h"
47 #include "lib/openbsd-queue.h"
49 DEFINE_MTYPE_STATIC(BFDD
, BFDD_DPLANE_CTX
,
50 "Data plane client allocated memory");
52 /** Data plane client socket buffer size. */
53 #define BFD_DPLANE_CLIENT_BUF_SIZE 8192
55 struct bfd_dplane_ctx
{
56 /** Client file descriptor. */
58 /** Is this a connected or accepted? */
60 /** Is the socket still connecting? */
62 /** Client/server address. */
65 struct sockaddr_in sin
;
66 struct sockaddr_in6 sin6
;
67 struct sockaddr_un sun
;
69 /** Address length. */
71 /** Data plane current last used ID. */
74 /** Input buffer data. */
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
;
85 /** Amount of bytes read. */
87 /** Amount of bytes read peak. */
88 uint64_t in_bytes_peak
;
89 /** Amount of bytes written. */
91 /** Amount of bytes written peak. */
92 uint64_t out_bytes_peak
;
93 /** Amount of output buffer full events (`bfd_dplane_enqueue` failed).
97 /** Amount of messages read (full messages). */
99 /** Amount of messages enqueued (maybe written). */
102 TAILQ_ENTRY(bfd_dplane_ctx
) entry
;
106 * Callback type for `bfd_dplane_expect`. \see bfd_dplane_expect.
108 typedef void (*bfd_dplane_expect_cb
)(struct bfddp_message
*msg
, void *arg
);
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
);
117 * BFD data plane helper functions.
119 static const char *bfd_dplane_messagetype2str(enum bfddp_message_type bmt
)
123 return "ECHO_REQUEST";
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";
141 static void bfd_dplane_debug_message(const struct bfddp_message
*msg
)
143 enum bfddp_message_type bmt
;
144 char buf
[256], addrs
[256];
148 if (!bglobal
.debug_dplane
)
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
);
159 zlog_debug(" [dp_time=%" PRIu64
" bfdd_time=%" PRIu64
"]",
160 be64toh(msg
->data
.echo
.dp_time
),
161 be64toh(msg
->data
.echo
.bfdd_time
));
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
);
172 snprintfrr(addrs
, sizeof(addrs
), "src=%pI4 dst=%pI4",
173 (struct in_addr
*)&msg
->data
.session
.src
,
174 (struct in_addr
*)&msg
->data
.session
.dst
);
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
));
192 /* Remove the last space to make things prettier. */
193 rv
= (int)strlen(buf
);
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
);
206 case BFD_STATE_CHANGE
:
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
));
214 strlcat(buf
, "mp ", sizeof(buf
));
216 /* Remove the last space to make things prettier. */
217 rv
= (int)strlen(buf
);
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
));
233 case DP_REQUEST_SESSION_COUNTERS
:
234 zlog_debug(" [lid=%u]", ntohl(msg
->data
.counters_req
.lid
));
237 case BFD_SESSION_COUNTERS
:
240 "control{in %" PRIu64
" bytes (%" PRIu64
242 "out %" PRIu64
" bytes (%" PRIu64
244 "echo{in %" PRIu64
" bytes (%" PRIu64
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
));
265 * Gets the next unused non zero identification.
267 * \param bdc the data plane context.
269 * \returns next usable id.
271 static uint16_t bfd_dplane_next_id(struct bfd_dplane_ctx
*bdc
)
275 /* Don't use reserved id `0`. */
276 if (bdc
->last_id
== 0)
282 static ssize_t
bfd_dplane_flush(struct bfd_dplane_ctx
*bdc
)
287 while (STREAM_READABLE(bdc
->outbuf
)) {
288 /* Flush buffer contents to socket. */
289 rv
= stream_flush(bdc
->outbuf
, bdc
->sock
);
291 /* Interruption: try again. */
292 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
296 zlog_warn("%s: socket failed: %s", __func__
,
298 bfd_dplane_ctx_free(bdc
);
302 if (bglobal
.debug_dplane
)
303 zlog_info("%s: connection closed", __func__
);
305 bfd_dplane_ctx_free(bdc
);
309 /* Account total written. */
312 /* Account output bytes. */
313 bdc
->out_bytes
+= (uint64_t)rv
;
315 /* Forward pointer. */
316 stream_forward_getp(bdc
->outbuf
, (size_t)rv
);
319 /* Make more space for new data. */
320 stream_pulldown(bdc
->outbuf
);
322 /* Disable write ready events. */
323 THREAD_OFF(bdc
->outbufev
);
328 static void bfd_dplane_write(struct thread
*t
)
330 struct bfd_dplane_ctx
*bdc
= THREAD_ARG(t
);
332 /* Handle connection stage. */
333 if (bdc
->connecting
&& bfd_dplane_client_connecting(bdc
))
336 bfd_dplane_flush(bdc
);
340 bfd_dplane_session_state_change(struct bfd_dplane_ctx
*bdc
,
341 const struct bfddp_state_change
*state
)
343 struct bfd_session
*bs
;
347 /* Look up session. */
348 bs
= bfd_id_lookup(ntohl(state
->lid
));
350 if (bglobal
.debug_dplane
)
351 zlog_debug("%s: failed to find session to update",
356 flags
= ntohl(state
->remote_flags
);
357 old_state
= bs
->ses_state
;
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
);
369 /* Notify and update counters. */
370 control_notify(bs
, bs
->ses_state
);
372 /* No state change. */
373 if (old_state
== bs
->ses_state
)
376 switch (bs
->ses_state
) {
377 case PTM_BFD_ADM_DOWN
:
379 /* Both states mean down. */
380 if (old_state
== PTM_BFD_ADM_DOWN
|| old_state
== PTM_BFD_DOWN
)
383 monotime(&bs
->downtime
);
384 bs
->stats
.session_down
++;
387 monotime(&bs
->uptime
);
388 bs
->stats
.session_up
++;
395 zlog_warn("%s: unhandled new state %d", __func__
,
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
);
407 * Enqueue message in output buffer.
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.
413 * \returns `-1` on failure (buffer full) or `0` on success.
415 static int bfd_dplane_enqueue(struct bfd_dplane_ctx
*bdc
, const void *buf
,
420 /* Handle not connected yet client. */
421 if (bdc
->client
&& bdc
->sock
== -1)
424 /* Not enough space. */
425 if (buflen
> STREAM_WRITEABLE(bdc
->outbuf
)) {
430 /* Show debug message if active. */
431 bfd_dplane_debug_message((struct bfddp_message
*)buf
);
433 /* Buffer the message. */
434 stream_write(bdc
->outbuf
, buf
, buflen
);
436 /* Account message as sent. */
438 /* Register peak buffered bytes. */
439 rlen
= STREAM_READABLE(bdc
->outbuf
);
440 if (bdc
->out_bytes_peak
< rlen
)
441 bdc
->out_bytes_peak
= rlen
;
443 /* Schedule if it is not yet. */
444 if (bdc
->outbufev
== NULL
)
445 thread_add_write(master
, bfd_dplane_write
, bdc
, bdc
->sock
,
451 static void bfd_dplane_echo_request_handle(struct bfd_dplane_ctx
*bdc
,
452 const struct bfddp_message
*bm
)
454 struct bfddp_message msg
= {};
455 uint16_t msglen
= sizeof(msg
.header
) + sizeof(msg
.data
.echo
);
458 gettimeofday(&tv
, NULL
);
460 /* Prepare header. */
461 msg
.header
.version
= BFD_DP_VERSION
;
462 msg
.header
.type
= htons(ECHO_REPLY
);
463 msg
.header
.length
= htons(msglen
);
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
));
470 /* Enqueue for output. */
471 bfd_dplane_enqueue(bdc
, &msg
, msglen
);
474 static void bfd_dplane_handle_message(struct bfddp_message
*msg
, void *arg
)
476 enum bfddp_message_type bmt
;
477 struct bfd_dplane_ctx
*bdc
= arg
;
479 /* Call the appropriated handler. */
480 bmt
= ntohs(msg
->header
.type
);
483 bfd_dplane_echo_request_handle(bdc
, msg
);
485 case BFD_STATE_CHANGE
:
486 bfd_dplane_session_state_change(bdc
, &msg
->data
.state
);
489 /* NOTHING: we don't do anything with this information. */
492 case DP_DELETE_SESSION
:
493 case DP_REQUEST_SESSION_COUNTERS
:
494 /* NOTHING: we are not supposed to receive this. */
496 case BFD_SESSION_COUNTERS
:
498 * NOTHING: caller of DP_REQUEST_SESSION_COUNTERS should
499 * handle this with `bfd_dplane_expect`.
504 zlog_debug("%s: unhandled message type %d", __func__
, bmt
);
510 * Reads the socket immediately to receive data plane answer to query.
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.
518 * `-2` on unavailability (try again), `-1` on failure or `0` on success.
520 static int bfd_dplane_expect(struct bfd_dplane_ctx
*bdc
, uint16_t id
,
521 bfd_dplane_expect_cb cb
, void *arg
)
523 struct bfddp_message_header
*bh
;
524 size_t rlen
= 0, reads
= 0;
528 * Don't attempt to read if buffer is full, otherwise we'll get a
529 * bogus 'connection closed' signal (rv == 0).
531 if (bdc
->inbuf
->endp
== bdc
->inbuf
->size
)
535 /* Attempt to read message from client. */
536 rv
= stream_read_try(bdc
->inbuf
, bdc
->sock
,
537 STREAM_WRITEABLE(bdc
->inbuf
));
539 if (bglobal
.debug_dplane
)
540 zlog_info("%s: socket closed", __func__
);
542 bfd_dplane_ctx_free(bdc
);
546 zlog_warn("%s: socket failed: %s", __func__
, strerror(errno
));
547 bfd_dplane_ctx_free(bdc
);
551 /* We got interrupted, reschedule read. */
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
;
564 bh
= (struct bfddp_message_header
*)stream_pnt(bdc
->inbuf
);
565 /* Not enough data read. */
566 if (ntohs(bh
->length
) > rlen
)
569 /* Account full message read. */
572 /* Account this message as whole read for buffer reorganize. */
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
);
582 /* Show debug message if active. */
583 bfd_dplane_debug_message((struct bfddp_message
*)bh
);
586 * Handle incoming message with callback if the ID matches,
587 * otherwise fallback to default handler.
589 if (id
&& ntohs(bh
->id
) == id
)
590 cb((struct bfddp_message
*)bh
, arg
);
592 bfd_dplane_handle_message((struct bfddp_message
*)bh
,
595 /* Advance current read pointer. */
596 stream_forward_getp(bdc
->inbuf
, ntohs(bh
->length
));
598 /* Reduce the buffer available bytes. */
599 rlen
-= ntohs(bh
->length
);
601 /* Reorganize buffer to handle more bytes read. */
603 stream_pulldown(bdc
->inbuf
);
607 /* We found the message, return to caller. */
608 if (id
&& ntohs(bh
->id
) == id
)
615 static void bfd_dplane_read(struct thread
*t
)
617 struct bfd_dplane_ctx
*bdc
= THREAD_ARG(t
);
620 rv
= bfd_dplane_expect(bdc
, 0, bfd_dplane_handle_message
, NULL
);
624 stream_pulldown(bdc
->inbuf
);
625 thread_add_read(master
, bfd_dplane_read
, bdc
, bdc
->sock
, &bdc
->inbufev
);
628 static void _bfd_session_register_dplane(struct hash_bucket
*hb
, void *arg
)
630 struct bfd_session
*bs
= hb
->data
;
631 struct bfd_dplane_ctx
*bdc
= arg
;
636 /* Disable software session. */
637 bfd_session_disable(bs
);
639 /* Move session to data plane. */
640 _bfd_dplane_add_session(bdc
, bs
);
643 static struct bfd_dplane_ctx
*bfd_dplane_ctx_new(int sock
)
645 struct bfd_dplane_ctx
*bdc
;
647 bdc
= XCALLOC(MTYPE_BFDD_DPLANE_CTX
, sizeof(*bdc
));
650 bdc
->inbuf
= stream_new(BFD_DPLANE_CLIENT_BUF_SIZE
);
651 bdc
->outbuf
= stream_new(BFD_DPLANE_CLIENT_BUF_SIZE
);
653 /* If not socket ready, skip read and session registration. */
657 thread_add_read(master
, bfd_dplane_read
, bdc
, sock
, &bdc
->inbufev
);
659 /* Register all unattached sessions. */
660 bfd_key_iterate(_bfd_session_register_dplane
, bdc
);
665 static void _bfd_session_unregister_dplane(struct hash_bucket
*hb
, void *arg
)
667 struct bfd_session
*bs
= hb
->data
;
668 struct bfd_dplane_ctx
*bdc
= arg
;
675 /* Fallback to software. */
676 bfd_session_enable(bs
);
679 static void bfd_dplane_ctx_free(struct bfd_dplane_ctx
*bdc
)
681 if (bglobal
.debug_dplane
)
682 zlog_debug("%s: terminating data plane client %d", __func__
,
685 /* Client mode has special treatment. */
687 /* Disable connection event if any. */
688 THREAD_OFF(bdc
->connectev
);
690 /* Normal treatment on shutdown. */
691 if (bglobal
.bg_shutdown
)
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,
704 /* Remove from the list of attached data planes. */
705 TAILQ_REMOVE(&bglobal
.bg_dplaneq
, bdc
, entry
);
707 /* Detach all associated sessions. */
708 if (bglobal
.bg_shutdown
== false)
709 bfd_key_iterate(_bfd_session_unregister_dplane
, bdc
);
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
);
720 static void _bfd_dplane_session_fill(const struct bfd_session
*bs
,
721 struct bfddp_message
*msg
)
723 uint16_t msglen
= sizeof(msg
->header
) + sizeof(msg
->data
.session
);
725 /* Message header. */
726 msg
->header
.version
= BFD_DP_VERSION
;
727 msg
->header
.length
= ntohs(msglen
);
728 msg
->header
.type
= ntohs(DP_ADD_SESSION
);
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
;
736 msg
->data
.session
.ifindex
= htonl(bs
->ifp
->ifindex
);
737 strlcpy(msg
->data
.session
.ifname
, bs
->ifp
->name
,
738 sizeof(msg
->data
.session
.ifname
));
740 if (bs
->flags
& BFD_SESS_FLAG_MH
) {
741 msg
->data
.session
.flags
|= SESSION_MULTIHOP
;
742 msg
->data
.session
.ttl
= bs
->mh_ttl
;
744 msg
->data
.session
.ttl
= BFD_TTL_VAL
;
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
;
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
);
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
);
765 static int _bfd_dplane_add_session(struct bfd_dplane_ctx
*bdc
,
766 struct bfd_session
*bs
)
770 /* Associate session. */
773 /* Reset previous state. */
776 bs
->ses_state
= PTM_BFD_DOWN
;
778 /* Enqueue message to data plane client. */
779 rv
= bfd_dplane_update_session(bs
);
786 static void _bfd_dplane_update_session_counters(struct bfddp_message
*msg
,
789 struct bfd_session
*bs
= arg
;
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
);
802 * Send message to data plane requesting the session counters.
804 * \param bs the BFD session.
806 * \returns `0` on failure or the request id.
808 static uint16_t bfd_dplane_request_counters(const struct bfd_session
*bs
)
810 struct bfddp_message msg
= {};
811 size_t msglen
= sizeof(msg
.header
) + sizeof(msg
.data
.counters_req
);
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
));
819 /* Session to get counters. */
820 msg
.data
.counters_req
.lid
= htonl(bs
->discrs
.my_discr
);
822 /* If enqueue failed, let caller know. */
823 if (bfd_dplane_enqueue(bs
->bdc
, &msg
, msglen
) == -1)
827 bfd_dplane_flush(bs
->bdc
);
829 return ntohs(msg
.header
.id
);
833 * Data plane listening socket.
835 static void bfd_dplane_accept(struct thread
*t
)
837 struct bfd_global
*bg
= THREAD_ARG(t
);
838 struct bfd_dplane_ctx
*bdc
;
841 /* Accept new connection. */
842 sock
= accept(bg
->bg_dplane_sock
, NULL
, 0);
844 zlog_warn("%s: accept failed: %s", __func__
, strerror(errno
));
845 goto reschedule_and_return
;
848 /* Create and handle new connection. */
849 bdc
= bfd_dplane_ctx_new(sock
);
850 TAILQ_INSERT_TAIL(&bglobal
.bg_dplaneq
, bdc
, entry
);
852 if (bglobal
.debug_dplane
)
853 zlog_debug("%s: new data plane client connected", __func__
);
855 reschedule_and_return
:
856 thread_add_read(master
, bfd_dplane_accept
, bg
, bg
->bg_dplane_sock
,
857 &bglobal
.bg_dplane_sockev
);
861 * Data plane connecting socket.
863 static void _bfd_dplane_client_bootstrap(struct bfd_dplane_ctx
*bdc
)
865 bdc
->connecting
= false;
867 /* Clean up buffers. */
868 stream_reset(bdc
->inbuf
);
869 stream_reset(bdc
->outbuf
);
871 /* Ask for read notifications. */
872 thread_add_read(master
, bfd_dplane_read
, bdc
, bdc
->sock
, &bdc
->inbufev
);
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
);
879 static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx
*bdc
)
882 socklen_t rvlen
= sizeof(rv
);
884 /* Make sure `errno` is reset, then test `getsockopt` success. */
886 if (getsockopt(bdc
->sock
, SOL_SOCKET
, SO_ERROR
, &rv
, &rvlen
) == -1)
889 /* Connection successful. */
891 if (bglobal
.debug_dplane
)
892 zlog_debug("%s: connected to server: %d", __func__
,
895 _bfd_dplane_client_bootstrap(bdc
);
904 /* non error, wait more. */
908 zlog_warn("%s: connection failed: %s", __func__
,
910 bfd_dplane_ctx_free(bdc
);
915 static void bfd_dplane_client_connect(struct thread
*t
)
917 struct bfd_dplane_ctx
*bdc
= THREAD_ARG(t
);
919 socklen_t rvlen
= sizeof(rv
);
921 /* Allocate new socket. */
922 sock
= socket(bdc
->addr
.sa
.sa_family
, SOCK_STREAM
, 0);
924 zlog_warn("%s: failed to initialize socket: %s", __func__
,
926 goto reschedule_connect
;
929 /* Set non blocking socket. */
930 set_nonblocking(sock
);
932 /* Set 'no delay' (disables nagle algorithm) for IPv4/IPv6. */
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
));
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__
,
943 goto reschedule_connect
;
948 if (bglobal
.debug_dplane
)
949 zlog_debug("%s: server connection in progress: %d",
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
,
957 if (bglobal
.debug_dplane
)
958 zlog_debug("%s: server connection: %d", __func__
, sock
);
960 /* Otherwise just start accepting data. */
961 _bfd_dplane_client_bootstrap(bdc
);
965 THREAD_OFF(bdc
->inbufev
);
966 THREAD_OFF(bdc
->outbufev
);
968 thread_add_timer(master
, bfd_dplane_client_connect
, bdc
, 3,
972 static void bfd_dplane_client_init(const struct sockaddr
*sa
, socklen_t salen
)
974 struct bfd_dplane_ctx
*bdc
;
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
);
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
);
990 thread_add_timer(master
, bfd_dplane_client_connect
, bdc
, 0,
993 /* Insert into data plane lists. */
994 TAILQ_INSERT_TAIL(&bglobal
.bg_dplaneq
, bdc
, entry
);
998 * Termination phase of the distributed BFD infrastructure: free all allocated
1001 static int bfd_dplane_finish_late(void)
1003 struct bfd_dplane_ctx
*bdc
;
1005 if (bglobal
.debug_dplane
)
1006 zlog_debug("%s: terminating distributed BFD", __func__
);
1008 /* Free all data plane client contexts. */
1009 while ((bdc
= TAILQ_FIRST(&bglobal
.bg_dplaneq
)) != NULL
)
1010 bfd_dplane_ctx_free(bdc
);
1012 /* Cancel accept thread and close socket. */
1013 THREAD_OFF(bglobal
.bg_dplane_sockev
);
1014 close(bglobal
.bg_dplane_sock
);
1020 * Data plane exported functions.
1022 void bfd_dplane_init(const struct sockaddr
*sa
, socklen_t salen
, bool client
)
1026 zlog_info("initializing distributed BFD");
1028 /* Initialize queue header. */
1029 TAILQ_INIT(&bglobal
.bg_dplaneq
);
1031 /* Initialize listening socket. */
1032 bglobal
.bg_dplane_sock
= -1;
1034 /* Observe shutdown events. */
1035 hook_register(frr_fini
, bfd_dplane_finish_late
);
1037 /* Handle client mode. */
1039 bfd_dplane_client_init(sa
, salen
);
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.
1050 sock
= socket(sa
->sa_family
, SOCK_STREAM
, 0);
1052 zlog_warn("%s: failed to initialize socket: %s", __func__
,
1057 if (sockopt_reuseaddr(sock
) == -1) {
1058 zlog_warn("%s: failed to set reuseaddr: %s", __func__
,
1064 /* Handle UNIX socket: delete previous socket if any. */
1065 if (sa
->sa_family
== AF_UNIX
)
1066 unlink(((struct sockaddr_un
*)sa
)->sun_path
);
1068 if (bind(sock
, sa
, salen
) == -1) {
1069 zlog_warn("%s: failed to bind socket: %s", __func__
,
1075 if (listen(sock
, SOMAXCONN
) == -1) {
1076 zlog_warn("%s: failed to put socket on listen: %s", __func__
,
1082 bglobal
.bg_dplane_sock
= sock
;
1083 thread_add_read(master
, bfd_dplane_accept
, &bglobal
, sock
,
1084 &bglobal
.bg_dplane_sockev
);
1087 int bfd_dplane_add_session(struct bfd_session
*bs
)
1089 struct bfd_dplane_ctx
*bdc
;
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)
1100 int bfd_dplane_update_session(const struct bfd_session
*bs
)
1102 struct bfddp_message msg
= {};
1104 if (bs
->bdc
== NULL
)
1107 _bfd_dplane_session_fill(bs
, &msg
);
1109 /* Enqueue message to data plane client. */
1110 return bfd_dplane_enqueue(bs
->bdc
, &msg
, ntohs(msg
.header
.length
));
1113 int bfd_dplane_delete_session(struct bfd_session
*bs
)
1115 struct bfddp_message msg
= {};
1118 /* Not using data plane, just return success. */
1119 if (bs
->bdc
== NULL
)
1122 /* Fill most of the common fields. */
1123 _bfd_dplane_session_fill(bs
, &msg
);
1125 /* Change the message type. */
1126 msg
.header
.type
= ntohs(DP_DELETE_SESSION
);
1128 /* Enqueue message to data plane client. */
1129 rv
= bfd_dplane_enqueue(bs
->bdc
, &msg
, ntohs(msg
.header
.length
));
1131 /* Remove association. */
1140 void bfd_dplane_show_counters(struct vty
*vty
)
1142 struct bfd_dplane_ctx
*bdc
;
1144 #define SHOW_COUNTER(label, counter, formatter) \
1145 vty_out(vty, "%28s: %" formatter "\n", (label), (counter))
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
),
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");
1166 int bfd_dplane_update_session_counters(struct bfd_session
*bs
)
1171 /* If session is not using data plane, then just return success. */
1172 if (bs
->bdc
== NULL
)
1175 /* Make the request. */
1176 id
= bfd_dplane_request_counters(bs
);
1178 zlog_debug("%s: counters request failed", __func__
);
1182 /* Handle interruptions. */
1184 rv
= bfd_dplane_expect(bs
->bdc
, id
,
1185 _bfd_dplane_update_session_counters
, bs
);