From 850bb9b642a2f842808c53e83b275204777cae7f Mon Sep 17 00:00:00 2001 From: James R T Date: Thu, 4 May 2023 09:21:59 +0800 Subject: [PATCH] Add VXLAN support (#501) This patch adds a new PDU class to support VXLAN. Several VXLAN-related tests have also been added. Signed-off-by: James Raphael Tiovalen --- include/tins/pdu.h | 1 + include/tins/tins.h | 2 +- include/tins/vxlan.h | 98 ++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 1 + src/vxlan.cpp | 36 +++++++++++++++ tests/src/CMakeLists.txt | 1 + tests/src/vxlan_test.cpp | 90 ++++++++++++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 include/tins/vxlan.h create mode 100644 src/vxlan.cpp create mode 100644 tests/src/vxlan_test.cpp diff --git a/include/tins/pdu.h b/include/tins/pdu.h index f4f5153..dffcad6 100644 --- a/include/tins/pdu.h +++ b/include/tins/pdu.h @@ -180,6 +180,7 @@ public: PKTAP, MPLS, DOT11_CONTROL_TA, + VXLAN, UNKNOWN = 999, USER_DEFINED_PDU = 1000 }; diff --git a/include/tins/tins.h b/include/tins/tins.h index d3e1038..0ddbbc7 100644 --- a/include/tins/tins.h +++ b/include/tins/tins.h @@ -78,7 +78,7 @@ #include #include #include - #include +#include #endif // TINS_TINS_H diff --git a/include/tins/vxlan.h b/include/tins/vxlan.h new file mode 100644 index 0000000..52728cf --- /dev/null +++ b/include/tins/vxlan.h @@ -0,0 +1,98 @@ +#ifndef TINS_VXLAN_H +#define TINS_VXLAN_H + +#include +#include + +namespace Tins { + +/** + * \class VXLAN + * \brief Represents a VXLAN PDU. + * + * This class represents a VXLAN PDU. + * + * \sa RawPDU + */ +class TINS_API VXLAN : public PDU { +public: + /** + * \brief This PDU's flag. + */ + static const PDU::PDUType pdu_flag = PDU::VXLAN; + + /** + * \brief Constructs a VXLAN PDU. + * + * \param vni VXLAN Network Identifier. + */ + VXLAN(const small_uint<24> vni = 0); + + /** + * \brief Constructs a VXLAN object from a buffer and adds + * the Ethernet II PDU. + * + * \param buffer The buffer from which this PDU will be constructed. + * \param total_sz The total size of the buffer. + */ + VXLAN(const uint8_t* buffer, uint32_t total_sz); + + /** + * \brief Getter for the flags. + */ + uint8_t get_flags() const { return Endian::be_to_host(header_.flags) >> 24; } + + /** + * \brief Getter for the VNI. + */ + small_uint<24> get_vni() const { return Endian::be_to_host(header_.vni) >> 8; } + + /** + * \brief Setter for the flags. + * \param new_flags The new flags. + */ + void set_flags(uint8_t new_flags) { header_.flags = Endian::host_to_be(new_flags << 24); } + + /** + * \brief Setter for the VNI. + * \param new_vni The new VNI. + */ + void set_vni(small_uint<24> new_vni) { header_.vni = Endian::host_to_be(new_vni << 8); } + + /** + * \brief Returns the VXLAN frame's header length. + * + * This method overrides PDU::header_size. This size includes the + * payload and options size. + * + * \return An uint32_t with the header's size. + * \sa PDU::header_size + */ + uint32_t header_size() const { return sizeof(header_); } + + /** + * \brief Getter for the PDU's type. + * \sa PDU::pdu_type + */ + PDUType pdu_type() const { return pdu_flag; } + + /** + * \sa PDU::clone + */ + VXLAN *clone() const { return new VXLAN(*this); } + +private: + TINS_BEGIN_PACK + struct vxlan_header { + uint32_t flags; + uint32_t vni; + } TINS_END_PACK; + + void write_serialization(uint8_t* buffer, uint32_t total_sz); + + vxlan_header header_; +}; + +} // Tins + +#endif // TINS_VXLAN_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e07772e..360037c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ set(SOURCES utils/routing_utils.cpp utils/resolve_utils.cpp utils/pdu_utils.cpp + vxlan.cpp ) set(HEADERS diff --git a/src/vxlan.cpp b/src/vxlan.cpp new file mode 100644 index 0000000..7a0cdba --- /dev/null +++ b/src/vxlan.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +using Tins::Memory::InputMemoryStream; +using Tins::Memory::OutputMemoryStream; + +namespace Tins { + +VXLAN::VXLAN(const small_uint<24> vni) { + set_flags(8); + set_vni(vni); +} + +VXLAN::VXLAN(const uint8_t* buffer, uint32_t total_sz) { + InputMemoryStream stream(buffer, total_sz); + stream.read(header_); + // If there is any size left + if (stream) { + inner_pdu( + Internals::pdu_from_flag( + PDU::ETHERNET_II, + stream.pointer(), + stream.size() + ) + ); + } +} + +void VXLAN::write_serialization(uint8_t* buffer, uint32_t total_sz) { + OutputMemoryStream stream(buffer, total_sz); + stream.write(header_); +} + +} // Tins diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index 01b2472..3e3ea4b 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -70,6 +70,7 @@ CREATE_TEST(tcp) CREATE_TEST(tcp_ip) CREATE_TEST(udp) CREATE_TEST(utils) +CREATE_TEST(vxlan) IF(LIBTINS_ENABLE_PCAP) CREATE_TEST(offline_packet_filter) diff --git a/tests/src/vxlan_test.cpp b/tests/src/vxlan_test.cpp new file mode 100644 index 0000000..c6e5d80 --- /dev/null +++ b/tests/src/vxlan_test.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PACKET_SIZE 68ul + +using namespace std; +using namespace Tins; + +class VXLANTest : public testing::Test { +public: + static const uint8_t expected_packet[PACKET_SIZE]; + static const uint8_t flags; + static const uint16_t dport, sport, p_type; + static const small_uint<24> vni; + static const IP::address_type dst_ip, src_ip; + static const EthernetII::address_type dst_addr, src_addr; +}; + +const uint8_t VXLANTest::expected_packet[PACKET_SIZE] = { + 0x08, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0x00, + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const uint8_t VXLANTest::flags = 8; +const uint16_t VXLANTest::dport = 19627; +const uint16_t VXLANTest::sport = 4789; +const uint16_t VXLANTest::p_type = 0xd0ab; +const small_uint<24> VXLANTest::vni = 0xffffff; +const IP::address_type VXLANTest::dst_ip = IP::address_type{"2.2.2.2"}; +const IP::address_type VXLANTest::src_ip = IP::address_type{"1.1.1.1"}; +const EthernetII::address_type VXLANTest::dst_addr = EthernetII::address_type{"aa:bb:cc:dd:ee:ff"}; +const EthernetII::address_type VXLANTest::src_addr = EthernetII::address_type{"8a:8b:8c:8d:8e:8f"}; + +TEST_F(VXLANTest, Flags) { + auto const vxlan = VXLAN{}; + EXPECT_EQ(vxlan.get_flags(), flags); +} + +TEST_F(VXLANTest, VNI) { + auto const vxlan = VXLAN{vni}; + EXPECT_EQ(vxlan.get_vni(), vni); +} + +TEST_F(VXLANTest, Find) { + auto const pdu = VXLAN{} / EthernetII{dst_addr, src_addr}; + auto const eth = pdu.find_pdu(); + ASSERT_TRUE(eth != nullptr); + EXPECT_EQ(eth->dst_addr(), dst_addr); + EXPECT_EQ(eth->src_addr(), src_addr); +} + +TEST_F(VXLANTest, Serialize) { + auto eth = EthernetII{dst_addr, src_addr}; + eth.payload_type(p_type); + auto vxlan = VXLAN{vni}; + vxlan.inner_pdu(eth); + auto serialized = vxlan.serialize(); + ASSERT_EQ(serialized.size(), PACKET_SIZE); + EXPECT_TRUE(std::equal(serialized.begin(), serialized.end(), expected_packet)); +} + +TEST_F(VXLANTest, ConstructorFromBuffer) { + auto vxlan = VXLAN{expected_packet, PACKET_SIZE}; + EXPECT_EQ(vxlan.get_vni(), vni); + EXPECT_EQ(vxlan.get_flags(), flags); + auto const eth = vxlan.find_pdu(); + ASSERT_TRUE(eth != nullptr); + EXPECT_EQ(eth->dst_addr(), dst_addr); + EXPECT_EQ(eth->src_addr(), src_addr); +} + +TEST_F(VXLANTest, OuterUDP) { + auto pkt = IP{dst_ip, src_ip} / UDP{dport, sport} / VXLAN{expected_packet, PACKET_SIZE}; + auto const vxlan = pkt.find_pdu(); + ASSERT_TRUE(vxlan != nullptr); + EXPECT_EQ(vxlan->get_flags(), flags); + EXPECT_EQ(vxlan->get_vni(), vni); +}