From a938d2ecfdb59d19268d85fb33cf9508afcc0a63 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Thu, 22 Nov 2012 17:18:59 -0300 Subject: [PATCH] Added support for IPv6 extension headers. Specific headers getters and setters are not yet implemented. --- include/ip.h | 5 ++ include/ipv6.h | 63 ++++++++++++++++++++++++ include/pdu_option.h | 10 +++- src/ip.cpp | 4 ++ src/ipv6.cpp | 112 ++++++++++++++++++++++++++++++++++--------- tests/src/ipv6.cpp | 78 ++++++++++++++++++++++++++++-- 6 files changed, 245 insertions(+), 27 deletions(-) diff --git a/include/ip.h b/include/ip.h index c6e4731..b51bb20 100644 --- a/include/ip.h +++ b/include/ip.h @@ -404,6 +404,11 @@ namespace Tins { /** * \brief Searchs for an option that matchs the given flag. + * + * If the option is not found, a null pointer is returned. + * Deleting the returned pointer will result in undefined + * behaviour. + * * \param id The option identifier to be searched. */ const ip_option *search_option(option_identifier id) const; diff --git a/include/ipv6.h b/include/ipv6.h index 4858877..dbd5acd 100644 --- a/include/ipv6.h +++ b/include/ipv6.h @@ -30,9 +30,12 @@ #ifndef TINS_IPV6_h #define TINS_IPV6_h +#include +#include #include "pdu.h" #include "endianness.h" #include "small_uint.h" +#include "pdu_option.h" #include "ipv6_address.h" namespace Tins { @@ -49,6 +52,42 @@ public: * The type used to store addresses. */ typedef IPv6Address address_type; + + /** + * The type used to represent IPv6 extension headers. + */ + typedef PDUOption ipv6_ext_header; + + /** + * The type used to store the extension headers. + */ + typedef std::list headers_type; + + /** + * The values used to identify extension headers. + */ + enum ExtensionHeader { + HOP_BY_HOP = 0, + DESTINATION_ROUTING_OPTIONS = 60, + ROUTING = 43, + FRAGMENT = 44, + AUTHENTICATION = 51, + SECURITY_ENCAPSULATION = 50, + DESTINATION_OPTIONS = 60, + MOBILITY = 135, + NO_NEXT_HEADER = 59 + }; + + /** + * Exception thrown when an invalid extension header size is + * encountered. + */ + class header_size_error : public std::exception { + public: + const char *what() const throw() { + return "Not enough size for an extension header"; + } + }; /** * \brief Constructs an IPv6 object. @@ -223,8 +262,30 @@ public: * \sa PDU::send() */ void send(PacketSender &sender); + + /** + * Adds an extension header. + * + * \param header The extension header to be added. + */ + void add_ext_header(const ipv6_ext_header &header); + + /** + * \brief Searchs for an extension header that matchs the given + * flag. + * + * If the header is not found, a null pointer is returned. + * Deleting the returned pointer will result in undefined + * behaviour. + * + * \param id The header identifier to be searched. + */ + const ipv6_ext_header *search_option(ExtensionHeader id) const; private: void write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *parent); + void set_last_next_header(uint8_t value); + static uint8_t *write_header(const ipv6_ext_header &header, uint8_t *buffer); + static bool is_extension_header(uint8_t header_id); struct ipv6_header { #if TINS_IS_BIG_ENDIAN @@ -246,6 +307,8 @@ private: } __attribute__((packed)); ipv6_header _header; + headers_type ext_headers; + uint32_t headers_size; }; } diff --git a/include/pdu_option.h b/include/pdu_option.h index 9d8c6ff..36b4ae9 100644 --- a/include/pdu_option.h +++ b/include/pdu_option.h @@ -104,6 +104,14 @@ public: return option_; } + /** + * Sets this option's type + * \param opt The option type to be set. + */ + void option(option_type opt) { + option_ = opt; + } + /** * Retrieves this option's data. * @@ -111,7 +119,7 @@ public: * dereferencing the returned pointer will result in undefined * behaviour. * - * \return const value_type& containing this option's value. + * \return const data_type& containing this option's value. */ const data_type *data_ptr() const { return &*(++value_.begin()); diff --git a/src/ip.cpp b/src/ip.cpp index f105844..b052fa2 100644 --- a/src/ip.cpp +++ b/src/ip.cpp @@ -38,6 +38,7 @@ #include #endif #include "ip.h" +#include "ipv6.h" #include "tcp.h" #include "udp.h" #include "icmp.h" @@ -141,6 +142,9 @@ IP::IP(const uint8_t *buffer, uint32_t total_sz) case Constants::IP::PROTO_ICMP: inner_pdu(new Tins::ICMP(buffer, total_sz)); break; + case Constants::IP::PROTO_IPV6: + inner_pdu(new Tins::IPv6(buffer, total_sz)); + break; default: inner_pdu(new Tins::RawPDU(buffer, total_sz)); break; diff --git a/src/ipv6.cpp b/src/ipv6.cpp index f445a81..f76ebd4 100644 --- a/src/ipv6.cpp +++ b/src/ipv6.cpp @@ -4,6 +4,7 @@ #include #include #endif +#include //borrame #include "ipv6.h" #include "constants.h" #include "packet_sender.h" @@ -15,37 +16,68 @@ namespace Tins { -IPv6::IPv6(address_type ip_dst, address_type ip_src, PDU *child) { +IPv6::IPv6(address_type ip_dst, address_type ip_src, PDU *child) +: headers_size(0) +{ std::memset(&_header, 0, sizeof(_header)); version(6); dst_addr(ip_dst); src_addr(ip_src); } -IPv6::IPv6(const uint8_t *buffer, uint32_t total_sz) { +IPv6::IPv6(const uint8_t *buffer, uint32_t total_sz) +: headers_size(0) { if(total_sz < sizeof(_header)) throw std::runtime_error("Not enough size for an IPv6 PDU"); std::memcpy(&_header, buffer, sizeof(_header)); buffer += sizeof(_header); total_sz -= sizeof(_header); - if (total_sz) { - switch(_header.next_header) { - case Constants::IP::PROTO_TCP: - inner_pdu(new Tins::TCP(buffer, total_sz)); - break; - case Constants::IP::PROTO_UDP: - inner_pdu(new Tins::UDP(buffer, total_sz)); - break; - case Constants::IP::PROTO_ICMP: - inner_pdu(new Tins::ICMP(buffer, total_sz)); - break; - default: - inner_pdu(new Tins::RawPDU(buffer, total_sz)); - break; + uint8_t current_header = _header.next_header; + while(total_sz) { + if(is_extension_header(current_header)) { + if(total_sz < 8) + throw header_size_error(); + // every ext header is at least 8 bytes long + // minus one, from the next_header field. + uint8_t size = buffer[1] + 8; + // -1 -> next header identifier + if(total_sz < size) + throw header_size_error(); + // minus one, from the size field + add_ext_header( + ipv6_ext_header(buffer[0], size - sizeof(uint8_t)*2, buffer + 2) + ); + current_header = buffer[0]; + buffer += size; + total_sz -= size; + } + else { + switch(current_header) { + case Constants::IP::PROTO_TCP: + inner_pdu(new Tins::TCP(buffer, total_sz)); + break; + case Constants::IP::PROTO_UDP: + inner_pdu(new Tins::UDP(buffer, total_sz)); + break; + case Constants::IP::PROTO_ICMP: + inner_pdu(new Tins::ICMP(buffer, total_sz)); + break; + default: + inner_pdu(new Tins::RawPDU(buffer, total_sz)); + break; + } + total_sz = 0; } } } +bool IPv6::is_extension_header(uint8_t header_id) { + return header_id == HOP_BY_HOP || header_id == DESTINATION_ROUTING_OPTIONS + || header_id == ROUTING || header_id == FRAGMENT || header_id == AUTHENTICATION + || header_id == SECURITY_ENCAPSULATION || header_id == DESTINATION_OPTIONS + || header_id == MOBILITY || header_id == NO_NEXT_HEADER; +} + void IPv6::version(small_uint<4> new_version) { _header.version = new_version; } @@ -91,13 +123,15 @@ void IPv6::dst_addr(const address_type &new_dst_addr) { } uint32_t IPv6::header_size() const { - return sizeof(_header); + return sizeof(_header) + headers_size; } void IPv6::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *parent) { - assert(total_sz >= sizeof(_header)); + #ifdef DEBUG + assert(total_sz >= header_size()); + #endif if(inner_pdu()) { - uint8_t new_flag; + uint8_t new_flag = 0xff; switch(inner_pdu()->pdu_type()) { case PDU::IP: new_flag = Constants::IP::PROTO_IPIP; @@ -112,13 +146,17 @@ void IPv6::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *pa new_flag = Constants::IP::PROTO_ICMP; break; default: - // check for other protos - new_flag = 0xff; + break; }; - next_header(new_flag); + if(new_flag != 0xff) + set_last_next_header(new_flag); } payload_length(total_sz - sizeof(_header)); std::memcpy(buffer, &_header, sizeof(_header)); + buffer += sizeof(_header); + for(headers_type::const_iterator it = ext_headers.begin(); it != ext_headers.end(); ++it) { + buffer = write_header(*it, buffer); + } } void IPv6::send(PacketSender &sender) { @@ -133,4 +171,34 @@ void IPv6::send(PacketSender &sender) { sender.send_l3(*this, (struct sockaddr*)&link_addr, sizeof(link_addr), type); } +void IPv6::add_ext_header(const ipv6_ext_header &header) { + ext_headers.push_back(header); + headers_size += header.data_size() + sizeof(uint8_t) * 2; +} + +const IPv6::ipv6_ext_header *IPv6::search_option(ExtensionHeader id) const { + uint8_t current_header = _header.next_header; + headers_type::const_iterator it = ext_headers.begin(); + while(it != ext_headers.end() && current_header != id) { + current_header = it->option(); + ++it; + } + if(it == ext_headers.end()) + return 0; + return &*it; +} + +void IPv6::set_last_next_header(uint8_t value) { + if(ext_headers.empty()) + _header.next_header = value; + else + ext_headers.back().option(value); +} + +uint8_t *IPv6::write_header(const ipv6_ext_header &header, uint8_t *buffer) { + *buffer++ = header.option(); + *buffer++ = (header.data_size() > 8) ? (header.data_size() - 8) : 0; + return std::copy(header.data_ptr(), header.data_ptr() + header.data_size(), buffer); +} + } diff --git a/tests/src/ipv6.cpp b/tests/src/ipv6.cpp index fa80f64..d27b80c 100644 --- a/tests/src/ipv6.cpp +++ b/tests/src/ipv6.cpp @@ -6,6 +6,7 @@ #include "tcp.h" #include "udp.h" #include "icmp.h" +#include "rawpdu.h" #include "ipv6_address.h" #include "utils.h" @@ -14,12 +15,12 @@ using namespace Tins; class IPv6Test : public testing::Test { public: - static const uint8_t expected_packet[]; + static const uint8_t expected_packet1[], expected_packet2[]; - void test_equals(const IPv6 &ip1, const IPv6 &ip2); + void test_equals(IPv6 &ip1, IPv6 &ip2); }; -const uint8_t IPv6Test::expected_packet[] = { +const uint8_t IPv6Test::expected_packet1[] = { 'i', '\xa8', '\'', '4', '\x00', '(', '\x06', '@', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', @@ -31,6 +32,45 @@ const uint8_t IPv6Test::expected_packet[] = { '\x00', '\x00', '\x01', '\x03', '\x03', '\x07' }; +const uint8_t IPv6Test::expected_packet2[] = { + '`', '\x00', '\x00', '\x00', '\x00', '$', '\x00', '\x01', '\xfe', + '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x02', + '\xd0', '\t', '\xff', '\xfe', '\xe3', '\xe8', '\xde', '\xff', '\x02', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x16', ':', '\x00', '\x05', '\x02', + '\x00', '\x00', '\x01', '\x00', '\x8f', '\x00', 't', '\xfe', '\x00', + '\x00', '\x00', '\x01', '\x04', '\x00', '\x00', '\x00', '\xff', '\x02', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x01', '\xff', '\x98', '\x06', '\xe1' +}; + +void IPv6Test::test_equals(IPv6 &ip1, IPv6 &ip2) { + EXPECT_EQ(ip1.version(), ip2.version()); + EXPECT_EQ(ip1.traffic_class(), ip2.traffic_class()); + EXPECT_EQ(ip1.flow_label(), ip2.flow_label()); + EXPECT_EQ(ip1.payload_length(), ip2.payload_length()); + EXPECT_EQ(ip1.next_header(), ip2.next_header()); + EXPECT_EQ(ip1.hop_limit(), ip2.hop_limit()); + EXPECT_EQ(ip1.dst_addr(), ip2.dst_addr()); + EXPECT_EQ(ip1.src_addr(), ip2.src_addr()); + + EXPECT_EQ(bool(ip1.search_option(IPv6::HOP_BY_HOP)), bool(ip2.search_option(IPv6::HOP_BY_HOP))); + const IPv6::ipv6_ext_header *header1 = ip1.search_option(IPv6::HOP_BY_HOP), + *header2 = ip2.search_option(IPv6::HOP_BY_HOP); + if(header1 && header2) { + EXPECT_EQ(header1->data_size(), header2->data_size()); + } + + EXPECT_EQ(bool(ip1.inner_pdu()), bool(ip2.inner_pdu())); + + const RawPDU *raw1 = ip1.find_pdu(), *raw2 = ip2.find_pdu(); + ASSERT_EQ(bool(raw1), bool(raw2)); + + if(raw1) { + EXPECT_EQ(raw1->payload(), raw2->payload()); + } +} + TEST_F(IPv6Test, Constructor) { IPv6 ipv6("::1:2:3", "f0aa:beef::1"); EXPECT_EQ(ipv6.version(), 6); @@ -44,7 +84,7 @@ TEST_F(IPv6Test, Constructor) { } TEST_F(IPv6Test, ConstructorFromBuffer) { - IPv6 ipv6(expected_packet, sizeof(expected_packet)); + IPv6 ipv6(expected_packet1, sizeof(expected_packet1)); EXPECT_EQ(ipv6.version(), 6); EXPECT_EQ(ipv6.traffic_class(), 0x9a); EXPECT_EQ(ipv6.flow_label(), 0x82734); @@ -60,6 +100,36 @@ TEST_F(IPv6Test, ConstructorFromBuffer) { EXPECT_EQ(tcp->dport(), 80); } +// This one has a hop-by-hop ext header +TEST_F(IPv6Test, ConstructorFromBuffer2) { + IPv6 ipv6(expected_packet2, sizeof(expected_packet2)); + EXPECT_EQ(ipv6.version(), 6); + EXPECT_EQ(ipv6.traffic_class(), 0); + EXPECT_EQ(ipv6.flow_label(), 0); + EXPECT_EQ(ipv6.payload_length(), 36); + EXPECT_EQ(ipv6.next_header(), IPv6::HOP_BY_HOP); + EXPECT_EQ(ipv6.hop_limit(), 1); + EXPECT_EQ(ipv6.dst_addr(), "ff02::16"); + EXPECT_EQ(ipv6.src_addr(), "fe80::2d0:9ff:fee3:e8de"); + // This will have to be changed when ICMPv6 is implemented + RawPDU *pdu = ipv6.find_pdu(); + ASSERT_TRUE(pdu); + EXPECT_EQ(pdu->payload_size(), 28); + + const IPv6::ipv6_ext_header *header = ipv6.search_option(IPv6::HOP_BY_HOP); + ASSERT_TRUE(header); + EXPECT_EQ(header->data_size(), 6); +} + +TEST_F(IPv6Test, Serialize) { + IPv6 ip1(expected_packet2, sizeof(expected_packet2)); + IPv6::serialization_type buffer = ip1.serialize(); + ASSERT_EQ(buffer.size(), sizeof(expected_packet2)); + EXPECT_TRUE(std::equal(buffer.begin(), buffer.end(), expected_packet2)); + IPv6 ip2(&buffer[0], buffer.size()); + test_equals(ip1, ip2); +} + TEST_F(IPv6Test, Version) { IPv6 ipv6; ipv6.version(3);