Skip to content

Add parsing of well known IPv6 extension headers #287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/tins/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ class option_payload_too_large : public exception_base {
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
*/
Expand Down
46 changes: 46 additions & 0 deletions include/tins/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class TINS_API IPv6 : public PDU {
*/
typedef std::vector<ext_header> headers_type;

/**
* The type used to store an extension header option.
*/
typedef std::pair<uint8_t, std::vector<uint8_t> > header_option_type;

/**
* The values used to identify extension headers.
*/
Expand Down Expand Up @@ -105,6 +110,46 @@ class TINS_API IPv6 : public PDU {
*/
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<header_option_type> 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<header_option_type> 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<uint8_t> 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.
*
Expand Down Expand Up @@ -365,6 +410,7 @@ class TINS_API IPv6 : public PDU {
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<header_option_type> parse_header_options(const uint8_t* data, size_t size);

TINS_BEGIN_PACK
struct ipv6_header {
Expand Down
70 changes: 70 additions & 0 deletions src/ipv6.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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<uint8_t>();
header.segments_left = stream.read<uint8_t>();
header.data = std::vector<uint8_t>(stream.pointer(), stream.pointer() + stream.size());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just do

header.data.assign(stream.pointer(), stream.pointer() + stream.size());
``

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course. Thanks for pointing this out.

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<uint16_t>();
header.fragment_offset = field >> 3;
header.more_fragments = field & 1;
header.identification = stream.read_be<uint32_t>();
return header;
}

IPv6::IPv6(address_type ip_dst, address_type ip_src, PDU* /*child*/)
: header_(), next_header_() {
version(6);
Expand Down Expand Up @@ -169,6 +212,33 @@ uint32_t IPv6::get_padding_size(const ext_header& header) {
return padding == 0 ? 0 : (8 - padding);
}

std::vector<IPv6::header_option_type> IPv6::parse_header_options(const uint8_t* data, size_t size)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to keep cpp files away from using "namespace specifiers" everywhere and instead just have using std::something at the top. In fact, std::vector is already pulled like this so you can just remove std:: here (there's also a couple other occurrences of this in the body of this method).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this brace up to the end of the previous line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Tried to keep the same coding style as the existing code, but I'm not surprised if I've missed a few places.

Memory::InputMemoryStream stream(data, size);
std::vector<header_option_type> options;

while (stream.size() > 0) {
try {
uint8_t option = stream.read<uint8_t>();
if (option == OptionType::PAD_1) {
continue;
}
uint8_t size = stream.read<uint8_t>();
if (size > stream.size()) {
throw invalid_ipv6_extension_header();
}
if (option != PAD_N) {
std::vector<uint8_t> value(stream.pointer(), stream.pointer() + size);
options.push_back(std::make_pair(option, value));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this sucks because I'm trying to keep the codebase both C++03 and C++11 compliant. If this was C++11 you would do:

options.push_back(make_pair(option, move(value)));

So you avoid creating an unnecessary copy of the vector. So in this case you can either have some ugly ifdef TINS_IS_CXX11 and use move, otherwise do what this is moving or you can just move this into a single expression:

options.push_back(make_pair(option, vector<uint8_t>(stream.pointer(), stream.pointer() + size)));

Possibly having to split that in multiple lines if it's too long.

Copy link
Contributor Author

@laudrup laudrup Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought a bit about that actually and was pretty sure the compiler could probably optimize the copy away, but I never tested that.

Anyway since this library has to support C++03, I much prefer your second option over ugly #ifdefs, so I'll go with that.

Well spotted.

}
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;
}
Expand Down
54 changes: 54 additions & 0 deletions tests/src/ipv6_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPv6>();

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