/* The software in this package is distributed under the GNU General Public License version 2 (with a special exception described below). A copy of GNU General Public License (GPL) is included in this distribution, in the file COPYING.GPL. As a special exception, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other works to produce a work based on this file, this file does not by itself cause the resulting work to be covered by the GNU General Public License. However the source code for this file must still be made available in accordance with section (3) of the GNU General Public License. This exception does not invalidate any other reasons why a work based on this file might be covered by the GNU General Public License. Christian Herdtweck, Intra2net AG 2015 Based on an example in Boost Documentation (by Christopher M. Kohlhoff) and adaptation by Guilherme M. Ferreira */ #include "icmp/icmppacket.h" #include #include #include #include #include "boost_assert_handler.h" #include "ip/ipv4header.h" #include "ip/ipv6header.h" #include "icmp/icmpechodata.h" #include "icmp/icmpdestinationunreachabledata.h" #include "icmp/icmptimeexceededdata.h" #include "icmp/icmppacketfactory.h" using I2n::Logger::GlobalLogger; IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol) : ip_head_ptr() , icmp_header() , icmp_data_ptr() { if ( icmp::v4() == protocol ) ip_head_ptr.reset( new Ipv4Header() ); else if ( icmp::v6() == protocol ) ip_head_ptr.reset( new Ipv6Header() ); else // pingcheck-type throwing of exceptions: { GlobalLogger.error() << "Invalid IP version, need 4 or 6!"; BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" ); } } // IP header is created with only 0s, is not used in write, anyway IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol, const IcmpHeader &icmp_header_arg, const IcmpDataPtr &icmp_data_arg) : ip_head_ptr() , icmp_header( icmp_header_arg ) , icmp_data_ptr( icmp_data_arg ) { if ( icmp::v4() == protocol ) ip_head_ptr.reset( new Ipv4Header() ); else if ( icmp::v6() == protocol ) ip_head_ptr.reset( new Ipv6Header() ); else // pingcheck-type throwing of exceptions: { GlobalLogger.error() << "Invalid IP version, need 4 or 6!"; BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" ); } // create checksum icmp_header.calc_checksum( icmp_data_ptr->calc_checksum_part() ); } // returns Icmpv4Type_InvalidLast if is actually v6 and not v4 Icmpv4Type IcmpPacket::get_type_v4() const { if (ip_head_ptr->get_version() == 4) return static_cast( icmp_header.get_type() ); else return Icmpv4Type_InvalidLast; // invalid } // returns Icmpv6Type_InvalidLast if is actually v4 and not v6 Icmpv6Type IcmpPacket::get_type_v6() const { if (ip_head_ptr->get_version() == 6) return static_cast( icmp_header.get_type() ); else return Icmpv6Type_InvalidLast; // invalid } uint8_t IcmpPacket::get_icmp_code() const { return icmp_header.get_code(); } std::size_t IcmpPacket::get_icmp_size() const { return static_cast(icmp_header.get_header_length()) + icmp_data_ptr->get_size(); } address IcmpPacket::get_source_address() const { return ip_head_ptr->get_source_address(); } address IcmpPacket::get_destination_address() const { return ip_head_ptr->get_destination_address(); } bool IcmpPacket::check_integrity() { return false; } // not implemented yet bool IcmpPacket::match_destination_unreachable(const uint16_t identifier, const uint16_t sequence_number, const address &destination_address) const { return icmp_data_ptr->match_destination_unreachable(identifier, sequence_number, destination_address); } bool IcmpPacket::match_time_exceeded(const uint16_t identifier, const uint16_t sequence_number, const address &destination_address) const { return icmp_data_ptr->match_time_exceeded(identifier, sequence_number, destination_address); } bool IcmpPacket::match_echo_reply(const uint16_t identifier, const uint16_t sequence_number, const address &destination_address) const { // ensure is not a EchoRequest but a EchoReply int version = ip_head_ptr->get_version(); uint8_t type = icmp_header.get_type(); if (version == 4 && type != Icmpv4Type_EchoReply) return false; if (version == 6 && type != Icmpv6Type_EchoReply) return false; return ip_head_ptr->get_source_address() == destination_address && icmp_data_ptr->match_echo_reply(identifier, sequence_number); } /** * @brief print echo reply / destination unreachable message depending on * ICMP data type * * @param bytes_transferred Number of bytes transferred. * @param time_packet_sent The time when this packet was sent. * * @return void */ void IcmpPacket::print( const size_t &bytes_transferred, const boost::posix_time::ptime &time_packet_sent ) const { size_t bytes_received = bytes_transferred - ip_head_ptr->get_header_length(); std::string remote_address = ip_head_ptr->get_source_address().to_string(); uint32_t ttl = ip_head_ptr->get_time_to_live(); icmp_data_ptr->print( bytes_received, time_packet_sent, remote_address, ttl); } /** * @brief Read (part of) the ICMP packet from the input stream @a is * * @param is The input stream. * * @return result of the read, currently one of {ok, fail, not enough data} */ IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is ) { if ( !is.good() ) return IcmpPacket::ReadReturnCode_BAD_STREAM; // try to read ip header uint8_t version_with_ihl = static_cast( is.peek() ); uint8_t version = (version_with_ihl & 0xF0) >> 4; // ( this is the same as icmp_data_ptr->get_ip_version() ) if ( version != ip_head_ptr->get_version() ) { GlobalLogger.error() << "IP version given to constructor (" << static_cast(ip_head_ptr->get_version()) << ") does not " << "match the one in input stream (" << static_cast(version) << ")!" << std::endl; BOOST_ASSERT( !"Inconsistent IP version!" ); } if (version == 4) { Ipv4Header *new_header = new Ipv4Header(); is >> *new_header; ip_head_ptr.reset( new_header ); } else if (version == 6) { Ipv6Header *new_header = new Ipv6Header(); is >> *new_header; ip_head_ptr.reset( new_header ); } else { // pingcheck-type error throwing GlobalLogger.error() << "Invalid IP version: " << static_cast(version) << "!" << std::endl; BOOST_ASSERT( !"Invalid IP version!" ); } if ( is.eof() ) return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; else if ( !is.good() ) return IcmpPacket::ReadReturnCode_BAD_STREAM; // try to read the icmp header icmp_header.read(is); if ( is.eof() ) return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; else if ( !is.good() ) return IcmpPacket::ReadReturnCode_BAD_STREAM; // could check validity of icmp type here // calculate the size of the ICMP data std::streamsize data_length = static_cast( ip_head_ptr->get_total_length() ) - static_cast( ip_head_ptr->get_header_length() ) - static_cast( icmp_header.get_header_length() ); // try to read that amount of data if ( data_length < 0 ) { GlobalLogger.error() << "Invalid size for optional ICMP data: " << data_length << std::endl; is.setstate( std::ios::failbit ); return IcmpPacket::ReadReturnCode_INVALID_SIZE; } else if ( data_length > 0 ) { if ( get_type_v4() == Icmpv4Type_EchoRequest || get_type_v4() == Icmpv4Type_EchoReply || get_type_v6() == Icmpv6Type_EchoRequest || get_type_v6() == Icmpv6Type_EchoReply ) icmp_data_ptr.reset( new IcmpEchoData( static_cast(data_length)) ); else if (get_type_v4() == Icmpv4Type_DestinationUnreachable || get_type_v6() == Icmpv6Type_DestinationUnreachable) icmp_data_ptr.reset( new IcmpDestinationUnreachableData( static_cast(data_length)) ); else if (get_type_v4() == Icmpv4Type_TimeExceeded || get_type_v6() == Icmpv6Type_TimeExceeded) icmp_data_ptr.reset( new IcmpTimeExceededData( static_cast(data_length)) ); else { // unspecified icmp data type, will consume right amount of data // from stream but return false for match_echo_request and // match_destination_unreachable icmp_data_ptr.reset( new IcmpData( static_cast(data_length)) ); } icmp_data_ptr->read( is ); } if ( is.eof() ) return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; else if ( !is.good() ) return IcmpPacket::ReadReturnCode_BAD_STREAM; else return IcmpPacket::ReadReturnCode_OK; } std::string IcmpPacket::return_code_to_string( const IcmpPacket::ReadReturnCode &code ) { switch (code) { case IcmpPacket::ReadReturnCode_OK: return "ok"; break; case IcmpPacket::ReadReturnCode_UNSPECIFIED: return "return code not set yet!"; break; case IcmpPacket::ReadReturnCode_FAIL: return "failed for unspecified reason!"; break; case IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA: return "not enough data!"; break; case IcmpPacket::ReadReturnCode_BAD_STREAM: return "bad stream!"; break; case IcmpPacket::ReadReturnCode_INVALID_SIZE: return "invalid data size!"; break; case IcmpPacket::ReadReturnCode_UNKNOWN_PROTOCOL: return "unknown protocol, expect ICMPv4/6!"; break; case IcmpPacket::ReadReturnCode_UNKNOWN_ICMP_TYPE: return "unknown icmp type, expect echo request/reply or destination unreachable!"; break; } // if nothing matched return "unknown return code!"; } /** * @brief Write the ICMP packet to the @c ostream. * * the IP header is not written to stream! * (is usually all 0s anyway) * * @param os The output stream. * * @return @c true if the write was successful, or @c false if an error occurred. */ bool IcmpPacket::write( std::ostream &os ) const { os.clear(); icmp_header.write(os); icmp_data_ptr->write(os); return !os.fail(); } /** @brief like write but also writes the IP header * * this is required in packet dumping for debugging */ bool IcmpPacket::dump( std::ostream &os ) const { os.clear(); ip_head_ptr->write(os); icmp_header.write(os); icmp_data_ptr->write(os); return !os.fail(); } /** * @brief create packet from data in @a is; calls IcmpPacket::read * * @param is input stream with hopefully sufficient data * * @returns stream with less data; failbit is set if read returns other than ReadReturnCode_OK */ std::istream& operator>>( std::istream &is, IcmpPacket &packet ) { IcmpPacket::ReadReturnCode return_code = packet.read(is); if ( return_code != IcmpPacket::ReadReturnCode_OK ) is.setstate( std::ios::failbit ); return is; } std::ostream& operator<<( std::ostream& os, const IcmpPacket& packet ) { packet.write( os ); return os; } /** * @brief returns a short string representation of this packet * * @throws some kind of exception, possibly a runtime_error cause by a failed * BOOST_ASSERT */ std::string IcmpPacket::to_string() const { std::stringstream buf; buf << "[IcmpPacket: "; if (ip_head_ptr) buf << ip_head_ptr->to_string() << ", "; else buf << "no ip header, "; buf << icmp_header.to_string() << ", "; if (icmp_data_ptr) buf << icmp_data_ptr->to_string(); else buf << "no icmp data"; buf << "]"; return buf.str(); }