--- /dev/null
+TARGET := libfreeradius-bfd$(L)
+
+SOURCES := base.c encode.c
+
+TGT_PREREQS := libfreeradius-util$(L)
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/bfd/attrs.h
+ * @brief BFD attributes
+ *
+ * @copyright 20223 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(radius_attrs_h, "$Id$")
+
+#include <freeradius-devel/util/dict.h>
+#include <freeradius-devel/bfd/bfd.h>
+
+extern HIDDEN fr_dict_t const *dict_freeradius;
+extern HIDDEN fr_dict_t const *dict_bfd;
+
+extern HIDDEN fr_dict_attr_t const *attr_packet_type;
+extern HIDDEN fr_dict_attr_t const *attr_bfd_packet;
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/bfd/base.c
+ * @brief Functions to send/receive BFD packets.
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+RCSID("$Id$")
+
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "attrs.h"
+
+#include <freeradius-devel/io/pair.h>
+#include <freeradius-devel/util/net.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/util/udp.h>
+
+static uint32_t instance_count = 0;
+
+fr_dict_t const *dict_freeradius;
+fr_dict_t const *dict_bfd;
+
+extern fr_dict_autoload_t libfreeradius_bfd_dict[];
+fr_dict_autoload_t libfreeradius_bfd_dict[] = {
+ { .out = &dict_freeradius, .proto = "freeradius" },
+ { .out = &dict_bfd, .proto = "bfd" },
+ { NULL }
+};
+
+fr_dict_attr_t const *attr_packet_type;
+fr_dict_attr_t const *attr_bfd_packet;
+
+extern fr_dict_attr_autoload_t libfreeradius_bfd_dict_attr[];
+fr_dict_attr_autoload_t libfreeradius_bfd_dict_attr[] = {
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_bfd },
+ { .out = &attr_bfd_packet, .name = "Packet", .type = FR_TYPE_STRUCT, .dict = &dict_bfd },
+
+ { NULL }
+};
+
+
+int fr_bfd_init(void)
+{
+ if (instance_count > 0) {
+ instance_count++;
+ return 0;
+ }
+
+ if (fr_dict_autoload(libfreeradius_bfd_dict) < 0) return -1;
+ if (fr_dict_attr_autoload(libfreeradius_bfd_dict_attr) < 0) {
+ fr_dict_autofree(libfreeradius_bfd_dict);
+ return -1;
+ }
+
+ instance_count++;
+
+ return 0;
+}
+
+void fr_bfd_free(void)
+{
+ if (--instance_count > 0) return;
+
+ fr_dict_autofree(libfreeradius_bfd_dict);
+}
+
+extern fr_dict_protocol_t libfreeradius_bfd_dict_protocol;
+fr_dict_protocol_t libfreeradius_bfd_dict_protocol = {
+ .name = "bfd",
+ .default_type_size = 1,
+ .default_type_length = 1,
+};
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * $Id$
+ *
+ * @file protocols/bfd/bfd.h
+ * @brief Structures and prototypes for base BFD functionality.
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+#include <freeradius-devel/util/rand.h>
+#include <freeradius-devel/util/log.h>
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/util/md5.h>
+#include <freeradius-devel/util/sha1.h>
+#include <freeradius-devel/util/dbuff.h>
+
+typedef enum bfd_session_state_t {
+ BFD_STATE_ADMIN_DOWN = 0,
+ BFD_STATE_DOWN,
+ BFD_STATE_INIT,
+ BFD_STATE_UP
+} bfd_session_state_t;
+
+typedef enum bfd_diag_t {
+ BFD_DIAG_NONE = 0,
+ BFD_CTRL_EXPIRED,
+ BFD_ECHO_FAILED,
+ BFD_NEIGHBOR_DOWN,
+ BFD_FORWARD_PLANE_RESET,
+ BFD_PATH_DOWN,
+ BFD_CONCATENATED_PATH_DOWN,
+ BFD_ADMIN_DOWN,
+ BFD_REVERSE_CONCAT_PATH_DOWN
+} bfd_diag_t;
+
+typedef enum bfd_auth_type_t {
+ BFD_AUTH_RESERVED = 0,
+ BFD_AUTH_SIMPLE,
+ BFD_AUTH_KEYED_MD5,
+ BFD_AUTH_MET_KEYED_MD5,
+ BFD_AUTH_KEYED_SHA1,
+ BFD_AUTH_MET_KEYED_SHA1,
+} bfd_auth_type_t;
+
+#define BFD_AUTH_INVALID (BFD_AUTH_MET_KEYED_SHA1 + 1)
+
+typedef struct {
+ uint8_t auth_type;
+ uint8_t auth_len;
+ uint8_t key_id;
+} __attribute__ ((packed)) bfd_auth_basic_t;
+
+
+typedef struct {
+ uint8_t auth_type;
+ uint8_t auth_len;
+ uint8_t key_id;
+ uint8_t password[16];
+} __attribute__ ((packed)) bfd_auth_simple_t;
+
+typedef struct {
+ uint8_t auth_type;
+ uint8_t auth_len;
+ uint8_t key_id;
+ uint8_t reserved;
+ uint32_t sequence_no;
+ uint8_t digest[MD5_DIGEST_LENGTH];
+} __attribute__ ((packed)) bfd_auth_md5_t;
+
+typedef struct {
+ uint8_t auth_type;
+ uint8_t auth_len;
+ uint8_t key_id;
+ uint8_t reserved;
+ uint32_t sequence_no;
+ uint8_t digest[SHA1_DIGEST_LENGTH];
+} __attribute__ ((packed)) bfd_auth_sha1_t;
+
+typedef union bfd_auth_t {
+ union {
+ bfd_auth_basic_t basic;
+ bfd_auth_simple_t password;
+ bfd_auth_md5_t md5;
+ bfd_auth_sha1_t sha1;
+ };
+} __attribute__ ((packed)) bfd_auth_t;
+
+
+/*
+ * A packet
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+ unsigned int version : 3;
+ unsigned int diag : 5;
+ unsigned int state : 2;
+ unsigned int poll : 1;
+ unsigned int final : 1;
+ unsigned int control_plane_independent : 1;
+ unsigned int auth_present : 1;
+ unsigned int demand : 1;
+ unsigned int multipoint : 1;
+#else
+ unsigned int diag : 5;
+ unsigned int version : 3;
+
+ unsigned int multipoint : 1;
+ unsigned int demand : 1;
+ unsigned int auth_present : 1;
+ unsigned int control_plane_independent : 1;
+ unsigned int final : 1;
+ unsigned int poll : 1;
+ unsigned int state : 2;
+#endif
+ uint8_t detect_multi;
+ uint8_t length;
+ uint32_t my_disc;
+ uint32_t your_disc;
+ uint32_t desired_min_tx_interval;
+ uint32_t required_min_rx_interval;
+ uint32_t min_echo_rx_interval;
+ bfd_auth_t auth;
+} __attribute__ ((packed)) bfd_packet_t;
+
+#define FR_BFD_HEADER_LENGTH (24)
+
+typedef struct {
+ TALLOC_CTX *tmp_ctx; //!< for temporary things cleaned up during decoding
+ char const *secret; //!< shared secret. MUST be talloc'd
+} fr_bfd_ctx_t;
+
+ssize_t fr_bfd_encode(uint8_t *packet, size_t packet_len, uint8_t const *original,
+ char const *secret, size_t secret_len, fr_pair_list_t *vps);
+
+
+int fr_bfd_init(void);
+void fr_bfd_free(void);
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/bfd/encode.c
+ * @brief Functions to encode BFD packets
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/util/dbuff.h>
+#include <freeradius-devel/util/md5.h>
+#include <freeradius-devel/util/struct.h>
+#include <freeradius-devel/io/test_point.h>
+#include "attrs.h"
+
+extern fr_dict_attr_t const *attr_bfd_packet;
+
+/** Encodes the data portion of an attribute
+ *
+ * @return
+ * > 0, Length of the data portion.
+ * = 0, we could not encode anything, skip this attribute (and don't encode the header)
+ * unless it's one of a list of exceptions.
+ * < 0, How many additional bytes we'd need as a negative integer.
+ * PAIR_ENCODE_FATAL_ERROR - Abort encoding the packet.
+ * PAIR_ENCODE_SKIPPED - Unencodable value
+ */
+static ssize_t encode_value(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, UNUSED void *encode_ctx)
+{
+ ssize_t slen;
+ fr_pair_t const *vp = fr_dcursor_current(cursor);
+ fr_dict_attr_t const *da = da_stack->da[depth];
+// fr_bfd_ctx_t *packet_ctx = encode_ctx;
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+
+ PAIR_VERIFY(vp);
+ FR_PROTO_STACK_PRINT(da_stack, depth);
+
+ /*
+ * This has special requirements.
+ */
+ if ((vp->da->type == FR_TYPE_STRUCT) || (da->type == FR_TYPE_STRUCT)) {
+ slen = fr_struct_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value, NULL);
+ goto done;
+ }
+
+ /*
+ * If it's not a TLV, it should be a value type RFC
+ * attribute make sure that it is.
+ */
+ if (da_stack->da[depth + 1] != NULL) {
+ fr_strerror_printf("%s: Encoding value but not at top of stack", __FUNCTION__);
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ if (vp->da != da) {
+ fr_strerror_printf("%s: Top of stack does not match vp->da", __FUNCTION__);
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ if (fr_type_is_structural(da->type)) {
+ fr_strerror_printf("%s: Called with structural type %s", __FUNCTION__,
+ fr_type_to_str(da_stack->da[depth]->type));
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ slen = fr_value_box_to_network(&work_dbuff, &vp->data);
+
+done:
+ if (slen < 0) return slen;
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "%pP", vp);
+
+ vp = fr_dcursor_next(cursor);
+ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+/** Encode VPS into a BFD packet.
+ *
+ */
+ssize_t fr_bfd_encode(uint8_t *out, size_t outlen, UNUSED uint8_t const *original,
+ char const *secret, size_t secret_len, fr_pair_list_t *vps)
+{
+ ssize_t slen;
+ fr_bfd_ctx_t packet_ctx;
+ bfd_packet_t *packet;
+ fr_dcursor_t cursor;
+ fr_dbuff_t work_dbuff = FR_DBUFF_TMP(out, outlen);
+ fr_da_stack_t da_stack;
+
+ fr_pair_dcursor_init(&cursor, vps);
+
+ packet_ctx.secret = secret;
+
+ fr_proto_da_stack_build(&da_stack, attr_bfd_packet);
+ FR_PROTO_STACK_PRINT(&da_stack, 0);
+
+ slen = fr_struct_to_network(&work_dbuff, &da_stack, 0, &cursor, &packet_ctx, encode_value, NULL);
+ if (slen < 0) return slen;
+
+ /*
+ * The length is only 8 bits. :(
+ */
+ if (slen > UINT8_MAX) {
+ fr_strerror_const("Packet is larger than 255 octets");
+ return -1;
+ }
+
+ /*
+ * For various reasons the base BFD struct has "auth-type" as the last MEMBER, even if it's not
+ * always used. The struct encoder will fill it in with zeros, so we have to check for
+ * "auth_present" and then remove the last byte if there's no authentication stuff present.
+ */
+ packet = (bfd_packet_t *) out;
+
+ if (!packet->auth_present) {
+ if (slen > FR_BFD_HEADER_LENGTH) slen = FR_BFD_HEADER_LENGTH;
+
+ } else if (!secret || secret_len == 0) {
+ fr_strerror_const("Cannot sign packets without a secret");
+ return -1;
+
+ } else {
+
+#if 0
+ /*
+ * @todo - sign the packet with the chosen auth type
+ */
+ if (fr_bfd_sign(data, NULL, (uint8_t const *) secret, secret_len - 1) < 0) {
+ return -1;
+ }
+#endif
+ }
+
+ packet->length = slen;
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), slen, "BFD Packet");
+
+ return slen;
+}
+
+
+static int _test_ctx_free(UNUSED fr_bfd_ctx_t *ctx)
+{
+ fr_bfd_free();
+
+ return 0;
+}
+
+static int encode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+ fr_bfd_ctx_t *test_ctx;
+
+ if (fr_bfd_init() < 0) return -1;
+
+ test_ctx = talloc_zero(ctx, fr_bfd_ctx_t);
+ if (!test_ctx) return -1;
+
+ test_ctx->secret = talloc_strdup(test_ctx, "testing123");
+ talloc_set_destructor(test_ctx, _test_ctx_free);
+
+ *out = test_ctx;
+
+ return 0;
+}
+
+static ssize_t fr_bfd_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, void *proto_ctx)
+{
+ fr_bfd_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_bfd_ctx_t);
+ ssize_t slen;
+
+ /*
+ * @todo - pass in test_ctx to this function, so that we
+ * can leverage a consistent random number generator.
+ */
+ slen = fr_bfd_encode(data, data_len, NULL, test_ctx->secret, talloc_array_length(test_ctx->secret) - 1, vps);
+ if (slen <= 0) return slen;
+
+ return slen;
+}
+
+/*
+ * No one else should be using this.
+ */
+extern void *fr_bfd_next_encodable(fr_dlist_head_t *list, void *to_eval, void *uctx);
+
+/*
+ * Test points
+ */
+extern fr_test_point_proto_encode_t bfd_tp_encode_proto;
+fr_test_point_proto_encode_t bfd_tp_encode_proto = {
+ .test_ctx = encode_test_ctx,
+ .func = fr_bfd_encode_proto
+};
--- /dev/null
+# Test vectors for BFD Packets
+#
+# Copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+#
+proto bfd
+proto-dictionary bfd
+fuzzer-out bfd
+
+#
+# A basic BFD packet.
+#
+encode-proto Packet = { version = 1, diagnostic = none, state = up, poll = false, final = false, control-plane-independent = false, auth-present = false, demand = false, multipoint = false, detect-multi = 3, my-discriminator = 0xdeadbeef, your-discriminator = 0x21126809, desired-min-tx-interval = 31us, required-min-tx-interval = 127us, required-min-echo-interval = 255us }
+match 20 c0 03 18 de ad be ef 21 12 68 09 00 00 00 1f 00 00 00 7f 00 00 00 ff
+
+count
+match 5