| 1 | /* |
| 2 | The software in this package is distributed under the GNU General |
| 3 | Public License version 2 (with a special exception described below). |
| 4 | |
| 5 | A copy of GNU General Public License (GPL) is included in this distribution, |
| 6 | in the file COPYING.GPL. |
| 7 | |
| 8 | As a special exception, if other files instantiate templates or use macros |
| 9 | or inline functions from this file, or you compile this file and link it |
| 10 | with other works to produce a work based on this file, this file |
| 11 | does not by itself cause the resulting work to be covered |
| 12 | by the GNU General Public License. |
| 13 | |
| 14 | However the source code for this file must still be made available |
| 15 | in accordance with section (3) of the GNU General Public License. |
| 16 | |
| 17 | This exception does not invalidate any other reasons why a work based |
| 18 | on this file might be covered by the GNU General Public License. |
| 19 | |
| 20 | Christian Herdtweck, Intra2net AG 2015 |
| 21 | Based on an example in Boost Documentation (by Christopher M. Kohlhoff) |
| 22 | and adaptation by Guilherme M. Ferreira |
| 23 | */ |
| 24 | |
| 25 | #include "icmp/icmppacket.h" |
| 26 | |
| 27 | #include <iostream> |
| 28 | #include <sstream> |
| 29 | #include <boost/scoped_array.hpp> |
| 30 | |
| 31 | #include <logfunc.hpp> |
| 32 | |
| 33 | #include "boost_assert_handler.h" |
| 34 | #include "ip/ipv4header.h" |
| 35 | #include "ip/ipv6header.h" |
| 36 | #include "icmp/icmpechodata.h" |
| 37 | #include "icmp/icmpdestinationunreachabledata.h" |
| 38 | #include "icmp/icmptimeexceededdata.h" |
| 39 | #include "icmp/icmppacketfactory.h" |
| 40 | |
| 41 | |
| 42 | using I2n::Logger::GlobalLogger; |
| 43 | |
| 44 | IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol) |
| 45 | : ip_head_ptr() |
| 46 | , icmp_header() |
| 47 | , icmp_data_ptr() |
| 48 | { |
| 49 | if ( icmp::v4() == protocol ) |
| 50 | ip_head_ptr.reset( new Ipv4Header() ); |
| 51 | else if ( icmp::v6() == protocol ) |
| 52 | ip_head_ptr.reset( new Ipv6Header() ); |
| 53 | else // pingcheck-type throwing of exceptions: |
| 54 | { |
| 55 | GlobalLogger.error() << "Invalid IP version, need 4 or 6!"; |
| 56 | BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" ); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | // IP header is created with only 0s, is not used in write, anyway |
| 61 | IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol, |
| 62 | const IcmpHeader &icmp_header_arg, |
| 63 | const IcmpDataPtr &icmp_data_arg) |
| 64 | : ip_head_ptr() |
| 65 | , icmp_header( icmp_header_arg ) |
| 66 | , icmp_data_ptr( icmp_data_arg ) |
| 67 | { |
| 68 | if ( icmp::v4() == protocol ) |
| 69 | ip_head_ptr.reset( new Ipv4Header() ); |
| 70 | else if ( icmp::v6() == protocol ) |
| 71 | ip_head_ptr.reset( new Ipv6Header() ); |
| 72 | else // pingcheck-type throwing of exceptions: |
| 73 | { |
| 74 | GlobalLogger.error() << "Invalid IP version, need 4 or 6!"; |
| 75 | BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" ); |
| 76 | } |
| 77 | |
| 78 | // create checksum |
| 79 | icmp_header.calc_checksum( icmp_data_ptr->calc_checksum_part() ); |
| 80 | } |
| 81 | |
| 82 | // returns Icmpv4Type_InvalidLast if is actually v6 and not v4 |
| 83 | Icmpv4Type IcmpPacket::get_type_v4() const |
| 84 | { |
| 85 | if (ip_head_ptr->get_version() == 4) |
| 86 | return static_cast<Icmpv4Type>( icmp_header.get_type() ); |
| 87 | else |
| 88 | return Icmpv4Type_InvalidLast; // invalid |
| 89 | } |
| 90 | |
| 91 | // returns Icmpv6Type_InvalidLast if is actually v4 and not v6 |
| 92 | Icmpv6Type IcmpPacket::get_type_v6() const |
| 93 | { |
| 94 | if (ip_head_ptr->get_version() == 6) |
| 95 | return static_cast<Icmpv6Type>( icmp_header.get_type() ); |
| 96 | else |
| 97 | return Icmpv6Type_InvalidLast; // invalid |
| 98 | } |
| 99 | |
| 100 | uint8_t IcmpPacket::get_icmp_code() const |
| 101 | { |
| 102 | return icmp_header.get_code(); |
| 103 | } |
| 104 | |
| 105 | std::size_t IcmpPacket::get_icmp_size() const |
| 106 | { |
| 107 | return static_cast<std::size_t>(icmp_header.get_header_length()) |
| 108 | + icmp_data_ptr->get_size(); |
| 109 | } |
| 110 | |
| 111 | address IcmpPacket::get_source_address() const |
| 112 | { |
| 113 | return ip_head_ptr->get_source_address(); |
| 114 | } |
| 115 | |
| 116 | address IcmpPacket::get_destination_address() const |
| 117 | { |
| 118 | return ip_head_ptr->get_destination_address(); |
| 119 | } |
| 120 | |
| 121 | |
| 122 | bool IcmpPacket::check_integrity() { return false; } // not implemented yet |
| 123 | |
| 124 | bool IcmpPacket::match_destination_unreachable(const uint16_t identifier, |
| 125 | const uint16_t sequence_number, |
| 126 | const address &destination_address) const |
| 127 | { |
| 128 | return icmp_data_ptr->match_destination_unreachable(identifier, |
| 129 | sequence_number, |
| 130 | destination_address); |
| 131 | } |
| 132 | |
| 133 | bool IcmpPacket::match_time_exceeded(const uint16_t identifier, |
| 134 | const uint16_t sequence_number, |
| 135 | const address &destination_address) const |
| 136 | { |
| 137 | return icmp_data_ptr->match_time_exceeded(identifier, |
| 138 | sequence_number, |
| 139 | destination_address); |
| 140 | } |
| 141 | |
| 142 | bool IcmpPacket::match_echo_reply(const uint16_t identifier, |
| 143 | const uint16_t sequence_number, |
| 144 | const address &destination_address) const |
| 145 | { |
| 146 | // ensure is not a EchoRequest but a EchoReply |
| 147 | int version = ip_head_ptr->get_version(); |
| 148 | uint8_t type = icmp_header.get_type(); |
| 149 | if (version == 4 && type != Icmpv4Type_EchoReply) |
| 150 | return false; |
| 151 | if (version == 6 && type != Icmpv6Type_EchoReply) |
| 152 | return false; |
| 153 | |
| 154 | return ip_head_ptr->get_source_address() == destination_address |
| 155 | && icmp_data_ptr->match_echo_reply(identifier, sequence_number); |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * @brief print echo reply / destination unreachable message depending on |
| 160 | * ICMP data type |
| 161 | * |
| 162 | * @param bytes_transferred Number of bytes transferred. |
| 163 | * @param time_packet_sent The time when this packet was sent. |
| 164 | * |
| 165 | * @return void |
| 166 | */ |
| 167 | void IcmpPacket::print( const size_t &bytes_transferred, |
| 168 | const boost::posix_time::ptime &time_packet_sent ) const |
| 169 | { |
| 170 | size_t bytes_received = bytes_transferred |
| 171 | - ip_head_ptr->get_header_length(); |
| 172 | std::string remote_address |
| 173 | = ip_head_ptr->get_source_address().to_string(); |
| 174 | uint32_t ttl = ip_head_ptr->get_time_to_live(); |
| 175 | icmp_data_ptr->print( bytes_received, time_packet_sent, |
| 176 | remote_address, ttl); |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * @brief Read (part of) the ICMP packet from the input stream @a is |
| 181 | * |
| 182 | * @param is The input stream. |
| 183 | * |
| 184 | * @return result of the read, currently one of {ok, fail, not enough data} |
| 185 | */ |
| 186 | IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is ) |
| 187 | { |
| 188 | if ( !is.good() ) |
| 189 | return IcmpPacket::ReadReturnCode_BAD_STREAM; |
| 190 | |
| 191 | // try to read ip header |
| 192 | uint8_t version_with_ihl = static_cast<uint8_t>( is.peek() ); |
| 193 | uint8_t version = (version_with_ihl & 0xF0) >> 4; |
| 194 | // ( this is the same as icmp_data_ptr->get_ip_version() ) |
| 195 | |
| 196 | if ( version != ip_head_ptr->get_version() ) |
| 197 | { |
| 198 | GlobalLogger.error() << "IP version given to constructor (" |
| 199 | << static_cast<int>(ip_head_ptr->get_version()) << ") does not " |
| 200 | << "match the one in input stream (" << static_cast<int>(version) |
| 201 | << ")!" << std::endl; |
| 202 | BOOST_ASSERT( !"Inconsistent IP version!" ); |
| 203 | } |
| 204 | if (version == 4) |
| 205 | { |
| 206 | Ipv4Header *new_header = new Ipv4Header(); |
| 207 | is >> *new_header; |
| 208 | ip_head_ptr.reset( new_header ); |
| 209 | } |
| 210 | else if (version == 6) |
| 211 | { |
| 212 | Ipv6Header *new_header = new Ipv6Header(); |
| 213 | is >> *new_header; |
| 214 | ip_head_ptr.reset( new_header ); |
| 215 | } |
| 216 | else |
| 217 | { // pingcheck-type error throwing |
| 218 | GlobalLogger.error() << "Invalid IP version: " |
| 219 | << static_cast<int>(version) << "!" << std::endl; |
| 220 | BOOST_ASSERT( !"Invalid IP version!" ); |
| 221 | } |
| 222 | |
| 223 | if ( is.eof() ) |
| 224 | return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; |
| 225 | else if ( !is.good() ) |
| 226 | return IcmpPacket::ReadReturnCode_BAD_STREAM; |
| 227 | |
| 228 | // try to read the icmp header |
| 229 | icmp_header.read(is); |
| 230 | |
| 231 | if ( is.eof() ) |
| 232 | return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; |
| 233 | else if ( !is.good() ) |
| 234 | return IcmpPacket::ReadReturnCode_BAD_STREAM; |
| 235 | |
| 236 | // could check validity of icmp type here |
| 237 | |
| 238 | // calculate the size of the ICMP data |
| 239 | std::streamsize data_length = |
| 240 | static_cast<std::streamsize>( ip_head_ptr->get_total_length() ) |
| 241 | - static_cast<std::streamsize>( ip_head_ptr->get_header_length() ) |
| 242 | - static_cast<std::streamsize>( icmp_header.get_header_length() ); |
| 243 | |
| 244 | // try to read that amount of data |
| 245 | if ( data_length < 0 ) |
| 246 | { |
| 247 | GlobalLogger.error() << "Invalid size for optional ICMP data: " |
| 248 | << data_length << std::endl; |
| 249 | is.setstate( std::ios::failbit ); |
| 250 | return IcmpPacket::ReadReturnCode_INVALID_SIZE; |
| 251 | } |
| 252 | else if ( data_length > 0 ) |
| 253 | { |
| 254 | if ( get_type_v4() == Icmpv4Type_EchoRequest |
| 255 | || get_type_v4() == Icmpv4Type_EchoReply |
| 256 | || get_type_v6() == Icmpv6Type_EchoRequest |
| 257 | || get_type_v6() == Icmpv6Type_EchoReply ) |
| 258 | icmp_data_ptr.reset( new IcmpEchoData( |
| 259 | static_cast<std::size_t>(data_length)) ); |
| 260 | else if (get_type_v4() == Icmpv4Type_DestinationUnreachable |
| 261 | || get_type_v6() == Icmpv6Type_DestinationUnreachable) |
| 262 | icmp_data_ptr.reset( new IcmpDestinationUnreachableData( |
| 263 | static_cast<std::size_t>(data_length)) ); |
| 264 | else if (get_type_v4() == Icmpv4Type_TimeExceeded |
| 265 | || get_type_v6() == Icmpv6Type_TimeExceeded) |
| 266 | icmp_data_ptr.reset( new IcmpTimeExceededData( |
| 267 | static_cast<std::size_t>(data_length)) ); |
| 268 | else |
| 269 | { // unspecified icmp data type, will consume right amount of data |
| 270 | // from stream but return false for match_echo_request and |
| 271 | // match_destination_unreachable |
| 272 | icmp_data_ptr.reset( new IcmpData( |
| 273 | static_cast<std::size_t>(data_length)) ); |
| 274 | } |
| 275 | icmp_data_ptr->read( is ); |
| 276 | } |
| 277 | |
| 278 | if ( is.eof() ) |
| 279 | return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA; |
| 280 | else if ( !is.good() ) |
| 281 | return IcmpPacket::ReadReturnCode_BAD_STREAM; |
| 282 | else |
| 283 | return IcmpPacket::ReadReturnCode_OK; |
| 284 | } |
| 285 | |
| 286 | |
| 287 | std::string IcmpPacket::return_code_to_string( const IcmpPacket::ReadReturnCode &code ) |
| 288 | { |
| 289 | switch (code) |
| 290 | { |
| 291 | case IcmpPacket::ReadReturnCode_OK: return "ok"; break; |
| 292 | case IcmpPacket::ReadReturnCode_UNSPECIFIED: return "return code not set yet!"; break; |
| 293 | case IcmpPacket::ReadReturnCode_FAIL: return "failed for unspecified reason!"; break; |
| 294 | case IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA: return "not enough data!"; break; |
| 295 | case IcmpPacket::ReadReturnCode_BAD_STREAM: return "bad stream!"; break; |
| 296 | case IcmpPacket::ReadReturnCode_INVALID_SIZE: return "invalid data size!"; break; |
| 297 | case IcmpPacket::ReadReturnCode_UNKNOWN_PROTOCOL: return "unknown protocol, expect ICMPv4/6!"; break; |
| 298 | case IcmpPacket::ReadReturnCode_UNKNOWN_ICMP_TYPE: |
| 299 | return "unknown icmp type, expect echo request/reply or destination unreachable!"; |
| 300 | break; |
| 301 | } |
| 302 | |
| 303 | // if nothing matched |
| 304 | return "unknown return code!"; |
| 305 | } |
| 306 | |
| 307 | /** |
| 308 | * @brief Write the ICMP packet to the @c ostream. |
| 309 | * |
| 310 | * the IP header is not written to stream! |
| 311 | * (is usually all 0s anyway) |
| 312 | * |
| 313 | * @param os The output stream. |
| 314 | * |
| 315 | * @return @c true if the write was successful, or @c false if an error occurred. |
| 316 | */ |
| 317 | bool IcmpPacket::write( std::ostream &os ) const |
| 318 | { |
| 319 | os.clear(); |
| 320 | |
| 321 | icmp_header.write(os); |
| 322 | icmp_data_ptr->write(os); |
| 323 | |
| 324 | return !os.fail(); |
| 325 | } |
| 326 | |
| 327 | /** @brief like write but also writes the IP header |
| 328 | * |
| 329 | * this is required in packet dumping for debugging |
| 330 | */ |
| 331 | bool IcmpPacket::dump( std::ostream &os ) const |
| 332 | { |
| 333 | os.clear(); |
| 334 | |
| 335 | ip_head_ptr->write(os); |
| 336 | icmp_header.write(os); |
| 337 | icmp_data_ptr->write(os); |
| 338 | |
| 339 | return !os.fail(); |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * @brief create packet from data in @a is; calls IcmpPacket::read |
| 344 | * |
| 345 | * @param is input stream with hopefully sufficient data |
| 346 | * |
| 347 | * @returns stream with less data; failbit is set if read returns other than ReadReturnCode_OK |
| 348 | */ |
| 349 | std::istream& operator>>( |
| 350 | std::istream &is, |
| 351 | IcmpPacket &packet |
| 352 | ) |
| 353 | { |
| 354 | IcmpPacket::ReadReturnCode return_code = packet.read(is); |
| 355 | if ( return_code != IcmpPacket::ReadReturnCode_OK ) |
| 356 | is.setstate( std::ios::failbit ); |
| 357 | |
| 358 | return is; |
| 359 | } |
| 360 | |
| 361 | |
| 362 | std::ostream& operator<<( |
| 363 | std::ostream& os, |
| 364 | const IcmpPacket& packet |
| 365 | ) |
| 366 | { |
| 367 | packet.write( os ); |
| 368 | return os; |
| 369 | } |
| 370 | |
| 371 | |
| 372 | /** |
| 373 | * @brief returns a short string representation of this packet |
| 374 | * |
| 375 | * @throws some kind of exception, possibly a runtime_error cause by a failed |
| 376 | * BOOST_ASSERT |
| 377 | */ |
| 378 | std::string IcmpPacket::to_string() const |
| 379 | { |
| 380 | std::stringstream buf; |
| 381 | buf << "[IcmpPacket: "; |
| 382 | if (ip_head_ptr) |
| 383 | buf << ip_head_ptr->to_string() << ", "; |
| 384 | else |
| 385 | buf << "no ip header, "; |
| 386 | |
| 387 | buf << icmp_header.to_string() << ", "; |
| 388 | |
| 389 | if (icmp_data_ptr) |
| 390 | buf << icmp_data_ptr->to_string(); |
| 391 | else |
| 392 | buf << "no icmp data"; |
| 393 | buf << "]"; |
| 394 | return buf.str(); |
| 395 | } |
| 396 | |