From cd1060656a8bc2a8202d06681be2984f079ff936 Mon Sep 17 00:00:00 2001 From: James Raphael Tiovalen Date: Fri, 14 Jun 2024 01:49:20 +0800 Subject: [PATCH] Add BFD support Add support for the Bidirectional Forwarding Detection (BFD) protocol as defined in RFC 5880. Some tests have also been added to ensure that the BFD PDU class functionalities are working as expected. Signed-off-by: James Raphael Tiovalen --- include/tins/bfd.h | 428 +++++++++++++++++++++++++++++++++++++++ include/tins/pdu.h | 1 + include/tins/tins.h | 1 + src/CMakeLists.txt | 2 + src/bfd.cpp | 307 ++++++++++++++++++++++++++++ tests/src/CMakeLists.txt | 1 + tests/src/bfd_test.cpp | 200 ++++++++++++++++++ 7 files changed, 940 insertions(+) create mode 100644 include/tins/bfd.h create mode 100644 src/bfd.cpp create mode 100644 tests/src/bfd_test.cpp diff --git a/include/tins/bfd.h b/include/tins/bfd.h new file mode 100644 index 00000000..04c48708 --- /dev/null +++ b/include/tins/bfd.h @@ -0,0 +1,428 @@ +#ifndef TINS_BFD_H +#define TINS_BFD_H + +#include +#include +#include +#include + +namespace Tins { + +/** + * \class BFD + * \brief Represents a BFD PDU. + * + * This class represents a BFD PDU. + * + * \sa RawPDU + */ +class TINS_API BFD : public PDU { +public: + /** + * \brief This PDU's flag. + */ + static const PDU::PDUType pdu_flag = PDU::BFD; + + /** + * Constants used by the BFD PDU class. + */ + static const size_t MAX_PASSWORD_SIZE = 16; + static const uint8_t MD5_DIGEST_SIZE = 16; + static const uint8_t SHA1_HASH_SIZE = 20; + + /** + * BFD Diagnostic Codes from RFC 5880 (and RFC 6428 for code 0x09). + * Diag values from 0x0A to 0x1F are unassigned and reserved for future use. + */ + enum Diagnostic { + NO_DIAGNOSTIC = 0x00, + CONTROL_DETECTION_TIME_EXPIRED = 0x01, + ECHO_FUNCTION_FAILED = 0x02, + NEIGHBOR_SIGNALED_SESSION_DOWN = 0x03, + FORWARDING_PLANE_RESET = 0x04, + PATH_DOWN = 0x05, + CONCATENATED_PATH_DOWN = 0x06, + ADMINISTRATIVELY_DOWN = 0x07, + REVERSE_CONCATENATED_PATH_DOWN = 0x08, + MISCONNECTIVITY_DEFECT = 0x09, + }; + + /** + * BFD State. + */ + enum State { + ADMIN_DOWN = 0x00, + DOWN = 0x01, + INIT = 0x02, + UP = 0x03, + }; + + /** + * BFD Authentication Types. + * Auth type values from 0x06 to 0xFF are unassigned and reserved for future use. + */ + enum AuthenticationType { + RESERVED = 0x00, + SIMPLE_PASSWORD = 0x01, + KEYED_MD5 = 0x02, + METICULOUS_KEYED_MD5 = 0x03, + KEYED_SHA1 = 0x04, + METICULOUS_KEYED_SHA1 = 0x05, + }; + + /** + * Default constructor. + */ + BFD(); + + /** + * \brief Constructs a BFD object from a buffer. + * + * \param data The buffer from which this PDU will be constructed. + * \param size The size of the data buffer. + */ + BFD(const uint8_t* data, uint32_t size); + + /** + * \brief Getter for the version. + */ + small_uint<3> version() const { return header_.version; } + + /** + * \brief Getter for the diagnostic code. + */ + enum Diagnostic diagnostic() const { return static_cast(header_.diagnostic); } + + /** + * \brief Getter for the state. + */ + enum State state() const { return static_cast(header_.state); } + + /** + * \brief Getter for the poll bit. + */ + bool poll() const { return header_.poll; } + + /** + * \brief Getter for the final bit. + */ + bool final() const { return header_.final; } + + /** + * \brief Getter for the control plane independent bit. + */ + bool control_plane_independent() const { return header_.control_plane_independent; } + + /** + * \brief Getter for the authentication present bit. + */ + bool authentication_present() const { return header_.authentication_present; } + + /** + * \brief Getter for the demand bit. + */ + bool demand() const { return header_.demand; } + + /** + * \brief Getter for the multipoint bit. + */ + bool multipoint() const { return header_.multipoint; } + + /** + * \brief Getter for the detection time multiplier. + */ + uint8_t detect_mult() const { return header_.detect_mult; } + + /** + * \brief Getter for the length. + */ + uint8_t length() const { return header_.length; } + + /** + * \brief Getter for the local discriminator ID. + */ + uint32_t my_discriminator() const { return Endian::be_to_host(header_.my_discriminator); } + + /** + * \brief Getter for the remote discriminator ID. + */ + uint32_t your_discriminator() const { return Endian::be_to_host(header_.your_discriminator); } + + /** + * \brief Getter for the minimum interval that the local system would like to use when transmitting BFD control packets. + */ + uint32_t desired_min_tx_interval() const { return Endian::be_to_host(header_.desired_min_tx_interval); } + + /** + * \brief Getter for the minimum interval between received BFD control packets. + */ + uint32_t required_min_rx_interval() const { return Endian::be_to_host(header_.required_min_rx_interval); } + + /** + * \brief Getter for the minimum interval between received BFD echo packets. + */ + uint32_t required_min_echo_rx_interval() const { return Endian::be_to_host(header_.required_min_echo_rx_interval); } + + /** + * \brief Getter for the authentication type. + */ + enum AuthenticationType auth_type() const { return static_cast(auth_header_.auth_type); } + + /** + * \brief Getter for the authentication length. + */ + uint8_t auth_len() const { return auth_header_.auth_len; } + + /** + * \brief Getter for the authentication key ID. + */ + uint8_t auth_key_id() const { return auth_header_.auth_key_id; } + + /** + * \brief Getter for the password. + */ + const byte_array& password() const; + + /** + * \brief Getter for the authentication sequence number. + */ + uint32_t auth_sequence_number() const; + + /** + * \brief Getter for the MD5 authentication value. + */ + const byte_array auth_md5_value() const; + + /** + * \brief Getter for the SHA1 authentication value. + */ + const byte_array auth_sha1_value() const; + + /** + * \brief Setter for the version. + * \param version The new version. + */ + void version(small_uint<3> version) { header_.version = version; } + + /** + * \brief Setter for the diagnostic code. + * \param diagnostic The new diagnostic code. + */ + void diagnostic(enum Diagnostic diagnostic) { header_.diagnostic = static_cast>(diagnostic); } + + /** + * \brief Setter for the state. + * \param state The new state. + */ + void state(enum State state) { header_.state = static_cast>(state); } + + /** + * \brief Setter for the poll bit. + * \param poll The new poll bit. + */ + void poll(bool poll) { header_.poll = poll; } + + /** + * \brief Setter for the final bit. + * \param final The new final bit. + */ + void final(bool final) { header_.final = final; } + + /** + * \brief Setter for the control plane independent bit. + * \param control_plane_independent The new control plane independent bit. + */ + void control_plane_independent(bool control_plane_independent) { header_.control_plane_independent = control_plane_independent; } + + /** + * \brief Setter for the authentication present bit. + * \param authentication_present The new authentication present bit. + */ + void authentication_present(bool authentication_present) { header_.authentication_present = authentication_present; } + + /** + * \brief Setter for the demand bit. + * \param demand The new demand bit. + */ + void demand(bool demand) { header_.demand = demand; } + + /** + * \brief Setter for the multipoint bit. + * \param multipoint The new multipoint bit. + */ + void multipoint(bool multipoint) { header_.multipoint = multipoint; } + + /** + * \brief Setter for the detection time multiplier. + * \param detect_mult The new detection time multiplier. + */ + void detect_mult(uint8_t detect_mult) { header_.detect_mult = detect_mult; } + + /** + * \brief Setter for the length. + * \param length The new length. + */ + void length(uint8_t length) { header_.length = length; } + + /** + * \brief Setter for the local discriminator ID. + * \param my_discriminator The new local discriminator ID. + */ + void my_discriminator(uint32_t my_discriminator) { header_.my_discriminator = Endian::host_to_be(my_discriminator); } + + /** + * \brief Setter for the remote discriminator ID. + * \param your_discriminator The new remote discriminator ID. + */ + void your_discriminator(uint32_t your_discriminator) { header_.your_discriminator = Endian::host_to_be(your_discriminator); } + + /** + * \brief Setter for the minimum interval that the local system would like to use when transmitting BFD control packets. + * \param desired_min_tx_interval The new desired minimum transmission interval. + */ + void desired_min_tx_interval(uint32_t desired_min_tx_interval) { header_.desired_min_tx_interval = Endian::host_to_be(desired_min_tx_interval); } + + /** + * \brief Setter for the minimum interval between received BFD control packets. + * \param required_min_rx_interval The new required minimum reception interval. + */ + void required_min_rx_interval(uint32_t required_min_rx_interval) { header_.required_min_rx_interval = Endian::host_to_be(required_min_rx_interval); } + + /** + * \brief Setter for the minimum interval between received BFD echo packets. + * \param required_min_echo_rx_interval The new required minimum echo reception interval. + */ + void required_min_echo_rx_interval(uint32_t required_min_echo_rx_interval) { header_.required_min_echo_rx_interval = Endian::host_to_be(required_min_echo_rx_interval); } + + /** + * \brief Setter for the authentication type. + * \param auth_type The new authentication type. + */ + void auth_type(enum AuthenticationType auth_type) { auth_header_.auth_type = static_cast(auth_type); } + + /** + * \brief Setter for the authentication length. + * \param auth_len The new authentication length. + */ + void auth_len(uint8_t auth_len) { auth_header_.auth_len = auth_len; } + + /** + * \brief Setter for the authentication key ID. + * \param auth_key_id The new authentication key ID. + */ + void auth_key_id(uint8_t auth_key_id) { auth_header_.auth_key_id = auth_key_id; } + + /** + * \brief Setter for the password. + * \param password The new password. + */ + void password(const byte_array& password); + + /** + * \brief Clear the password. + */ + void clear_password() { password_.clear(); } + + /** + * \brief Setter for the authentication sequence number. + * \param sequence_number The new authentication sequence number. + */ + void auth_sequence_number(uint32_t sequence_number); + + /** + * \brief Setter for the MD5 authentication value. + * \param auth_value The new MD5 authentication value. + */ + void auth_md5_value(const byte_array& auth_value); + + /** + * \brief Setter for the SHA1 authentication value. + * \param auth_value The new SHA1 authentication value. + */ + void auth_sha1_value(const byte_array& auth_value); + + /** + * \brief Returns the BFD frame's header length. + * + * This method overrides PDU::header_size. + * + * \return An uint32_t with the header's size. + * \sa PDU::header_size + */ + uint32_t header_size() const; + + /** + * \brief Getter for the PDU's type. + * \sa PDU::pdu_type + */ + PDUType pdu_type() const { return pdu_flag; } + + /** + * \sa PDU::clone + */ + BFD *clone() const { return new BFD(*this); } + +private: + TINS_BEGIN_PACK + struct bfd_header { + #if TINS_IS_BIG_ENDIAN + uint16_t version:3, + diagnostic:5, + state:2, + poll:1, + final:1, + control_plane_independent:1, + authentication_present:1, + demand:1, + multipoint:1; + #elif TINS_IS_LITTLE_ENDIAN + uint16_t diagnostic:5, + version:3, + multipoint:1, + demand:1, + authentication_present:1, + control_plane_independent:1, + final:1, + poll:1, + state:2; + #endif + uint8_t detect_mult; + uint8_t length; + uint32_t my_discriminator; + uint32_t your_discriminator; + uint32_t desired_min_tx_interval; + uint32_t required_min_rx_interval; + uint32_t required_min_echo_rx_interval; + } TINS_END_PACK; + + TINS_BEGIN_PACK + struct bfd_authentication_header { + uint8_t auth_type; + uint8_t auth_len; + uint8_t auth_key_id; + } TINS_END_PACK; + + TINS_BEGIN_PACK + struct bfd_md5_authentication_data { + uint32_t sequence_number; + uint8_t auth_value[MD5_DIGEST_SIZE]; + } TINS_END_PACK; + + TINS_BEGIN_PACK + struct bfd_sha1_authentication_data { + uint32_t sequence_number; + uint8_t auth_value[SHA1_HASH_SIZE]; + } TINS_END_PACK; + + void write_serialization(uint8_t* buffer, uint32_t size); + + bfd_header header_; + bfd_authentication_header auth_header_; + byte_array password_; + bfd_md5_authentication_data auth_data_md5_; + bfd_sha1_authentication_data auth_data_sha1_; +}; + +} // Tins + +#endif // TINS_BFD_H diff --git a/include/tins/pdu.h b/include/tins/pdu.h index 6402a129..2c36c767 100644 --- a/include/tins/pdu.h +++ b/include/tins/pdu.h @@ -182,6 +182,7 @@ class TINS_API PDU { DOT11_CONTROL_TA, VXLAN, RTP, + BFD, UNKNOWN = 999, USER_DEFINED_PDU = 1000 }; diff --git a/include/tins/tins.h b/include/tins/tins.h index 6526c50e..dc8722eb 100644 --- a/include/tins/tins.h +++ b/include/tins/tins.h @@ -81,5 +81,6 @@ #include #include #include +#include #endif // TINS_TINS_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8dc4fc82..65a684a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,7 @@ INCLUDE_DIRECTORIES(BEFORE set(SOURCES address_range.cpp arp.cpp + bfd.cpp bootp.cpp crypto.cpp detail/address_helpers.cpp @@ -80,6 +81,7 @@ set(SOURCES set(HEADERS ${LIBTINS_INCLUDE_DIR}/tins/address_range.h ${LIBTINS_INCLUDE_DIR}/tins/arp.h + ${LIBTINS_INCLUDE_DIR}/tins/bfd.h ${LIBTINS_INCLUDE_DIR}/tins/bootp.h ${LIBTINS_INCLUDE_DIR}/tins/handshake_capturer.h ${LIBTINS_INCLUDE_DIR}/tins/stp.h diff --git a/src/bfd.cpp b/src/bfd.cpp new file mode 100644 index 00000000..096f04c4 --- /dev/null +++ b/src/bfd.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include + +using std::copy; +using std::invalid_argument; +using std::logic_error; +using std::min; +using Tins::Memory::InputMemoryStream; +using Tins::Memory::OutputMemoryStream; + +namespace Tins { + +BFD::BFD() +: header_(), auth_header_(), auth_data_md5_(), auth_data_sha1_() { + version(1); + length(sizeof(header_)); +} + +BFD::BFD(const uint8_t* buffer, uint32_t total_sz) { + InputMemoryStream stream(buffer, total_sz); + stream.read(header_); + + if (authentication_present()) { + uint8_t candidate_auth_type = 0; + uint8_t candidate_auth_len = 0; + uint8_t candidate_auth_key_id = 0; + stream.read(candidate_auth_type); + stream.read(candidate_auth_len); + + BFD::AuthenticationType potential_auth_type = static_cast(candidate_auth_type); + + size_t auth_header_size = sizeof(auth_header_.auth_type) + sizeof(auth_header_.auth_len); + const uint8_t reserved = 0; + + switch (potential_auth_type) { + case BFD::AuthenticationType::RESERVED: + if (candidate_auth_len >= auth_header_size) { + auth_type(potential_auth_type); + auth_len(candidate_auth_len); + stream.skip(candidate_auth_len - auth_header_size); + } else { + throw malformed_packet(); + } + break; + + case BFD::AuthenticationType::SIMPLE_PASSWORD: + auth_header_size += sizeof(auth_header_.auth_key_id); + if ((candidate_auth_len < auth_header_size + 1) || (candidate_auth_len > auth_header_size + MAX_PASSWORD_SIZE)) { + throw malformed_packet(); + } + stream.read(candidate_auth_key_id); + auth_type(potential_auth_type); + auth_len(candidate_auth_len); + auth_key_id(candidate_auth_key_id); + for (size_t i = 0; i < min(candidate_auth_len - auth_header_size, MAX_PASSWORD_SIZE); ++i) { + uint8_t password_byte = 0; + stream.read(password_byte); + password_.push_back(password_byte); + } + break; + + case BFD::AuthenticationType::KEYED_MD5: + case BFD::AuthenticationType::METICULOUS_KEYED_MD5: + auth_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_md5_.sequence_number); + if (candidate_auth_len != auth_header_size + MD5_DIGEST_SIZE) { + throw malformed_packet(); + } + stream.read(candidate_auth_key_id); + auth_type(potential_auth_type); + auth_len(candidate_auth_len); + auth_key_id(candidate_auth_key_id); + stream.skip(sizeof(reserved)); + stream.read(auth_data_md5_); + break; + + case BFD::AuthenticationType::KEYED_SHA1: + case BFD::AuthenticationType::METICULOUS_KEYED_SHA1: + auth_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_sha1_.sequence_number); + if (candidate_auth_len != auth_header_size + SHA1_HASH_SIZE) { + throw malformed_packet(); + } + stream.read(candidate_auth_key_id); + auth_type(potential_auth_type); + auth_len(candidate_auth_len); + auth_key_id(candidate_auth_key_id); + stream.skip(sizeof(reserved)); + stream.read(auth_data_sha1_); + break; + + default: + throw malformed_packet(); + } + } + + if (length() != header_size()) { + throw malformed_packet(); + } +} + +uint32_t BFD::header_size() const { + uint32_t bfd_header_size = sizeof(header_); + + if (authentication_present()) { + const size_t auth_header_size = sizeof(auth_header_.auth_type) + sizeof(auth_header_.auth_len); + const uint8_t reserved = 0; + bfd_header_size += auth_header_size; + + switch (auth_type()) { + case BFD::AuthenticationType::RESERVED: + if (auth_len() >= auth_header_size) { + bfd_header_size += auth_len() - auth_header_size; + } else { + throw malformed_packet(); + } + break; + + case BFD::AuthenticationType::SIMPLE_PASSWORD: + bfd_header_size += sizeof(auth_header_.auth_key_id) + password_.size(); + break; + + case BFD::AuthenticationType::KEYED_MD5: + case BFD::AuthenticationType::METICULOUS_KEYED_MD5: + bfd_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_md5_.sequence_number) + sizeof(auth_data_md5_.auth_value); + break; + + case BFD::AuthenticationType::KEYED_SHA1: + case BFD::AuthenticationType::METICULOUS_KEYED_SHA1: + bfd_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_sha1_.sequence_number) + sizeof(auth_data_sha1_.auth_value); + break; + + default: + throw logic_error("Unknown authentication type"); + } + } + + return bfd_header_size; +} + +const byte_array& BFD::password() const { + if (auth_type() != BFD::AuthenticationType::SIMPLE_PASSWORD) { + throw logic_error("Authentication type is not SIMPLE_PASSWORD"); + } + + return password_; +} + +void BFD::password(const byte_array& password) { + if (auth_type() != BFD::AuthenticationType::SIMPLE_PASSWORD) { + throw logic_error("Authentication type is not SIMPLE_PASSWORD"); + } + + if (password.size() > MAX_PASSWORD_SIZE) { + throw invalid_argument("Password is too long"); + } else if (password.empty()) { + throw invalid_argument("Password is empty"); + } + + password_ = password; +} + +uint32_t BFD::auth_sequence_number() const { + switch (auth_type()) { + case BFD::AuthenticationType::KEYED_MD5: + case BFD::AuthenticationType::METICULOUS_KEYED_MD5: + return Endian::be_to_host(auth_data_md5_.sequence_number); + + case BFD::AuthenticationType::KEYED_SHA1: + case BFD::AuthenticationType::METICULOUS_KEYED_SHA1: + return Endian::be_to_host(auth_data_sha1_.sequence_number); + + default: + throw logic_error("Authentication type does not have a sequence number"); + } +} + +void BFD::auth_sequence_number(uint32_t sequence_number) { + switch (auth_type()) { + case BFD::AuthenticationType::KEYED_MD5: + case BFD::AuthenticationType::METICULOUS_KEYED_MD5: + auth_data_md5_.sequence_number = Endian::host_to_be(sequence_number); + break; + + case BFD::AuthenticationType::KEYED_SHA1: + case BFD::AuthenticationType::METICULOUS_KEYED_SHA1: + auth_data_sha1_.sequence_number = Endian::host_to_be(sequence_number); + break; + + default: + throw logic_error("Authentication type does not have a sequence number"); + } +} + +const byte_array BFD::auth_md5_value() const { + if (auth_type() != BFD::AuthenticationType::KEYED_MD5 && auth_type() != BFD::AuthenticationType::METICULOUS_KEYED_MD5) { + throw logic_error("Authentication type is not MD5-based"); + } + + return byte_array(auth_data_md5_.auth_value, auth_data_md5_.auth_value + MD5_DIGEST_SIZE); +} + +const byte_array BFD::auth_sha1_value() const { + if (auth_type() != BFD::AuthenticationType::KEYED_SHA1 && auth_type() != BFD::AuthenticationType::METICULOUS_KEYED_SHA1) { + throw logic_error("Authentication type is not SHA1-based"); + } + + return byte_array(auth_data_sha1_.auth_value, auth_data_sha1_.auth_value + SHA1_HASH_SIZE); +} + +void BFD::auth_md5_value(const byte_array& auth_value) { + if (auth_type() != BFD::AuthenticationType::KEYED_MD5 && auth_type() != BFD::AuthenticationType::METICULOUS_KEYED_MD5) { + throw logic_error("Authentication type is not MD5-based"); + } + + if (auth_value.size() != MD5_DIGEST_SIZE) { + throw invalid_argument("Invalid MD5 authentication value size"); + } + + copy(auth_value.begin(), auth_value.end(), auth_data_md5_.auth_value); +} + +void BFD::auth_sha1_value(const byte_array& auth_value) { + if (auth_type() != BFD::AuthenticationType::KEYED_SHA1 && auth_type() != BFD::AuthenticationType::METICULOUS_KEYED_SHA1) { + throw logic_error("Authentication type is not SHA1-based"); + } + + if (auth_value.size() != SHA1_HASH_SIZE) { + throw invalid_argument("Invalid SHA1 authentication value size"); + } + + copy(auth_value.begin(), auth_value.end(), auth_data_sha1_.auth_value); +} + +void BFD::write_serialization(uint8_t* buffer, uint32_t total_sz) { + OutputMemoryStream stream(buffer, total_sz); + size_t packet_length = sizeof(header_); + stream.write(header_); + + if (authentication_present()) { + size_t auth_header_size = sizeof(auth_header_.auth_type) + sizeof(auth_header_.auth_len); + const uint8_t reserved = 0; + + switch (auth_type()) { + case BFD::AuthenticationType::RESERVED: + if (auth_len() >= auth_header_size) { + stream.write(auth_header_.auth_type); + stream.write(auth_header_.auth_len); + for (size_t i = 0; i < auth_len() - auth_header_size; ++i) { + stream.write(reserved); + } + auth_header_size += auth_len() - auth_header_size; + } else { + throw logic_error("Invalid authentication section length"); + } + break; + + case BFD::AuthenticationType::SIMPLE_PASSWORD: + if (password_.empty()) { + throw logic_error("Password is empty"); + } + auth_header_size += sizeof(auth_header_.auth_key_id) + password_.size(); + if (auth_len() != auth_header_size) { + throw logic_error("Invalid authentication section length"); + } + stream.write(auth_header_); + for (size_t i = 0; i < password_.size(); ++i) { + stream.write(password_[i]); + } + break; + + case BFD::AuthenticationType::KEYED_MD5: + case BFD::AuthenticationType::METICULOUS_KEYED_MD5: + auth_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_md5_.sequence_number) + sizeof(auth_data_md5_.auth_value); + if (auth_len() != auth_header_size) { + throw logic_error("Invalid authentication section length"); + } + stream.write(auth_header_); + stream.write(reserved); + stream.write(auth_data_md5_); + break; + + case BFD::AuthenticationType::KEYED_SHA1: + case BFD::AuthenticationType::METICULOUS_KEYED_SHA1: + auth_header_size += sizeof(auth_header_.auth_key_id) + sizeof(reserved) + sizeof(auth_data_sha1_.sequence_number) + sizeof(auth_data_sha1_.auth_value); + if (auth_len() != auth_header_size) { + throw logic_error("Invalid authentication section length"); + } + stream.write(auth_header_); + stream.write(reserved); + stream.write(auth_data_sha1_); + break; + + default: + throw logic_error("Unknown authentication type"); + } + + packet_length += auth_header_size; + } + + if (length() != packet_length) { + throw logic_error("Invalid BFD packet length"); + } +} + +} // Tins diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index aa0c3777..37ed8442 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -37,6 +37,7 @@ ENDMACRO() CREATE_TEST(address_range) CREATE_TEST(allocators) CREATE_TEST(arp) +CREATE_TEST(bfd) CREATE_TEST(dhcp) CREATE_TEST(dhcpv6) CREATE_TEST(dns) diff --git a/tests/src/bfd_test.cpp b/tests/src/bfd_test.cpp new file mode 100644 index 00000000..d7b1c66e --- /dev/null +++ b/tests/src/bfd_test.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PACKET_SIZE 52ul +#define DEFAULT_HEADER_SIZE 24ul + +using namespace std; +using namespace Tins; + +class BFDTest : public testing::Test { +public: + static const uint8_t expected_packet[PACKET_SIZE]; + static const small_uint<3> version; + static const BFD::Diagnostic diagnostic; + static const BFD::State state; + static const uint8_t detect_mult; + static const uint32_t my_discriminator, your_discriminator; + static const uint32_t desired_min_tx_interval, required_min_rx_interval, required_min_echo_rx_interval; + static const BFD::AuthenticationType auth_type; + static const uint8_t sha1_auth_len, auth_key_id; + static const uint32_t auth_seq_num; + static const byte_array auth_sha1_value; + static const byte_array password1, password2; + static const uint16_t dport, sport; + static const IP::address_type dst_ip, src_ip; + static const EthernetII::address_type dst_addr, src_addr; +}; + +const uint8_t BFDTest::expected_packet[PACKET_SIZE] = { + 0x20, 0xff, 0x05, 0x34, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x0c, + 0x05, 0x1c, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x3d, 0xde, 0x2a, 0x34, + 0xef, 0x6c, 0xaf, 0xf9, + 0xa4, 0x05, 0x87, 0xab, + 0x41, 0x23, 0x87, 0x53, + 0x21, 0xcd, 0x99, 0xce, +}; + +const small_uint<3> BFDTest::version = 1; +const BFD::Diagnostic BFDTest::diagnostic = BFD::Diagnostic::NO_DIAGNOSTIC; +const BFD::State BFDTest::state = BFD::State::UP; +const uint8_t BFDTest::detect_mult = 5; +const uint32_t BFDTest::my_discriminator = 1; +const uint32_t BFDTest::your_discriminator = 0; +const uint32_t BFDTest::desired_min_tx_interval = 0xff; +const uint32_t BFDTest::required_min_rx_interval = 0xff; +const uint32_t BFDTest::required_min_echo_rx_interval = 0x0c; +const BFD::AuthenticationType BFDTest::auth_type = BFD::AuthenticationType::METICULOUS_KEYED_SHA1; +const uint8_t BFDTest::sha1_auth_len = 28; +const uint8_t BFDTest::auth_key_id = 1; +const uint32_t BFDTest::auth_seq_num = 1; +const byte_array BFDTest::auth_sha1_value = {0x3d, 0xde, 0x2a, 0x34, 0xef, 0x6c, 0xaf, 0xf9, 0xa4, 0x05, 0x87, 0xab, 0x41, 0x23, 0x87, 0x53, 0x21, 0xcd, 0x99, 0xce}; +const byte_array BFDTest::password1 = {0x41, 0x42, 0x43, 0x44, 0x45}; +const byte_array BFDTest::password2 = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c}; +const uint16_t BFDTest::dport = 3784; +const uint16_t BFDTest::sport = 49152; +const IP::address_type BFDTest::dst_ip = IP::address_type{"2.2.2.2"}; +const IP::address_type BFDTest::src_ip = IP::address_type{"1.1.1.1"}; +const EthernetII::address_type BFDTest::dst_addr = EthernetII::address_type{"aa:bb:cc:dd:ee:ff"}; +const EthernetII::address_type BFDTest::src_addr = EthernetII::address_type{"8a:8b:8c:8d:8e:8f"}; + +TEST_F(BFDTest, DefaultConstructor) { + auto const bfd = BFD{}; + EXPECT_EQ(bfd.version(), version); + EXPECT_EQ(bfd.diagnostic(), BFD::Diagnostic::NO_DIAGNOSTIC); + EXPECT_EQ(bfd.state(), BFD::State::ADMIN_DOWN); + EXPECT_EQ(bfd.poll(), false); + EXPECT_EQ(bfd.final(), false); + EXPECT_EQ(bfd.control_plane_independent(), false); + EXPECT_EQ(bfd.authentication_present(), false); + EXPECT_EQ(bfd.demand(), false); + EXPECT_EQ(bfd.multipoint(), false); + EXPECT_EQ(bfd.detect_mult(), 0); + EXPECT_EQ(bfd.length(), DEFAULT_HEADER_SIZE); + EXPECT_EQ(bfd.my_discriminator(), (unsigned int)0); + EXPECT_EQ(bfd.your_discriminator(), (unsigned int)0); + EXPECT_EQ(bfd.desired_min_tx_interval(), (unsigned int)0); + EXPECT_EQ(bfd.required_min_rx_interval(), (unsigned int)0); + EXPECT_EQ(bfd.required_min_echo_rx_interval(), (unsigned int)0); + EXPECT_EQ(bfd.auth_type(), BFD::AuthenticationType::RESERVED); + EXPECT_EQ(bfd.auth_len(), 0); + EXPECT_EQ(bfd.auth_key_id(), 0); +} + +TEST_F(BFDTest, Serialize) { + auto bfd = BFD{}; + bfd.version(version); + bfd.diagnostic(diagnostic); + bfd.state(state); + bfd.poll(true); + bfd.final(true); + bfd.control_plane_independent(true); + bfd.authentication_present(true); + bfd.demand(true); + bfd.multipoint(true); + bfd.detect_mult(detect_mult); + bfd.length(PACKET_SIZE); + bfd.my_discriminator(my_discriminator); + bfd.your_discriminator(your_discriminator); + bfd.desired_min_tx_interval(desired_min_tx_interval); + bfd.required_min_rx_interval(required_min_rx_interval); + bfd.required_min_echo_rx_interval(required_min_echo_rx_interval); + bfd.auth_type(auth_type); + bfd.auth_len(sha1_auth_len); + bfd.auth_key_id(auth_key_id); + bfd.auth_sequence_number(auth_seq_num); + bfd.auth_sha1_value(auth_sha1_value); + + auto const serialized = bfd.serialize(); + EXPECT_EQ(serialized.size(), PACKET_SIZE); + EXPECT_TRUE(equal(serialized.begin(), serialized.end(), expected_packet)); +} + +TEST_F(BFDTest, ConstructorFromBuffer) { + auto const bfd = BFD{expected_packet, PACKET_SIZE}; + EXPECT_EQ(bfd.version(), version); + EXPECT_EQ(bfd.diagnostic(), diagnostic); + EXPECT_EQ(bfd.state(), state); + EXPECT_EQ(bfd.poll(), true); + EXPECT_EQ(bfd.final(), true); + EXPECT_EQ(bfd.control_plane_independent(), true); + EXPECT_EQ(bfd.authentication_present(), true); + EXPECT_EQ(bfd.demand(), true); + EXPECT_EQ(bfd.multipoint(), true); + EXPECT_EQ(bfd.detect_mult(), detect_mult); + EXPECT_EQ(bfd.length(), PACKET_SIZE); + EXPECT_EQ(bfd.my_discriminator(), my_discriminator); + EXPECT_EQ(bfd.your_discriminator(), your_discriminator); + EXPECT_EQ(bfd.desired_min_tx_interval(), desired_min_tx_interval); + EXPECT_EQ(bfd.required_min_rx_interval(), required_min_rx_interval); + EXPECT_EQ(bfd.required_min_echo_rx_interval(), required_min_echo_rx_interval); + EXPECT_EQ(bfd.auth_type(), auth_type); + EXPECT_EQ(bfd.auth_len(), sha1_auth_len); + EXPECT_EQ(bfd.auth_key_id(), auth_key_id); + EXPECT_EQ(bfd.auth_sequence_number(), auth_seq_num); + EXPECT_EQ(bfd.auth_sha1_value(), auth_sha1_value); +} + +TEST_F(BFDTest, ChangePassword) { + auto bfd = BFD{}; + EXPECT_THROW(bfd.password(byte_array{}), logic_error); + + bfd.auth_type(BFD::AuthenticationType::SIMPLE_PASSWORD); + + bfd.password(password1); + auto const set_password1 = bfd.password(); + EXPECT_EQ(set_password1, password1); + + bfd.password(password2); + auto const set_password2 = bfd.password(); + EXPECT_EQ(set_password2, password2); + + EXPECT_THROW(bfd.password(byte_array{}), invalid_argument); + + EXPECT_THROW(bfd.password(byte_array(BFD::MAX_PASSWORD_SIZE + 1, 0x41)), invalid_argument); +} + +TEST_F(BFDTest, InvalidAuthValue) { + auto bfd = BFD{}; + + bfd.auth_type(BFD::AuthenticationType::KEYED_MD5); + EXPECT_THROW(bfd.auth_md5_value(byte_array{}), invalid_argument); + + bfd.auth_type(BFD::AuthenticationType::METICULOUS_KEYED_MD5); + EXPECT_THROW(bfd.auth_md5_value(byte_array{}), invalid_argument); + + bfd.auth_type(BFD::AuthenticationType::KEYED_SHA1); + EXPECT_THROW(bfd.auth_sha1_value(byte_array{}), invalid_argument); + + bfd.auth_type(BFD::AuthenticationType::METICULOUS_KEYED_SHA1); + EXPECT_THROW(bfd.auth_sha1_value(byte_array{}), invalid_argument); +} + +TEST_F(BFDTest, OuterUDP) { + auto pkt = EthernetII{dst_addr, src_addr} / IP{dst_ip, src_ip} / UDP{dport, sport} / BFD{expected_packet, PACKET_SIZE}; + + auto udp = pkt.find_pdu(); + ASSERT_TRUE(udp != nullptr); + EXPECT_EQ(udp->dport(), dport); + EXPECT_EQ(udp->sport(), sport); + + auto bfd = udp->find_pdu(); + ASSERT_TRUE(bfd != nullptr); + EXPECT_EQ(bfd->header_size(), PACKET_SIZE); + EXPECT_EQ(bfd->size(), PACKET_SIZE); +}