/* * 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 #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include using std::memcmp; using std::vector; using Tins::Memory::InputMemoryStream; using Tins::Memory::OutputMemoryStream; namespace Tins { const uint8_t IP::DEFAULT_TTL = 128; PDU::metadata IP::extract_metadata(const uint8_t *buffer, uint32_t total_sz) { if (TINS_UNLIKELY(total_sz < sizeof(ip_header))) { throw malformed_packet(); } const ip_header* header = (const ip_header*)buffer; PDUType next_type = Internals::ip_type_to_pdu_flag( static_cast(header->protocol)); return metadata(header->ihl * 4, pdu_flag, next_type); } IP::IP(address_type ip_dst, address_type ip_src) { init_ip_fields(); this->dst_addr(ip_dst); this->src_addr(ip_src); } IP::IP(const uint8_t* buffer, uint32_t total_sz) { InputMemoryStream stream(buffer, total_sz); stream.read(header_); // Make sure we have enough size for options and not less than we should if (TINS_UNLIKELY(head_len() * sizeof(uint32_t) > total_sz || head_len() * sizeof(uint32_t) < sizeof(header_))) { throw malformed_packet(); } const uint8_t* options_end = buffer + head_len() * sizeof(uint32_t); // While the end of the options is not reached read an option while (stream.pointer() < options_end) { option_identifier opt_type = (option_identifier)stream.read(); if (opt_type.number > NOOP) { // Multibyte options with length as second byte const uint32_t option_size = stream.read(); if (TINS_UNLIKELY(option_size < (sizeof(uint8_t) << 1))) { throw malformed_packet(); } // The data size is the option size - the identifier and size fields const uint32_t data_size = option_size - (sizeof(uint8_t) << 1); if (data_size > 0) { if (stream.pointer() + data_size > options_end) { throw malformed_packet(); } options_.push_back( option(opt_type, stream.pointer(), stream.pointer() + data_size) ); stream.skip(data_size); } else { options_.push_back(option(opt_type)); } } else if (opt_type == END) { // If the end option found, we're done if (TINS_UNLIKELY(stream.pointer() != options_end)) { // Make sure we found the END option at the end of the options list throw malformed_packet(); } break; } else { options_.push_back(option(opt_type)); } } if (stream) { // Don't avoid consuming more than we should if tot_len is 0, // since this is the case when using TCP segmentation offload if (tot_len() != 0) { const uint32_t advertised_length = (uint32_t)tot_len() - head_len() * sizeof(uint32_t); const uint32_t stream_size = static_cast(stream.size()); total_sz = (stream_size < advertised_length) ? stream_size : advertised_length; } else { total_sz = stream.size(); } // Don't try to decode it if it's fragmented if (!is_fragmented()) { inner_pdu( Internals::pdu_from_flag( static_cast(header_.protocol), stream.pointer(), total_sz, false ) ); if (!inner_pdu()) { inner_pdu( Internals::allocate( header_.protocol, stream.pointer(), total_sz ) ); if (!inner_pdu()) { inner_pdu(new RawPDU(stream.pointer(), total_sz)); } } } else { // It's fragmented, just use RawPDU inner_pdu(new RawPDU(stream.pointer(), total_sz)); } } } void IP::init_ip_fields() { memset(&header_, 0, sizeof(header_)); header_.version = 4; ttl(DEFAULT_TTL); id(1); } bool IP::is_fragmented() const { return (flags() & IP::MORE_FRAGMENTS) != 0 || fragment_offset() != 0; } // Setters void IP::tos(uint8_t new_tos) { header_.tos = new_tos; } void IP::tot_len(uint16_t new_tot_len) { header_.tot_len = Endian::host_to_be(new_tot_len); } void IP::id(uint16_t new_id) { header_.id = Endian::host_to_be(new_id); } void IP::frag_off(uint16_t new_frag_off) { header_.frag_off = Endian::host_to_be(new_frag_off); } void IP::fragment_offset(small_uint<13> new_frag_off) { uint16_t value = (Endian::be_to_host(header_.frag_off) & 0xe000) | new_frag_off; header_.frag_off = Endian::host_to_be(value); } void IP::flags(Flags new_flags) { uint16_t value = (Endian::be_to_host(header_.frag_off) & 0x1fff) | (new_flags << 13); header_.frag_off = Endian::host_to_be(value); } void IP::ttl(uint8_t new_ttl) { header_.ttl = new_ttl; } void IP::protocol(uint8_t new_protocol) { header_.protocol = new_protocol; } void IP::checksum(uint16_t new_check) { header_.check = Endian::host_to_be(new_check); } void IP::src_addr(address_type ip) { header_.saddr = ip; } void IP::dst_addr(address_type ip) { header_.daddr = ip; } void IP::head_len(small_uint<4> new_head_len) { header_.ihl = new_head_len; } void IP::version(small_uint<4> ver) { header_.version = ver; } void IP::eol() { add_option(option_identifier(IP::END, IP::CONTROL, 0)); } void IP::noop() { add_option(option_identifier(IP::NOOP, IP::CONTROL, 0)); } void IP::security(const security_type& data) { uint8_t array[9]; OutputMemoryStream stream(array, sizeof(array)); uint32_t value = data.transmission_control; stream.write_be(data.security); stream.write_be(data.compartments); stream.write_be(data.handling_restrictions); stream.write((value >> 16) & 0xff); stream.write((value >> 8) & 0xff); stream.write(value & 0xff); add_option( option( 130, sizeof(array), array ) ); } void IP::stream_identifier(uint16_t stream_id) { stream_id = Endian::host_to_be(stream_id); add_option( option( 136, sizeof(uint16_t), (const uint8_t*)&stream_id ) ); } void IP::add_route_option(option_identifier id, const generic_route_option_type& data) { vector opt_data(1 + sizeof(uint32_t) * data.routes.size()); opt_data[0] = data.pointer; for (size_t i(0); i < data.routes.size(); ++i) { uint32_t ip = data.routes[i]; #if TINS_IS_BIG_ENDIAN ip = Endian::change_endian(ip); #endif opt_data[1 + i * 4] = ip & 0xff; opt_data[1 + i * 4 + 1] = (ip >> 8) & 0xff; opt_data[1 + i * 4 + 2] = (ip >> 16) & 0xff; opt_data[1 + i * 4 + 3] = (ip >> 24) & 0xff; } add_option( option( id, opt_data.size(), &opt_data[0] ) ); } IP::generic_route_option_type IP::search_route_option(option_identifier id) const { const option* opt = search_option(id); if (!opt) { throw option_not_found(); } return opt->to(); } IP::security_type IP::security() const { const option* opt = search_option(130); if (!opt) { throw option_not_found(); } return opt->to(); } uint16_t IP::stream_identifier() const { const option* opt = search_option(136); if (!opt) { throw option_not_found(); } return opt->to(); } void IP::add_option(const option& opt) { options_.push_back(opt); } uint32_t IP::calculate_options_size() const { uint32_t options_size = 0; for (options_type::const_iterator iter = options_.begin(); iter != options_.end(); ++iter) { options_size += sizeof(uint8_t); const option_identifier option_id = iter->option(); // Only add length field and data size for non [NOOP, EOL] options if (option_id.op_class != CONTROL || option_id.number > NOOP) { options_size += sizeof(uint8_t) + iter->data_size(); } } return options_size; } uint32_t IP::pad_options_size(uint32_t size) const { uint8_t padding = size % 4; return padding ? (size - padding + 4) : size; } bool IP::remove_option(option_identifier id) { options_type::iterator iter = search_option_iterator(id); if (iter == options_.end()) { return false; } options_.erase(iter); return true; } const IP::option* IP::search_option(option_identifier id) const { options_type::const_iterator iter = search_option_iterator(id); return (iter != options_.end()) ? &*iter : 0; } IP::options_type::const_iterator IP::search_option_iterator(option_identifier id) const { return Internals::find_option_const