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