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); +}