/* * Copyright (c) 2017, Matias Fontanini * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include using std::memset; using std::vector; using std::string; using Tins::Memory::InputMemoryStream; using Tins::Memory::OutputMemoryStream; namespace Tins { ICMPv6::ICMPv6(Types tp) : options_size_(), reach_time_(0), retrans_timer_(0), mlqm_(), use_mldv2_(true) { memset(&header_, 0, sizeof(header_)); type(tp); } ICMPv6::ICMPv6(const uint8_t* buffer, uint32_t total_sz) : options_size_(), reach_time_(0), retrans_timer_(0), mlqm_(), use_mldv2_(true) { InputMemoryStream stream(buffer, total_sz); stream.read(header_); if (has_target_addr()) { target_address_ = stream.read(); } if (has_dest_addr()) { dest_address_ = stream.read(); } if (type() == ROUTER_ADVERT) { reach_time_ = stream.read(); retrans_timer_ = stream.read(); } else if (type() == MLD2_REPORT) { uint16_t record_count = Endian::be_to_host(header_.mlrm2.record_count); for (uint16_t i = 0; i < record_count; ++i) { multicast_records_.push_back( multicast_address_record(stream.pointer(), stream.size()) ); stream.skip(multicast_records_.back().size()); } } else if (type() == MGM_QUERY) { stream.read(multicast_address_); // MLDv1 ends here use_mldv2_ = stream; if (stream) { stream.read(mlqm_); int sources_count = stream.read_be(); while (sources_count--) { ipaddress_type address; stream.read(address); sources_.push_back(address); } } } // Retrieve options if (has_options()) { parse_options(stream); } // Attempt to parse ICMP extensions try_parse_extensions(stream); if (stream) { inner_pdu(new RawPDU(stream.pointer(), stream.size())); } } void ICMPv6::parse_options(InputMemoryStream& stream) { while (stream) { const uint8_t opt_type = stream.read(); const uint32_t opt_size = static_cast(stream.read()) * 8; if (opt_size < sizeof(uint8_t) << 1) { throw malformed_packet(); } // size(option) = option_size - identifier_size - length_identifier_size const uint32_t payload_size = opt_size - (sizeof(uint8_t) << 1); if (!stream.can_read(payload_size)) { throw malformed_packet(); } add_option( option( opt_type, payload_size, stream.pointer() ) ); stream.skip(payload_size); } } void ICMPv6::type(Types new_type) { header_.type = new_type; } void ICMPv6::code(uint8_t new_code) { header_.code = new_code; } void ICMPv6::checksum(uint16_t new_cksum) { header_.cksum = Endian::host_to_be(new_cksum); } void ICMPv6::identifier(uint16_t new_identifier) { header_.u_echo.identifier = Endian::host_to_be(new_identifier); } void ICMPv6::sequence(uint16_t new_sequence) { header_.u_echo.sequence = Endian::host_to_be(new_sequence); } void ICMPv6::override(small_uint<1> new_override) { header_.u_nd_advt.override = new_override; } void ICMPv6::solicited(small_uint<1> new_solicited) { header_.u_nd_advt.solicited = new_solicited; } void ICMPv6::router(small_uint<1> new_router) { header_.u_nd_advt.router = new_router; } void ICMPv6::hop_limit(uint8_t new_hop_limit) { header_.u_nd_ra.hop_limit = new_hop_limit; } void ICMPv6::maximum_response_code(uint16_t maximum_response_code) { header_.u_echo.identifier = Endian::host_to_be(maximum_response_code); } void ICMPv6::router_pref(small_uint<2> new_router_pref) { header_.u_nd_ra.router_pref = new_router_pref; } void ICMPv6::home_agent(small_uint<1> new_home_agent) { header_.u_nd_ra.home_agent = new_home_agent; } void ICMPv6::other(small_uint<1> new_other) { header_.u_nd_ra.other = new_other; } void ICMPv6::managed(small_uint<1> new_managed) { header_.u_nd_ra.managed = new_managed; } void ICMPv6::router_lifetime(uint16_t new_router_lifetime) { header_.u_nd_ra.router_lifetime = Endian::host_to_be(new_router_lifetime); } void ICMPv6::reachable_time(uint32_t new_reachable_time) { reach_time_ = Endian::host_to_be(new_reachable_time); } void ICMPv6::retransmit_timer(uint32_t new_retrans_timer_) { retrans_timer_ = Endian::host_to_be(new_retrans_timer_); } void ICMPv6::multicast_address_records(const multicast_address_records_list& records) { multicast_records_ = records; } void ICMPv6::sources(const sources_list& new_sources) { sources_ = new_sources; } void ICMPv6::supress(small_uint<1> value) { mlqm_.supress = value; } void ICMPv6::qrv(small_uint<3> value) { mlqm_.qrv = value; } void ICMPv6::qqic(uint8_t value) { mlqm_.qqic = value; } 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; } void ICMPv6::multicast_addr(const ipaddress_type& new_multicast_addr) { multicast_address_ = new_multicast_addr; } uint32_t ICMPv6::header_size() const { uint32_t extra = 0; if (type() == ROUTER_ADVERT) { extra = sizeof(uint32_t) * 2; } else if (type() == MLD2_REPORT) { typedef multicast_address_records_list::const_iterator iterator; for (iterator iter = multicast_records_.begin(); iter != multicast_records_.end(); ++iter) { extra += iter->size(); } } else if (type() == MGM_QUERY) { extra += ipaddress_type::address_size; if (use_mldv2_) { extra += sizeof(mlqm_) + sizeof(uint16_t) + ipaddress_type::address_size * sources_.size(); } } return sizeof(header_) + options_size_ + extra + (has_target_addr() ? ipaddress_type::address_size : 0) + (has_dest_addr() ? ipaddress_type::address_size : 0); } uint32_t ICMPv6::trailer_size() const { uint32_t output = 0; if (has_extensions()) { output += extensions_.size(); if (inner_pdu()) { // This gets how much padding we'll use. // If the next pdu size is lower than 128 bytes, then padding = 128 - pdu size // If the next pdu size is greater than 128 bytes, // then padding = pdu size padded to next 32 bit boundary - pdu size const uint32_t upper_bound = (get_adjusted_inner_pdu_size() > 128U) ? get_adjusted_inner_pdu_size() : 128U; output += upper_bound - inner_pdu()->size(); } } return output; } void ICMPv6::use_length_field(bool value) { // We just need a non 0 value here, we'll use the right value on // write_serialization header_.rfc4884.length = value ? 1 : 0; } bool ICMPv6::matches_response(const uint8_t* ptr, uint32_t total_sz) const { if (total_sz < sizeof(header_)) { return false; } const icmp6_header* hdr_ptr = (const icmp6_header*)ptr; if (type() == ECHO_REQUEST && hdr_ptr->type == ECHO_REPLY) { return hdr_ptr->u_echo.identifier == header_.u_echo.identifier && hdr_ptr->u_echo.sequence == header_.u_echo.sequence; } else if ( (type() == ROUTER_SOLICIT && hdr_ptr->type == ROUTER_ADVERT) || (type() == NEIGHBOUR_SOLICIT && hdr_ptr->type == NEIGHBOUR_ADVERT) ) { return hdr_ptr->code == 0; } return false; } void ICMPv6::write_serialization(uint8_t* buffer, uint32_t total_sz) { OutputMemoryStream stream(buffer, total_sz); // If extensions are allowed and we have to set the length field if (are_extensions_allowed()) { uint32_t length_value = get_adjusted_inner_pdu_size(); // If the next pdu size is greater than 128, we are forced to set the length field if (length() != 0 || length_value > 128) { if (length_value > 0) { length_value = (length_value > 128U) ? length_value : 128U; } // This field uses 64 bit words as the unit header_.rfc4884.length = length_value / sizeof(uint64_t); } } // Initially set checksum to 0, we'll calculate it at the end header_.cksum = 0; // Update the MLRM record count before writing the header if (type() == MLD2_REPORT) { header_.mlrm2.record_count = Endian::host_to_be(multicast_records_.size()); } stream.write(header_); if (has_target_addr()) { stream.write(target_address_); } if (has_dest_addr()) { stream.write(dest_address_); } if (type() == ROUTER_ADVERT) { stream.write(reach_time_); stream.write(retrans_timer_); } else if (type() == MLD2_REPORT) { typedef multicast_address_records_list::const_iterator iterator; for (iterator iter = multicast_records_.begin(); iter != multicast_records_.end(); ++iter) { iter->serialize(stream.pointer(), stream.size()); stream.skip(iter->size()); } } else if (type() == MGM_QUERY) { stream.write(multicast_address_); // Only write this if we're using MLDv2 if (use_mldv2_) { stream.write(mlqm_); stream.write_be(sources_.size()); typedef sources_list::const_iterator iterator; for (iterator iter = sources_.begin(); iter != sources_.end(); ++iter) { stream.write(*iter); } } } for (options_type::const_iterator it = options_.begin(); it != options_.end(); ++it) { write_option(*it, stream); } if (has_extensions()) { uint8_t* extensions_ptr = stream.pointer(); if (inner_pdu()) { // Get the size of the next pdu, padded to the next 32 bit boundary uint32_t inner_pdu_size = get_adjusted_inner_pdu_size(); // If it's lower than 128, we need to padd enough zeroes to make it 128 bytes long if (inner_pdu_size < 128) { memset(extensions_ptr + inner_pdu_size, 0, 128 - inner_pdu_size); inner_pdu_size = 128; } else { // If the packet has to be padded to 64 bits, append the amount // of zeroes we need uint32_t diff = inner_pdu_size - inner_pdu()->size(); memset(extensions_ptr + inner_pdu_size, 0, diff); } extensions_ptr += inner_pdu_size; } // Now serialize the exensions where they should be extensions_.serialize( extensions_ptr, total_sz - (extensions_ptr - stream.pointer()) ); } const Tins::IPv6* ipv6 = tins_cast(parent_pdu()); if (ipv6) { uint32_t checksum = Utils::pseudoheader_checksum( ipv6->src_addr(), ipv6->dst_addr(), size(), Constants::IP::PROTO_ICMPV6 ) + Utils::sum_range(buffer, buffer + total_sz); while (checksum >> 16) { checksum = (checksum & 0xffff) + (checksum >> 16); } header_.cksum = ~checksum & 0xffff; memcpy(buffer + 2, &header_.cksum, sizeof(uint16_t)); } } uint8_t ICMPv6::get_option_padding(uint32_t data_size) { uint8_t padding = 8 - data_size % 8; if (padding == 8) { padding = 0; } return padding; } // can i haz more? bool ICMPv6::has_options() const { switch (type()) { case NEIGHBOUR_SOLICIT: case NEIGHBOUR_ADVERT: case ROUTER_SOLICIT: case ROUTER_ADVERT: case REDIRECT: return true; default: return false; } } void ICMPv6::add_option(const option& option) { internal_add_option(option); options_.push_back(option); } void ICMPv6::internal_add_option(const option& option) { options_size_ += static_cast(option.data_size() + sizeof(uint8_t) * 2); } bool ICMPv6::remove_option(OptionTypes type) { options_type::iterator iter = search_option_iterator(type); if (iter == options_.end()) { return false; } options_size_ -= static_cast(iter->data_size() + sizeof(uint8_t) * 2); options_.erase(iter); return true; } void ICMPv6::write_option(const option& opt, OutputMemoryStream& stream) { stream.write(opt.option()); stream.write((opt.length_field() + sizeof(uint8_t) * 2) / 8); stream.write(opt.data_ptr(), opt.data_size()); } void ICMPv6::use_mldv2(bool value) { use_mldv2_ = value; } const ICMPv6::option* ICMPv6::search_option(OptionTypes type) const { // Search for the iterator. If we found something, return it, otherwise return nullptr. options_type::const_iterator iter = search_option_iterator(type); return (iter != options_.end()) ? &*iter : 0; } ICMPv6::options_type::const_iterator ICMPv6::search_option_iterator(OptionTypes type) const { return Internals::find_option_const