/* * Copyright (c) 2014, 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. * */ #ifdef TINS_DEBUG #include #endif #include #include "icmpv6.h" #include "ipv6.h" #include "rawpdu.h" #include "utils.h" #include "constants.h" #include "exceptions.h" namespace Tins { ICMPv6::ICMPv6(Types tp) : _options_size(), reach_time(0), retrans_timer(0) { std::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) { if(total_sz < sizeof(_header)) throw malformed_packet(); std::memcpy(&_header, buffer, sizeof(_header)); buffer += sizeof(_header); total_sz -= sizeof(_header); if(has_target_addr()) { if(total_sz < ipaddress_type::address_size) throw malformed_packet(); target_addr(buffer); buffer += ipaddress_type::address_size; total_sz -= ipaddress_type::address_size; } if(has_dest_addr()) { if(total_sz < ipaddress_type::address_size) throw malformed_packet(); 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) throw malformed_packet(); memcpy(&reach_time, buffer, sizeof(uint32_t)); memcpy(&retrans_timer, buffer + sizeof(uint32_t), sizeof(uint32_t)); buffer += sizeof(uint32_t) * 2; total_sz -= sizeof(uint32_t) * 2; } if(has_options()) parse_options(buffer, total_sz); if(total_sz > 0) inner_pdu(new RawPDU(buffer, total_sz)); } void ICMPv6::parse_options(const uint8_t *&buffer, uint32_t &total_sz) { while(total_sz > 0) { if(total_sz < 8 || (static_cast(buffer[1]) * 8) > total_sz || buffer[1] < 1) throw malformed_packet(); // size(option) = option_size - identifier_size - length_identifier_size add_option( option( buffer[0], static_cast(buffer[1]) * 8 - sizeof(uint8_t) * 2, buffer + 2 ) ); total_sz -= buffer[1] * 8; buffer += buffer[1] * 8; } } 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::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::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 { uint32_t extra = 0; if(type() == ROUTER_ADVERT) extra = sizeof(uint32_t) * 2; return sizeof(_header) + _options_size + extra + (has_target_addr() ? ipaddress_type::address_size : 0) + (has_dest_addr() ? ipaddress_type::address_size : 0); } bool ICMPv6::matches_response(const uint8_t *ptr, uint32_t total_sz) const { if(total_sz < sizeof(icmp6hdr)) return false; const icmp6hdr *hdr_ptr = (const icmp6hdr*)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; return false; } void ICMPv6::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *parent) { #ifdef TINS_DEBUG assert(total_sz >= header_size()); #endif uint32_t full_sz = total_sz; uint8_t *buffer_start = buffer; _header.cksum = 0; 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); } if(type() == ROUTER_ADVERT) { std::memcpy(buffer, &reach_time, sizeof(uint32_t)); buffer += sizeof(uint32_t); std::memcpy(buffer, &retrans_timer, sizeof(uint32_t)); buffer += sizeof(uint32_t); total_sz -= sizeof(uint32_t) * 2; } 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); // total_sz is only used if TINS_DEBUG is defined. total_sz -= it->data_size() + sizeof(uint8_t) * 2; #endif buffer = write_option(*it, buffer); } const Tins::IPv6 *ipv6 = tins_cast(parent); if(ipv6) { uint32_t checksum = Utils::pseudoheader_checksum( ipv6->src_addr(), ipv6->dst_addr(), size(), Constants::IP::PROTO_ICMPV6 ) + Utils::do_checksum(buffer_start, buffer_start + full_sz); while (checksum >> 16) checksum = (checksum & 0xffff) + (checksum >> 16); this->checksum(~checksum); memcpy(buffer_start + 2, &_header.cksum, sizeof(uint16_t)); } } // 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; } uint8_t *ICMPv6::write_option(const option &opt, uint8_t *buffer) { *buffer++ = opt.option(); *buffer++ = static_cast((opt.length_field() + sizeof(uint8_t) * 2) / 8); return std::copy(opt.data_ptr(), opt.data_ptr() + opt.data_size(), buffer); } 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 { Internals::option_type_equality_comparator