diff --git a/include/icmpv6.h b/include/icmpv6.h index 558986a..bad4601 100644 --- a/include/icmpv6.h +++ b/include/icmpv6.h @@ -36,6 +36,7 @@ #include "pdu_option.h" #include "endianness.h" #include "small_uint.h" +#include "hw_address.h" namespace Tins { /** @@ -120,7 +121,12 @@ public: /** * The type used to store addresses. */ - typedef IPv6Address address_type; + typedef IPv6Address ipaddress_type; + + /** + * The type used to store addresses. + */ + typedef HWAddress<6> hwaddress_type; /** * The type used to represent ICMPv6 options. @@ -132,6 +138,49 @@ public: */ typedef std::list options_type; + /** + * \brief The type used to store the new home agent information + * option data. + * + * The first member contains the home agent preference field, while + * the second one contains the home agent lifetime. + */ + typedef std::pair new_ha_info_type; + + /** + * Type type used to store the prefix information option data. + */ + struct prefix_info_type { + uint8_t prefix_len; + #if TINS_IS_LITTLE_ENDIAN + uint8_t reserved1:6, + A:1, + L:1; + #else + uint8_t L:1, + A:1, + reserved1:6; + #endif + uint32_t valid_lifetime, + preferred_lifetime, + reserved2; + uint8_t prefix[ipaddress_type::address_size]; + + prefix_info_type(uint8_t prefix_len=0, small_uint<1> A=0, small_uint<1> L=0, + uint32_t valid_lifetime=0, uint32_t preferred_lifetime=0, + const ipaddress_type &addr = ipaddress_type()) + : prefix_len(prefix_len), reserved1(0), + #if TINS_IS_LITTLE_ENDIAN + A(A), L(L), + #else + L(L), A(A), + #endif + valid_lifetime(valid_lifetime), preferred_lifetime(preferred_lifetime) + { + addr.copy(prefix); + } + } __attribute__((packed)); + /** * \brief Constructs an ICMPv6 object. * @@ -285,9 +334,17 @@ public: * \brief Getter for the target address field. * \return The stored target address field value. */ - const address_type &target_addr() const { + const ipaddress_type &target_addr() const { return _target_address; } + + /** + * \brief Getter for the destination address field. + * \return The stored destination address field value. + */ + const ipaddress_type &dest_addr() const { + return _dest_address; + } // Setters @@ -379,7 +436,13 @@ public: * \brief Setter for the target address field. * \param new_target_addr The new target address field value. */ - void target_addr(const address_type &new_target_addr); + void target_addr(const ipaddress_type &new_target_addr); + + /** + * \brief Setter for the destination address field. + * \param new_dest_addr The new destination address field value. + */ + void dest_addr(const ipaddress_type &new_dest_addr); /** * \brief Setter for the reachable_time field. @@ -411,6 +474,15 @@ public: type() == REDIRECT; } + /** + * \brief Checks whether this ICMPv6 object has a target_addr field. + * + * This depends on the type field. + */ + bool has_dest_addr() const { + return type() == REDIRECT; + } + /** * \brief Adds an ICMPv6 option. * @@ -446,6 +518,133 @@ public: ICMPv6 *clone() const { return new ICMPv6(*this); } + + // Option setters + + /** + * \brief Setter for the source link layer address option. + * + * \param addr The source link layer address. + */ + void source_link_layer_addr(const hwaddress_type &addr); + + /** + * \brief Setter for the target link layer address option. + * + * \param addr The target link layer address. + */ + void target_link_layer_addr(const hwaddress_type &addr); + + /** + * \brief Setter for the prefix information option. + * + * \param info The prefix information. + */ + void prefix_info(prefix_info_type info); + + /** + * \brief Setter for the redirect header option. + * + * This method appends the 6 reserved bytes and inserts the + * necessary padding at the end. + * + * \param data The redirect header option data. + */ + void redirect_header(PDU::serialization_type data); + + /** + * \brief Setter for the MTU option. + * + * \param value The MTU option data. + */ + void mtu(uint32_t value); + + /** + * \brief Setter for the shortcut limit option. + * + * \param value The shortcut limit option data. + */ + void shortcut_limit(uint8_t value); + + /** + * \brief Setter for the new advertisement interval option. + * + * \param value The new advertisement interval option data. + */ + void new_advert_interval(uint32_t value); + + /** + * \brief Setter for the new home agent information option. + * + * \param value The new home agent information option data. + */ + void new_home_agent_info(const new_ha_info_type &value); + + // Option getters + + /** + * \brief Getter for the source link layer address option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + hwaddress_type source_link_layer_addr() const; + + /** + * \brief Getter for the target link layer address option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + hwaddress_type target_link_layer_addr() const; + + /** + * \brief Getter for the prefix information option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + prefix_info_type prefix_info() const; + + /** + * \brief Getter for the redirect header option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + PDU::serialization_type redirect_header() const; + + /** + * \brief Getter for the MTU option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + uint32_t mtu() const; + + /** + * \brief Getter for the shortcut limit option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + uint8_t shortcut_limit() const; + + /** + * \brief Getter for the new advertisement interval option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + uint32_t new_advert_interval() const; + + /** + * \brief Getter for the new home agent information option. + * + * This method will throw an option_not_found exception if the + * option is not found. + */ + new_ha_info_type new_home_agent_info() const; private: struct icmp6hdr { uint8_t type; @@ -498,7 +697,7 @@ private: icmp6hdr _header; - address_type _target_address; + ipaddress_type _target_address, _dest_address; options_type _options; uint32_t _options_size; uint32_t reach_time, retrans_timer; diff --git a/src/icmpv6.cpp b/src/icmpv6.cpp index 083b3d9..7ab9cc7 100644 --- a/src/icmpv6.cpp +++ b/src/icmpv6.cpp @@ -52,12 +52,19 @@ ICMPv6::ICMPv6(const uint8_t *buffer, uint32_t total_sz) std::memcpy(&_header, buffer, sizeof(_header)); buffer += sizeof(_header); total_sz -= sizeof(_header); - if(type() == NEIGHBOUR_SOLICIT || type() == NEIGHBOUR_ADVERT || type() == REDIRECT) { - if(total_sz < address_type::address_size) + if(has_target_addr()) { + if(total_sz < ipaddress_type::address_size) throw std::runtime_error("Not enough size for the target address"); target_addr(buffer); - buffer += address_type::address_size; - total_sz -= address_type::address_size; + buffer += ipaddress_type::address_size; + total_sz -= ipaddress_type::address_size; + } + if(has_dest_addr()) { + if(total_sz < ipaddress_type::address_size) + throw std::runtime_error("Not enough size for the destination address"); + dest_addr(buffer); + buffer += ipaddress_type::address_size; + total_sz -= ipaddress_type::address_size; } if(type() == ROUTER_ADVERT) { if(total_sz < sizeof(uint32_t) * 2) @@ -150,12 +157,18 @@ void ICMPv6::retransmit_timer(uint32_t new_retrans_timer) { retrans_timer = Endian::host_to_be(new_retrans_timer); } -void ICMPv6::target_addr(const address_type &new_target_addr) { +void ICMPv6::target_addr(const ipaddress_type &new_target_addr) { _target_address = new_target_addr; } +void ICMPv6::dest_addr(const ipaddress_type &new_dest_addr) { + _dest_address = new_dest_addr; +} + uint32_t ICMPv6::header_size() const { - return sizeof(_header) + _options_size; + return sizeof(_header) + _options_size + + (has_target_addr() ? ipaddress_type::address_size : 0) + + (has_dest_addr() ? ipaddress_type::address_size : 0); } void ICMPv6::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *parent) { @@ -166,6 +179,14 @@ void ICMPv6::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU * std::memcpy(buffer, &_header, sizeof(_header)); buffer += sizeof(_header); total_sz -= sizeof(_header); + if(has_target_addr()) { + buffer = _target_address.copy(buffer); + total_sz -= sizeof(ipaddress_type::address_size); + } + if(has_dest_addr()) { + buffer = _dest_address.copy(buffer); + total_sz -= sizeof(ipaddress_type::address_size); + } for(options_type::const_iterator it = _options.begin(); it != _options.end(); ++it) { #ifdef TINS_DEBUG assert(total_sz >= it->data_size() + sizeof(uint8_t) * 2); @@ -202,7 +223,7 @@ void ICMPv6::add_option(const icmpv6_option &option) { uint8_t *ICMPv6::write_option(const icmpv6_option &opt, uint8_t *buffer) { *buffer++ = opt.option(); - *buffer++ = opt.data_size(); + *buffer++ = (opt.data_size() + sizeof(uint8_t) * 2) / 8; return std::copy(opt.data_ptr(), opt.data_ptr() + opt.data_size(), buffer); } @@ -213,5 +234,125 @@ const ICMPv6::icmpv6_option *ICMPv6::search_option(Options id) const { } return 0; } + +// Option setters + +void ICMPv6::source_link_layer_addr(const hwaddress_type &addr) { + add_option(icmpv6_option(SOURCE_ADDRESS, addr.begin(), addr.end())); +} + +void ICMPv6::target_link_layer_addr(const hwaddress_type &addr) { + add_option(icmpv6_option(TARGET_ADDRESS, addr.begin(), addr.end())); +} + +void ICMPv6::prefix_info(prefix_info_type info) { + info.valid_lifetime = Endian::host_to_be(info.valid_lifetime); + info.preferred_lifetime = Endian::host_to_be(info.preferred_lifetime); + add_option( + icmpv6_option(PREFIX_INFO, sizeof(prefix_info_type), (const uint8_t*)&info) + ); +} + +void ICMPv6::redirect_header(PDU::serialization_type data) { + // Reserved fields + data.insert(data.begin(), 6, 0); + // Padding(if necessary) + uint8_t padding = 8 - (data.size() + sizeof(uint8_t) * 2) % 8; + if(padding == 8) + padding = 0; + data.insert(data.end(), padding, 0); + add_option(icmpv6_option(REDIRECT_HEADER, data.begin(), data.end())); +} + +void ICMPv6::mtu(uint32_t value) { + uint8_t buffer[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + *((uint32_t*)(buffer + sizeof(uint16_t))) = Endian::host_to_be(value); + add_option(icmpv6_option(MTU, sizeof(buffer), buffer)); +} + +void ICMPv6::shortcut_limit(uint8_t value) { + uint8_t buffer[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + buffer[0] = value; + add_option(icmpv6_option(NBMA_SHORT_LIMIT, sizeof(buffer), buffer)); +} + +void ICMPv6::new_advert_interval(uint32_t value) { + uint8_t buffer[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + *((uint32_t*)(buffer + sizeof(uint16_t))) = Endian::host_to_be(value); + add_option(icmpv6_option(ADVERT_INTERVAL, sizeof(buffer), buffer)); +} + +void ICMPv6::new_home_agent_info(const new_ha_info_type &value) { + uint8_t buffer[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + *((uint16_t*)(buffer + sizeof(uint16_t))) = Endian::host_to_be(value.first); + *((uint16_t*)(buffer + sizeof(uint16_t) * 2)) = Endian::host_to_be(value.second); + add_option(icmpv6_option(HOME_AGENT_INFO, sizeof(buffer), buffer)); +} + +// Option getters + +ICMPv6::hwaddress_type ICMPv6::source_link_layer_addr() const { + const icmpv6_option *opt = search_option(SOURCE_ADDRESS); + if(!opt || opt->data_size() != hwaddress_type::address_size) + throw option_not_found(); + return hwaddress_type(opt->data_ptr()); +} + +ICMPv6::hwaddress_type ICMPv6::target_link_layer_addr() const { + const icmpv6_option *opt = search_option(TARGET_ADDRESS); + if(!opt || opt->data_size() != hwaddress_type::address_size) + throw option_not_found(); + return hwaddress_type(opt->data_ptr()); +} + +ICMPv6::prefix_info_type ICMPv6::prefix_info() const { + const icmpv6_option *opt = search_option(PREFIX_INFO); + if(!opt || opt->data_size() != sizeof(prefix_info_type)) + throw option_not_found(); + prefix_info_type output; + std::memcpy(&output, opt->data_ptr(), sizeof(prefix_info_type)); + output.valid_lifetime = Endian::be_to_host(output.valid_lifetime); + output.preferred_lifetime = Endian::be_to_host(output.preferred_lifetime); + return output; +} + +PDU::serialization_type ICMPv6::redirect_header() const { + const icmpv6_option *opt = search_option(REDIRECT_HEADER); + if(!opt || opt->data_size() < 6) + throw option_not_found(); + const uint8_t *ptr = opt->data_ptr() + 6; + return serialization_type(ptr, ptr + opt->data_size() - 6); +} + +uint32_t ICMPv6::mtu() const { + const icmpv6_option *opt = search_option(MTU); + if(!opt || opt->data_size() != sizeof(uint16_t) + sizeof(uint32_t)) + throw option_not_found(); + return Endian::be_to_host(*(const uint32_t*)(opt->data_ptr() + sizeof(uint16_t))); +} + +uint8_t ICMPv6::shortcut_limit() const { + const icmpv6_option *opt = search_option(NBMA_SHORT_LIMIT); + if(!opt || opt->data_size() != sizeof(uint16_t) + sizeof(uint32_t)) + throw option_not_found(); + return *opt->data_ptr(); +} + +uint32_t ICMPv6::new_advert_interval() const { + const icmpv6_option *opt = search_option(ADVERT_INTERVAL); + if(!opt || opt->data_size() != sizeof(uint16_t) + sizeof(uint32_t)) + throw option_not_found(); + return Endian::be_to_host(*(const uint32_t*)(opt->data_ptr() + sizeof(uint16_t))); +} + +ICMPv6::new_ha_info_type ICMPv6::new_home_agent_info() const { + const icmpv6_option *opt = search_option(HOME_AGENT_INFO); + if(!opt || opt->data_size() != sizeof(uint16_t) + sizeof(uint32_t)) + throw option_not_found(); + return std::make_pair( + Endian::be_to_host(*(const uint16_t*)(opt->data_ptr() + sizeof(uint16_t))), + Endian::be_to_host(*(const uint16_t*)(opt->data_ptr() + sizeof(uint16_t) * 2)) + ); +} } diff --git a/tests/src/icmpv6.cpp b/tests/src/icmpv6.cpp index 8608065..5b84bee 100644 --- a/tests/src/icmpv6.cpp +++ b/tests/src/icmpv6.cpp @@ -3,6 +3,8 @@ #include #include #include "icmpv6.h" +#include "ip.h" +#include "tcp.h" #include "utils.h" #include "hw_address.h" @@ -178,3 +180,63 @@ TEST_F(ICMPv6Test, RTLifetime) { icmp.router_lifetime(0x827f); EXPECT_EQ(icmp.router_lifetime(), 0x827f); } + +TEST_F(ICMPv6Test, SourceLinkLayerAddress) { + ICMPv6 icmp; + icmp.source_link_layer_addr("09:fe:da:fe:22:33"); + EXPECT_EQ(icmp.source_link_layer_addr(), "09:fe:da:fe:22:33"); +} + +TEST_F(ICMPv6Test, TargetLinkLayerAddress) { + ICMPv6 icmp; + icmp.target_link_layer_addr("09:fe:da:fe:22:33"); + EXPECT_EQ(icmp.target_link_layer_addr(), "09:fe:da:fe:22:33"); +} + +TEST_F(ICMPv6Test, PrefixInformation) { + ICMPv6 icmp; + ICMPv6::prefix_info_type result, info(0x8, 1, 0, 0x92038fad, + 0x918273fa, "827d:adae::1"); + icmp.prefix_info(info); + result = icmp.prefix_info(); + EXPECT_EQ(result.prefix_len, info.prefix_len); + EXPECT_EQ(result.A, info.A); + EXPECT_EQ(result.L, info.L); + EXPECT_EQ(result.valid_lifetime, info.valid_lifetime); + EXPECT_EQ(result.preferred_lifetime, info.preferred_lifetime); + EXPECT_EQ(IPv6Address(result.prefix), IPv6Address(result.prefix)); + EXPECT_EQ(IPv6Address(result.prefix), "827d:adae::1"); +} + +TEST_F(ICMPv6Test, RedirectHeader) { + ICMPv6 icmp; + IP ip = IP("127.0.0.1") / TCP(22); + PDU::serialization_type buffer = ip.serialize(); + icmp.redirect_header(buffer); + EXPECT_EQ(buffer, icmp.redirect_header()); +} + +TEST_F(ICMPv6Test, MTU) { + ICMPv6 icmp; + icmp.mtu(0x9a8df7); + EXPECT_EQ(icmp.mtu(), 0x9a8df7); +} + +TEST_F(ICMPv6Test, ShortcutLimit) { + ICMPv6 icmp; + icmp.shortcut_limit(123); + EXPECT_EQ(icmp.shortcut_limit(), 123); +} + +TEST_F(ICMPv6Test, NewAdvertisementInterval) { + ICMPv6 icmp; + icmp.new_advert_interval(0x9a8df7); + EXPECT_EQ(icmp.new_advert_interval(), 0x9a8df7); +} + +TEST_F(ICMPv6Test, NewHomeAgentInformation) { + ICMPv6 icmp; + ICMPv6::new_ha_info_type data(0x92fa, 0xaab3); + icmp.new_home_agent_info(data); + EXPECT_EQ(icmp.new_home_agent_info(), data); +}