diff --git a/include/icmpv6.h b/include/icmpv6.h index 3b1bf8a..c388a5b 100644 --- a/include/icmpv6.h +++ b/include/icmpv6.h @@ -31,6 +31,7 @@ #define TINS_ICMPV6_H #include +#include #include "macros.h" #include "pdu.h" #include "ipv6_address.h" @@ -148,6 +149,55 @@ public: */ typedef std::pair new_ha_info_type; + /** + * The type used to store the source/target address list options. + */ + typedef std::vector addr_list_type; + + /** + * The type used to store the nonce option data. + */ + typedef std::vector nonce_type; + + /** + * \brief The type used to store the link layer address option data. + */ + struct lladdr_type { + typedef std::vector address_type; + + uint8_t option_code; + address_type address; + + /** + * Constructor taking an option code and an address. + * + * \param option_code The option code. + * \param address The address to be stored. + */ + lladdr_type(uint8_t option_code = 0, + const address_type &address = address_type()) + : option_code(option_code), address(address) + { + + } + + /** + * \brief Constructor taking an option code and hwaddress_type. + * + * This is a helper constructor, since it'll be common to use + * hwaddress_type as the link layer address. + * + * \param option_code The option code. + * \param address The address to be stored. + */ + lladdr_type(uint8_t option_code, + const hwaddress_type &address) + : option_code(option_code), address(address.begin(), address.end()) + { + + } + }; + /** * Type type used to store the prefix information option data. */ @@ -183,6 +233,83 @@ public: } } TINS_END_PACK; + /** + * The type used to store the RSA signature option. + */ + struct rsa_sign_type { + typedef std::vector signature_type; + + uint8_t key_hash[16]; + signature_type signature; + + /** + * \brief Constructs a rsa_sign_type object. + * + * The first parameter must be a random access iterator + * which will be used to initialize the key_hash member. + * It is assumed that std::distance(hash, end_of_hash) >= 16. + * + * The second and third arguments indicate the start and end of + * the sequence which will be used to initialize the signature + * member. + * + * \param hash A random access iterator used to initialize the + * key_hash member. + * \param start A forward iterator pointing to the start of the + * sequence which will be used to initialize the signature member. + * \param end A forward iterator pointing to the end of the + * sequence used to initialize signature. + */ + template + rsa_sign_type(RAIterator hash, ForwardIterator start, ForwardIterator end) + : signature(start, end) + { + std::copy(hash, hash + sizeof(key_hash), key_hash); + } + + /** + * \brief Constructs a rsa_sign_type object. + * + * The first parameter must be a random access iterator + * which will be used to initialize the key_hash member. + * It is assumed that std::distance(hash, end_of_hash) >= 16. + * + * + * \param hash A random access iterator used to initialize the + * key_hash member. + * \param sign The signature to be set. + */ + template + rsa_sign_type(RAIterator hash, const signature_type &sign) + : signature(sign) + { + std::copy(hash, hash + sizeof(key_hash), key_hash); + } + + /** + * \brief Default constructs a rsa_sign_type. + * + * The key_hash member will be 0-initialized. + */ + rsa_sign_type() + { + std::fill(key_hash, key_hash + sizeof(key_hash), 0); + } + }; + + /** + * The type used to store IP address/preffix option. + */ + struct ip_prefix_type { + uint8_t option_code, prefix_len; + ipaddress_type address; + + ip_prefix_type(uint8_t option_code = 0, uint8_t prefix_len = 0, + const ipaddress_type &address = ipaddress_type()) + : option_code(option_code), prefix_len(prefix_len), address(address) + {} + }; + /** * \brief Constructs an ICMPv6 object. * @@ -582,6 +709,55 @@ public: */ void new_home_agent_info(const new_ha_info_type &value); + /** + * \brief Setter for the new source address list option. + * + * \param value The new source address list option data. + */ + void source_addr_list(const addr_list_type &value); + + /** + * \brief Setter for the new target address list option. + * + * \param value The new target address list option data. + */ + void target_addr_list(const addr_list_type &value); + + /** + * \brief Setter for the new RSA signature option. + * + * \param value The new RSA signature option data. + */ + void rsa_signature(const rsa_sign_type &value); + + /** + * \brief Setter for the new timestamp option. + * + * \param value The new timestamp option data. + */ + void timestamp(uint64_t value); + + /** + * \brief Setter for the new nonce option. + * + * \param value The new nonce option data. + */ + void nonce(const nonce_type &value); + + /** + * \brief Setter for the new IP address/prefix option. + * + * \param value The new IP address/prefix option data. + */ + void ip_prefix(const ip_prefix_type &value); + + /** + * \brief Setter for the new link layer address option. + * + * \param value The new link layer address option data. + */ + void link_layer_addr(lladdr_type value); + // Option getters /** @@ -647,6 +823,62 @@ public: * option is not found. */ new_ha_info_type new_home_agent_info() const; + + /** + * \brief Getter for the new source address list option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + addr_list_type source_addr_list() const; + + /** + * \brief Getter for the new target address list option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + addr_list_type target_addr_list() const; + + /** + * \brief Getter for the new RSA signature option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + rsa_sign_type rsa_signature() const; + + /** + * \brief Getter for the new timestamp option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + uint64_t timestamp() const; + + /** + * \brief Getter for the new nonce option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + nonce_type nonce() const; + + /** + * \brief Getter for the new IP address/prefix option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + ip_prefix_type ip_prefix() const; + + /** + * \brief Getter for the new link layer address option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + lladdr_type link_layer_addr() const; private: TINS_BEGIN_PACK struct icmp6hdr { @@ -697,6 +929,8 @@ private: bool has_options() const; uint8_t *write_option(const icmpv6_option &opt, uint8_t *buffer); void parse_options(const uint8_t *&buffer, uint32_t &total_sz); + void add_addr_list(uint8_t type, const addr_list_type &value); + addr_list_type search_addr_list(Options type) const; icmp6hdr _header; diff --git a/src/icmpv6.cpp b/src/icmpv6.cpp index c33c1b9..a82b1bb 100644 --- a/src/icmpv6.cpp +++ b/src/icmpv6.cpp @@ -289,6 +289,68 @@ void ICMPv6::new_home_agent_info(const new_ha_info_type &value) { add_option(icmpv6_option(HOME_AGENT_INFO, sizeof(buffer), buffer)); } +void ICMPv6::source_addr_list(const addr_list_type &value) { + add_addr_list(S_ADDRESS_LIST, value); +} + +void ICMPv6::target_addr_list(const addr_list_type &value) { + add_addr_list(T_ADDRESS_LIST, value); +} + +void ICMPv6::add_addr_list(uint8_t type, const addr_list_type &value) { + std::vector buffer; + buffer.reserve(value.size() + 6); + buffer.insert(buffer.end(), 6, 0); + for(addr_list_type::const_iterator it(value.begin()); it != value.end(); ++it) + buffer.insert(buffer.end(), it->begin(), it->end()); + add_option(icmpv6_option(type, buffer.begin(), buffer.end())); +} + +void ICMPv6::rsa_signature(const rsa_sign_type &value) { + uint32_t total_sz = 2 + sizeof(value.key_hash) + value.signature.size(); + uint8_t padding = 8 - total_sz % 8; + if(padding == 8) + padding = 0; + std::vector buffer; + buffer.reserve(total_sz + padding); + buffer.insert(buffer.end(), 2, 0); + buffer.insert(buffer.end(), value.key_hash, value.key_hash + sizeof(value.key_hash)); + buffer.insert(buffer.end(), value.signature.begin(), value.signature.end()); + buffer.insert(buffer.end(), padding, 0); + add_option(icmpv6_option(RSA_SIGN, buffer.begin(), buffer.end())); +} + +void ICMPv6::timestamp(uint64_t value) { + std::vector buffer(6 + sizeof(uint64_t)); + buffer.insert(buffer.begin(), 6, 0); + *((uint64_t*)&buffer[6]) = Endian::host_to_be(value); + add_option(icmpv6_option(TIMESTAMP, buffer.begin(), buffer.end())); +} + +void ICMPv6::nonce(const nonce_type &value) { + add_option(icmpv6_option(NONCE, value.begin(), value.end())); +} + +void ICMPv6::ip_prefix(const ip_prefix_type &value) { + std::vector buffer; + buffer.reserve(6 + ipaddress_type::address_size); + buffer.push_back(value.option_code); + buffer.push_back(value.prefix_len); + // reserved + buffer.insert(buffer.end(), sizeof(uint32_t), 0); + buffer.insert(buffer.end(), value.address.begin(), value.address.end()); + add_option(icmpv6_option(IP_PREFIX, buffer.begin(), buffer.end())); +} + +void ICMPv6::link_layer_addr(lladdr_type value) { + value.address.insert(value.address.begin(), value.option_code); + uint8_t padding = 8 - value.address.size() % 8; + if(padding == 8) + padding = 0; + value.address.insert(value.address.end(), padding, 0); + add_option(icmpv6_option(LINK_ADDRESS, value.address.begin(), value.address.end())); +} + // Option getters ICMPv6::hwaddress_type ICMPv6::source_link_layer_addr() const { @@ -354,5 +416,82 @@ ICMPv6::new_ha_info_type ICMPv6::new_home_agent_info() const { Endian::be_to_host(*(const uint16_t*)(opt->data_ptr() + sizeof(uint16_t) * 2)) ); } + +ICMPv6::addr_list_type ICMPv6::source_addr_list() const { + return search_addr_list(S_ADDRESS_LIST); +} + +ICMPv6::addr_list_type ICMPv6::target_addr_list() const { + return search_addr_list(T_ADDRESS_LIST); +} + +ICMPv6::addr_list_type ICMPv6::search_addr_list(Options type) const { + const icmpv6_option *opt = search_option(type); + if(!opt || opt->data_size() < 6 + ipaddress_type::address_size) + throw option_not_found(); + addr_list_type output; + const uint8_t *ptr = opt->data_ptr() + 6, *end = opt->data_ptr() + opt->data_size(); + while(ptr < end) { + if(ptr + ipaddress_type::address_size > end) + throw option_not_found(); + output.push_back(ipaddress_type(ptr)); + ptr += ipaddress_type::address_size; + } + return output; +} + +ICMPv6::rsa_sign_type ICMPv6::rsa_signature() const { + const icmpv6_option *opt = search_option(RSA_SIGN); + // 2 bytes reserved + at least 1 byte signature. + if(!opt || opt->data_size() < 2 + sizeof(rsa_sign_type::key_hash) + 1) + throw option_not_found(); + const uint8_t *ptr = opt->data_ptr() + 2; + rsa_sign_type output; + std::copy(ptr, ptr + sizeof(output.key_hash), output.key_hash); + ptr += sizeof(output.key_hash); + output.signature.assign(ptr, opt->data_ptr() + opt->data_size()); + return output; +} + +uint64_t ICMPv6::timestamp() const { + const icmpv6_option *opt = search_option(TIMESTAMP); + // 6 bytes reserved + if(!opt || opt->data_size() < 6 + sizeof(uint64_t)) + throw option_not_found(); + return Endian::be_to_host(*(uint64_t*)(opt->data_ptr() + 6)); +} + +ICMPv6::nonce_type ICMPv6::nonce() const { + const icmpv6_option *opt = search_option(NONCE); + // at least a one byte nonce(though it should be 8byte-padded as per the RFC). + if(!opt || opt->data_size() == 0) + throw option_not_found(); + return nonce_type(opt->data_ptr(), opt->data_ptr() + opt->data_size()); +} + +ICMPv6::ip_prefix_type ICMPv6::ip_prefix() const { + const icmpv6_option *opt = search_option(IP_PREFIX); + if(!opt || opt->data_size() != 6 + ipaddress_type::address_size) + throw option_not_found(); + const uint8_t *ptr = opt->data_ptr(); + ip_prefix_type output; + output.option_code = *ptr++; + output.prefix_len = *ptr++; + // skip padding + ptr += sizeof(uint32_t); + output.address = ipaddress_type(ptr); + return output; +} + +ICMPv6::lladdr_type ICMPv6::link_layer_addr() const { + const icmpv6_option *opt = search_option(LINK_ADDRESS); + // at least the option_code and 1 byte from the link layer address + if(!opt || opt->data_size() < 2) + throw option_not_found(); + const uint8_t *ptr = opt->data_ptr(); + lladdr_type output(*ptr++); + output.address.assign(ptr, opt->data_ptr() + opt->data_size()); + return output; +} } diff --git a/tests/src/icmpv6.cpp b/tests/src/icmpv6.cpp index 5b84bee..d3e1ce8 100644 --- a/tests/src/icmpv6.cpp +++ b/tests/src/icmpv6.cpp @@ -240,3 +240,79 @@ TEST_F(ICMPv6Test, NewHomeAgentInformation) { icmp.new_home_agent_info(data); EXPECT_EQ(icmp.new_home_agent_info(), data); } + +TEST_F(ICMPv6Test, SourceAddressList) { + ICMPv6 icmp; + ICMPv6::addr_list_type data; + data.push_back("827d:adae::1"); + data.push_back("2929:1234:fefe::2"); + icmp.source_addr_list(data); + EXPECT_EQ(icmp.source_addr_list(), data); +} + +TEST_F(ICMPv6Test, TargetAddressList) { + ICMPv6 icmp; + ICMPv6::addr_list_type data; + data.push_back("827d:adae::1"); + data.push_back("2929:1234:fefe::2"); + icmp.target_addr_list(data); + EXPECT_EQ(icmp.target_addr_list(), data); +} + +TEST_F(ICMPv6Test, RSASignature) { + ICMPv6 icmp; + ICMPv6::rsa_sign_type data, result; + // can i haz std::iota? + const uint8_t arr[16] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }; + std::copy(arr, arr + sizeof(arr), data.key_hash); + data.signature.push_back(12); + data.signature.push_back(15); + data.signature.push_back(221); + icmp.rsa_signature(data); + result = icmp.rsa_signature(); + EXPECT_TRUE(std::equal(data.key_hash, data.key_hash + sizeof(data.key_hash), result.key_hash)); + // There might be some padding(in this case, there is). + ASSERT_LE(data.signature.size(), result.signature.size()); + EXPECT_TRUE(std::equal(data.signature.begin(), data.signature.end(), result.signature.begin())); +} + +TEST_F(ICMPv6Test, Timestamp) { + ICMPv6 icmp; + icmp.timestamp(0x2837d6aaa231L); + EXPECT_EQ(icmp.timestamp(), 0x2837d6aaa231L); +} + +TEST_F(ICMPv6Test, Nonce) { + ICMPv6 icmp; + ICMPv6::nonce_type data; + data.push_back(22); + data.push_back(211); + data.push_back(67); + icmp.nonce(data); + EXPECT_EQ(icmp.nonce(), data); +} + +TEST_F(ICMPv6Test, IPPrefix) { + ICMPv6 icmp; + ICMPv6::ip_prefix_type data(67, 198, "ff00:0928:ddfa::"), output; + icmp.ip_prefix(data); + output = icmp.ip_prefix(); + EXPECT_EQ(output.option_code, data.option_code); + EXPECT_EQ(output.prefix_len, data.prefix_len); + EXPECT_EQ(output.address, data.address); +} + +TEST_F(ICMPv6Test, LinkLayerAddress) { + ICMPv6 icmp; + ICMPv6::lladdr_type data(67), output; + data.address.push_back(87); + data.address.push_back(22); + data.address.push_back(185); + icmp.link_layer_addr(data); + output = icmp.link_layer_addr(); + EXPECT_EQ(output.option_code, data.option_code); + ASSERT_LE(data.address.size(), output.address.size()); + EXPECT_TRUE(std::equal(data.address.begin(), data.address.end(), output.address.begin())); +}