2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
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
25 #include "icmp/icmppacket.h"
29 #include <boost/scoped_array.hpp>
31 #include <logfunc.hpp>
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"
42 using I2n::Logger::GlobalLogger;
44 IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol)
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:
55 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
56 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
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)
65 , icmp_header( icmp_header_arg )
66 , icmp_data_ptr( icmp_data_arg )
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:
74 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
75 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
79 icmp_header.calc_checksum( icmp_data_ptr->calc_checksum_part() );
82 // returns Icmpv4Type_InvalidLast if is actually v6 and not v4
83 Icmpv4Type IcmpPacket::get_type_v4() const
85 if (ip_head_ptr->get_version() == 4)
86 return static_cast<Icmpv4Type>( icmp_header.get_type() );
88 return Icmpv4Type_InvalidLast; // invalid
91 // returns Icmpv6Type_InvalidLast if is actually v4 and not v6
92 Icmpv6Type IcmpPacket::get_type_v6() const
94 if (ip_head_ptr->get_version() == 6)
95 return static_cast<Icmpv6Type>( icmp_header.get_type() );
97 return Icmpv6Type_InvalidLast; // invalid
100 uint8_t IcmpPacket::get_icmp_code() const
102 return icmp_header.get_code();
105 std::size_t IcmpPacket::get_icmp_size() const
107 return static_cast<std::size_t>(icmp_header.get_header_length())
108 + icmp_data_ptr->get_size();
111 address IcmpPacket::get_source_address() const
113 return ip_head_ptr->get_source_address();
116 address IcmpPacket::get_destination_address() const
118 return ip_head_ptr->get_destination_address();
122 bool IcmpPacket::check_integrity() { return false; } // not implemented yet
124 bool IcmpPacket::match_destination_unreachable(const uint16_t identifier,
125 const uint16_t sequence_number,
126 const address &destination_address) const
128 return icmp_data_ptr->match_destination_unreachable(identifier,
130 destination_address);
133 bool IcmpPacket::match_time_exceeded(const uint16_t identifier,
134 const uint16_t sequence_number,
135 const address &destination_address) const
137 return icmp_data_ptr->match_time_exceeded(identifier,
139 destination_address);
142 bool IcmpPacket::match_echo_reply(const uint16_t identifier,
143 const uint16_t sequence_number,
144 const address &destination_address) const
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)
151 if (version == 6 && type != Icmpv6Type_EchoReply)
154 return ip_head_ptr->get_source_address() == destination_address
155 && icmp_data_ptr->match_echo_reply(identifier, sequence_number);
159 * @brief print echo reply / destination unreachable message depending on
162 * @param bytes_transferred Number of bytes transferred.
163 * @param time_packet_sent The time when this packet was sent.
167 void IcmpPacket::print( const size_t &bytes_transferred,
168 const boost::posix_time::ptime &time_packet_sent ) const
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);
180 * @brief Read (part of) the ICMP packet from the input stream @a is
182 * @param is The input stream.
184 * @return result of the read, currently one of {ok, fail, not enough data}
186 IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is )
189 return IcmpPacket::ReadReturnCode_BAD_STREAM;
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() )
196 if ( version != ip_head_ptr->get_version() )
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!" );
206 Ipv4Header *new_header = new Ipv4Header();
208 ip_head_ptr.reset( new_header );
210 else if (version == 6)
212 Ipv6Header *new_header = new Ipv6Header();
214 ip_head_ptr.reset( new_header );
217 { // pingcheck-type error throwing
218 GlobalLogger.error() << "Invalid IP version: "
219 << static_cast<int>(version) << "!" << std::endl;
220 BOOST_ASSERT( !"Invalid IP version!" );
224 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
225 else if ( !is.good() )
226 return IcmpPacket::ReadReturnCode_BAD_STREAM;
228 // try to read the icmp header
229 icmp_header.read(is);
232 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
233 else if ( !is.good() )
234 return IcmpPacket::ReadReturnCode_BAD_STREAM;
236 // could check validity of icmp type here
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() );
244 // try to read that amount of data
245 if ( data_length < 0 )
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;
252 else if ( data_length > 0 )
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)) );
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)) );
275 icmp_data_ptr->read( is );
279 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
280 else if ( !is.good() )
281 return IcmpPacket::ReadReturnCode_BAD_STREAM;
283 return IcmpPacket::ReadReturnCode_OK;
287 std::string IcmpPacket::return_code_to_string( const IcmpPacket::ReadReturnCode &code )
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!";
303 // if nothing matched
304 return "unknown return code!";
308 * @brief Write the ICMP packet to the @c ostream.
310 * the IP header is not written to stream!
311 * (is usually all 0s anyway)
313 * @param os The output stream.
315 * @return @c true if the write was successful, or @c false if an error occurred.
317 bool IcmpPacket::write( std::ostream &os ) const
321 icmp_header.write(os);
322 icmp_data_ptr->write(os);
327 /** @brief like write but also writes the IP header
329 * this is required in packet dumping for debugging
331 bool IcmpPacket::dump( std::ostream &os ) const
335 ip_head_ptr->write(os);
336 icmp_header.write(os);
337 icmp_data_ptr->write(os);
343 * @brief create packet from data in @a is; calls IcmpPacket::read
345 * @param is input stream with hopefully sufficient data
347 * @returns stream with less data; failbit is set if read returns other than ReadReturnCode_OK
349 std::istream& operator>>(
354 IcmpPacket::ReadReturnCode return_code = packet.read(is);
355 if ( return_code != IcmpPacket::ReadReturnCode_OK )
356 is.setstate( std::ios::failbit );
362 std::ostream& operator<<(
364 const IcmpPacket& packet
373 * @brief returns a short string representation of this packet
375 * @throws some kind of exception, possibly a runtime_error cause by a failed
378 std::string IcmpPacket::to_string() const
380 std::stringstream buf;
381 buf << "[IcmpPacket: ";
383 buf << ip_head_ptr->to_string() << ", ";
385 buf << "no ip header, ";
387 buf << icmp_header.to_string() << ", ";
390 buf << icmp_data_ptr->to_string();
392 buf << "no icmp data";