diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h index cd841ca..7285a5b 100644 --- a/include/tins/exceptions.h +++ b/include/tins/exceptions.h @@ -204,6 +204,15 @@ public: option_payload_too_large() : exception_base("Option payload too large") { } }; +/** + * \brief Exception thrown when an IPv6 extension header is being + * created from invalid data + */ +class invalid_ipv6_extension_header : public exception_base { +public: + invalid_ipv6_extension_header() : exception_base("Invalid IPv6 extension header") { } +}; + /** * \brief Generic pcap error */ diff --git a/include/tins/ipv6.h b/include/tins/ipv6.h index 6cb5f78..7ffb4b3 100644 --- a/include/tins/ipv6.h +++ b/include/tins/ipv6.h @@ -73,6 +73,11 @@ public: */ typedef std::vector headers_type; + /** + * The type used to store an extension header option. + */ + typedef std::pair > header_option_type; + /** * The values used to identify extension headers. */ @@ -105,6 +110,46 @@ public: */ static metadata extract_metadata(const uint8_t *buffer, uint32_t total_sz); + /* + * \brief The type used to store Hop-By-Hop Extension Headers + */ + struct hop_by_hop_header { + std::vector options; + + static hop_by_hop_header from_extension_header(const ext_header& hdr); + }; + + /* + * \brief The type used to store Destination Routing Extension Headers + */ + struct destination_routing_header { + std::vector options; + + static destination_routing_header from_extension_header(const ext_header& hdr); + }; + + /** + * \brief The type used to store Routing Extension headers + */ + struct routing_header { + uint8_t routing_type; + uint8_t segments_left; + std::vector data; + + static routing_header from_extension_header(const ext_header& hdr); + }; + + /** + * \brief The type used to store Fragment Extension headers + */ + struct fragment_header { + uint16_t fragment_offset; + bool more_fragments; + uint32_t identification; + + static fragment_header from_extension_header(const ext_header& hdr); + }; + /** * \brief Constructs an IPv6 object. * @@ -365,6 +410,7 @@ private: static void write_header(const ext_header& header, Memory::OutputMemoryStream& stream); static bool is_extension_header(uint8_t header_id); static uint32_t get_padding_size(const ext_header& header); + static std::vector parse_header_options(const uint8_t* data, size_t size); TINS_BEGIN_PACK struct ipv6_header { diff --git a/src/ipv6.cpp b/src/ipv6.cpp index 49f6622..1c4c4c2 100644 --- a/src/ipv6.cpp +++ b/src/ipv6.cpp @@ -43,6 +43,7 @@ #include #include +using std::make_pair; using std::vector; using Tins::Memory::InputMemoryStream; @@ -69,6 +70,49 @@ PDU::metadata IPv6::extract_metadata(const uint8_t *buffer, uint32_t total_sz) { return metadata(header_size, pdu_flag, PDU::UNKNOWN); } +IPv6::hop_by_hop_header IPv6::hop_by_hop_header::from_extension_header(const ext_header& hdr) { + if (TINS_UNLIKELY(hdr.option() != HOP_BY_HOP)) { + throw invalid_ipv6_extension_header(); + } + hop_by_hop_header header; + header.options = parse_header_options(hdr.data_ptr(), hdr.data_size()); + return header; +} + +IPv6::destination_routing_header IPv6::destination_routing_header::from_extension_header(const ext_header& hdr) { + if (TINS_UNLIKELY(hdr.option() != DESTINATION_ROUTING_OPTIONS)) { + throw invalid_ipv6_extension_header(); + } + destination_routing_header header; + header.options = parse_header_options(hdr.data_ptr(), hdr.data_size()); + return header; +} + +IPv6::routing_header IPv6::routing_header::from_extension_header(const ext_header& hdr) { + if (TINS_UNLIKELY(hdr.option() != ROUTING)) { + throw invalid_ipv6_extension_header(); + } + Memory::InputMemoryStream stream(hdr.data_ptr(), hdr.data_size()); + routing_header header; + header.routing_type = stream.read(); + header.segments_left = stream.read(); + header.data.assign(stream.pointer(), stream.pointer() + stream.size()); + return header; +} + +IPv6::fragment_header IPv6::fragment_header::from_extension_header(const ext_header& hdr) { + if (TINS_UNLIKELY(hdr.option() != FRAGMENT)) { + throw invalid_ipv6_extension_header(); + } + Memory::InputMemoryStream stream(hdr.data_ptr(), hdr.data_size()); + fragment_header header; + uint16_t field = stream.read_be(); + header.fragment_offset = field >> 3; + header.more_fragments = field & 1; + header.identification = stream.read_be(); + return header; +} + IPv6::IPv6(address_type ip_dst, address_type ip_src, PDU* /*child*/) : header_(), next_header_() { version(6); @@ -169,6 +213,33 @@ uint32_t IPv6::get_padding_size(const ext_header& header) { return padding == 0 ? 0 : (8 - padding); } +vector IPv6::parse_header_options(const uint8_t* data, size_t size) { + Memory::InputMemoryStream stream(data, size); + vector options; + + while (stream.size() > 0) { + try { + uint8_t option = stream.read(); + if (option == PAD_1) { + continue; + } + uint8_t size = stream.read(); + if (size > stream.size()) { + throw invalid_ipv6_extension_header(); + } + if (option != PAD_N) { + options.push_back(make_pair(option, vector(stream.pointer(), + stream.pointer() + + size))); + } + stream.skip(size); + } catch (const malformed_packet&) { + throw invalid_ipv6_extension_header(); + } + } + return options; +} + void IPv6::version(small_uint<4> new_version) { header_.version = new_version; } diff --git a/tests/src/ipv6_test.cpp b/tests/src/ipv6_test.cpp index e317456..27fdcb4 100644 --- a/tests/src/ipv6_test.cpp +++ b/tests/src/ipv6_test.cpp @@ -366,3 +366,57 @@ TEST_F(IPv6Test, HopByHopPadding) { ipv6_header.add_header(IPv6::ExtensionHeader::HOP_BY_HOP); EXPECT_EQ(48UL, ipv6_header.serialize().size()); } + +TEST_F(IPv6Test, HopByHopParsing) { + EthernetII pkt(hop_by_hop_options, sizeof(hop_by_hop_options)); + IPv6& ipv6 = pkt.rfind_pdu(); + + const IPv6::headers_type& headers = ipv6.headers(); + EXPECT_EQ(1UL, headers.size()); + + const IPv6::ext_header* ext_header = ipv6.search_header(IPv6::ExtensionHeader::HOP_BY_HOP); + EXPECT_TRUE(ext_header != NULL); + + const IPv6::hop_by_hop_header hbh_header = IPv6::hop_by_hop_header::from_extension_header(*ext_header); + EXPECT_EQ(1UL, hbh_header.options.size()); + EXPECT_EQ(5, hbh_header.options[0].first); +} + +TEST_F(IPv6Test, HopByHopExtensionHeader) { + const uint8_t options[] = {42, 3, 0, 0, 0, 86, 0, 17, 2, 0, 0, 1, 2, 0, 0}; + IPv6::ext_header hdr(IPv6::HOP_BY_HOP, options, options + sizeof(options)); + + IPv6::hop_by_hop_header header = IPv6::hop_by_hop_header::from_extension_header(hdr); + EXPECT_EQ(3UL, header.options.size()); + EXPECT_EQ(42, header.options[0].first); + EXPECT_EQ(86, header.options[1].first); + EXPECT_EQ(17, header.options[2].first); +} + +TEST_F(IPv6Test, DestinationRoutingExtensionHeader) { + EXPECT_THROW(IPv6::destination_routing_header::from_extension_header(IPv6::HOP_BY_HOP), + invalid_ipv6_extension_header); + + IPv6::destination_routing_header header = IPv6::destination_routing_header::from_extension_header(IPv6::DESTINATION_ROUTING_OPTIONS); + EXPECT_EQ(0UL, header.options.size()); +} + +TEST_F(IPv6Test, RoutingExtensionHeader) { + const uint8_t header_data[] = {42, 17, 0, 0, 0, 0, 0}; + IPv6::ext_header hdr(IPv6::ROUTING, header_data, header_data + sizeof(header_data)); + + IPv6::routing_header header = IPv6::routing_header::from_extension_header(hdr); + EXPECT_EQ(42, header.routing_type); + EXPECT_EQ(17, header.segments_left); + EXPECT_EQ(5UL, header.data.size()); +} + +TEST_F(IPv6Test, FragmentExtensionHeader) { + const uint8_t header_data[] = {128, 1, 0, 0, 0, 42}; + IPv6::ext_header hdr(IPv6::FRAGMENT, header_data, header_data + sizeof(header_data)); + + IPv6::fragment_header header = IPv6::fragment_header::from_extension_header(hdr); + EXPECT_EQ(4096, header.fragment_offset); + EXPECT_TRUE(header.more_fragments); + EXPECT_EQ(42UL, header.identification); +}