From: Alan T. DeKok Date: Tue, 28 Feb 2023 20:31:16 +0000 (-0500) Subject: add encoder for BFD, and test cases. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cb1dac652275ffc115bcd58d76f9107ddce66eb9;p=thirdparty%2Ffreeradius-server.git add encoder for BFD, and test cases. There's no decoder, and no authentication tests yet --- diff --git a/src/protocols/bfd/all.mk b/src/protocols/bfd/all.mk new file mode 100644 index 00000000000..23b7badc4d9 --- /dev/null +++ b/src/protocols/bfd/all.mk @@ -0,0 +1,5 @@ +TARGET := libfreeradius-bfd$(L) + +SOURCES := base.c encode.c + +TGT_PREREQS := libfreeradius-util$(L) diff --git a/src/protocols/bfd/attrs.h b/src/protocols/bfd/attrs.h new file mode 100644 index 00000000000..46dd09e0147 --- /dev/null +++ b/src/protocols/bfd/attrs.h @@ -0,0 +1,34 @@ +#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 +#include + +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; diff --git a/src/protocols/bfd/base.c b/src/protocols/bfd/base.c new file mode 100644 index 00000000000..db350df4467 --- /dev/null +++ b/src/protocols/bfd/base.c @@ -0,0 +1,92 @@ +/* + * 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 +#include + +#include "attrs.h" + +#include +#include +#include +#include + +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, +}; diff --git a/src/protocols/bfd/bfd.h b/src/protocols/bfd/bfd.h new file mode 100644 index 00000000000..4f0462dc898 --- /dev/null +++ b/src/protocols/bfd/bfd.h @@ -0,0 +1,153 @@ +#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 +#include +#include +#include +#include +#include + +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); diff --git a/src/protocols/bfd/encode.c b/src/protocols/bfd/encode.c new file mode 100644 index 00000000000..ff6c3e41aef --- /dev/null +++ b/src/protocols/bfd/encode.c @@ -0,0 +1,215 @@ +/* + * 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 +#include +#include +#include +#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 +}; diff --git a/src/tests/unit/protocols/bfd/base.txt b/src/tests/unit/protocols/bfd/base.txt new file mode 100644 index 00000000000..754690ad217 --- /dev/null +++ b/src/tests/unit/protocols/bfd/base.txt @@ -0,0 +1,16 @@ +# 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