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"
41 using I2n::Logger::GlobalLogger;
43 IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol)
48 if ( icmp::v4() == protocol )
49 ip_head_ptr.reset( new Ipv4Header() );
50 else if ( icmp::v6() == protocol )
51 ip_head_ptr.reset( new Ipv6Header() );
52 else // pingcheck-type throwing of exceptions:
54 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
55 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
59 // IP header is created with only 0s, is not used in write, anyway
60 IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol,
61 const IcmpHeader &icmp_header_arg,
62 const IcmpDataPtr &icmp_data_arg)
64 , icmp_header( icmp_header_arg )
65 , icmp_data_ptr( icmp_data_arg )
67 if ( icmp::v4() == protocol )
68 ip_head_ptr.reset( new Ipv4Header() );
69 else if ( icmp::v6() == protocol )
70 ip_head_ptr.reset( new Ipv6Header() );
71 else // pingcheck-type throwing of exceptions:
73 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
74 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
78 icmp_header.calc_checksum( icmp_data_ptr->calc_checksum_part() );
81 // returns Icmpv4Type_InvalidLast if is actually v6 and not v4
82 Icmpv4Type IcmpPacket::get_type_v4() const
84 if (ip_head_ptr->get_version() == 4)
85 return static_cast<Icmpv4Type>( icmp_header.get_type() );
87 return Icmpv4Type_InvalidLast; // invalid
90 // returns Icmpv6Type_InvalidLast if is actually v4 and not v6
91 Icmpv6Type IcmpPacket::get_type_v6() const
93 if (ip_head_ptr->get_version() == 6)
94 return static_cast<Icmpv6Type>( icmp_header.get_type() );
96 return Icmpv6Type_InvalidLast; // invalid
99 uint8_t IcmpPacket::get_icmp_code() const
101 return icmp_header.get_code();
104 std::size_t IcmpPacket::get_icmp_size() const
106 return static_cast<std::size_t>(icmp_header.get_header_length())
107 + icmp_data_ptr->get_size();
110 address IcmpPacket::get_source_address() const
112 return ip_head_ptr->get_source_address();
115 address IcmpPacket::get_destination_address() const
117 return ip_head_ptr->get_destination_address();
121 bool IcmpPacket::check_integrity() { return false; } // not implemented yet
123 bool IcmpPacket::match_destination_unreachable(const uint16_t identifier,
124 const uint16_t sequence_number,
125 const address &destination_address) const
127 return icmp_data_ptr->match_destination_unreachable(identifier,
129 destination_address);
132 bool IcmpPacket::match_time_exceeded(const uint16_t identifier,
133 const uint16_t sequence_number,
134 const address &destination_address) const
136 return icmp_data_ptr->match_time_exceeded(identifier,
138 destination_address);
141 bool IcmpPacket::match_echo_reply(const uint16_t identifier,
142 const uint16_t sequence_number,
143 const address &destination_address) const
145 // ensure is not a EchoRequest but a EchoReply
146 int version = ip_head_ptr->get_version();
147 uint8_t type = icmp_header.get_type();
148 if (version == 4 && type != Icmpv4Type_EchoReply)
150 if (version == 6 && type != Icmpv6Type_EchoReply)
153 return ip_head_ptr->get_source_address() == destination_address
154 && icmp_data_ptr->match_echo_reply(identifier, sequence_number);
158 * @brief print echo reply / destination unreachable message depending on
161 * @param bytes_transferred Number of bytes transferred.
162 * @param time_packet_sent The time when this packet was sent.
166 void IcmpPacket::print( const size_t &bytes_transferred,
167 const boost::posix_time::ptime &time_packet_sent ) const
169 size_t bytes_received = bytes_transferred
170 - ip_head_ptr->get_header_length();
171 std::string remote_address
172 = ip_head_ptr->get_source_address().to_string();
173 uint32_t ttl = ip_head_ptr->get_time_to_live();
174 icmp_data_ptr->print( bytes_received, time_packet_sent,
175 remote_address, ttl);
179 * @brief Read (part of) the ICMP packet from the input stream @a is
181 * @param is The input stream.
183 * @return result of the read, currently one of {ok, fail, not enough data}
185 IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is )
188 return IcmpPacket::ReadReturnCode_BAD_STREAM;
190 // try to read ip header
191 uint8_t version_with_ihl = static_cast<uint8_t>( is.peek() );
192 uint8_t version = (version_with_ihl & 0xF0) >> 4;
193 // ( this is the same as icmp_data_ptr->get_ip_version() )
195 if ( version != ip_head_ptr->get_version() )
197 GlobalLogger.error() << "IP version given to constructor ("
198 << static_cast<int>(ip_head_ptr->get_version()) << ") does not "
199 << "match the one in input stream (" << static_cast<int>(version)
200 << ")!" << std::endl;
201 BOOST_ASSERT( !"Inconsistent IP version!" );
205 Ipv4Header *new_header = new Ipv4Header();
207 ip_head_ptr.reset( new_header );
209 else if (version == 6)
211 Ipv6Header *new_header = new Ipv6Header();
213 ip_head_ptr.reset( new_header );
216 { // pingcheck-type error throwing
217 GlobalLogger.error() << "Invalid IP version: "
218 << static_cast<int>(version) << "!" << std::endl;
219 BOOST_ASSERT( !"Invalid IP version!" );
223 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
224 else if ( !is.good() )
225 return IcmpPacket::ReadReturnCode_BAD_STREAM;
227 // try to read the icmp header
228 icmp_header.read(is);
231 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
232 else if ( !is.good() )
233 return IcmpPacket::ReadReturnCode_BAD_STREAM;
235 // could check validity of icmp type here
237 // calculate the size of the ICMP data
238 std::streamsize data_length =
239 static_cast<std::streamsize>( ip_head_ptr->get_total_length() )
240 - static_cast<std::streamsize>( ip_head_ptr->get_header_length() )
241 - static_cast<std::streamsize>( icmp_header.get_header_length() );
243 // try to read that amount of data
244 if ( data_length < 0 )
246 GlobalLogger.error() << "Invalid size for optional ICMP data: "
247 << data_length << std::endl;
248 is.setstate( std::ios::failbit );
249 return IcmpPacket::ReadReturnCode_INVALID_SIZE;
251 else if ( data_length > 0 )
253 if ( get_type_v4() == Icmpv4Type_EchoRequest
254 || get_type_v4() == Icmpv4Type_EchoReply
255 || get_type_v6() == Icmpv6Type_EchoRequest
256 || get_type_v6() == Icmpv6Type_EchoReply )
257 icmp_data_ptr.reset( new IcmpEchoData(
258 static_cast<std::size_t>(data_length)) );
259 else if (get_type_v4() == Icmpv4Type_DestinationUnreachable
260 || get_type_v6() == Icmpv6Type_DestinationUnreachable)
261 icmp_data_ptr.reset( new IcmpDestinationUnreachableData(
262 static_cast<std::size_t>(data_length)) );
263 else if (get_type_v4() == Icmpv4Type_TimeExceeded
264 || get_type_v6() == Icmpv6Type_TimeExceeded)
265 icmp_data_ptr.reset( new IcmpTimeExceededData(
266 static_cast<std::size_t>(data_length)) );
268 { // unspecified icmp data type, will consume right amount of data
269 // from stream but return false for match_echo_request and
270 // match_destination_unreachable
271 icmp_data_ptr.reset( new IcmpData(
272 static_cast<std::size_t>(data_length)) );
274 icmp_data_ptr->read( is );
278 return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
279 else if ( !is.good() )
280 return IcmpPacket::ReadReturnCode_BAD_STREAM;
282 return IcmpPacket::ReadReturnCode_OK;
286 std::string IcmpPacket::return_code_to_string( const IcmpPacket::ReadReturnCode &code )
290 case IcmpPacket::ReadReturnCode_OK: return "ok"; break;
291 case IcmpPacket::ReadReturnCode_UNSPECIFIED: return "return code not set yet!"; break;
292 case IcmpPacket::ReadReturnCode_FAIL: return "failed for unspecified reason!"; break;
293 case IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA: return "not enough data!"; break;
294 case IcmpPacket::ReadReturnCode_BAD_STREAM: return "bad stream!"; break;
295 case IcmpPacket::ReadReturnCode_INVALID_SIZE: return "invalid data size!"; break;
296 case IcmpPacket::ReadReturnCode_UNKNOWN_PROTOCOL: return "unknown protocol, expect ICMPv4/6!"; break;
297 case IcmpPacket::ReadReturnCode_UNKNOWN_ICMP_TYPE:
298 return "unknown icmp type, expect echo request/reply or destination unreachable!";
302 // if nothing matched
303 return "unknown return code!";
307 * @brief Write the ICMP packet to the @c ostream.
309 * the IP header is not written to stream!
310 * (is usually all 0s anyway)
312 * @param os The output stream.
314 * @return @c true if the write was successful, or @c false if an error occurred.
316 bool IcmpPacket::write( std::ostream &os ) const
320 icmp_header.write(os);
321 icmp_data_ptr->write(os);
327 * @brief create packet from data in @a is; calls IcmpPacket::read
329 * @param is input stream with hopefully sufficient data
331 * @returns stream with less data; failbit is set if read returns other than ReadReturnCode_OK
333 std::istream& operator>>(
338 IcmpPacket::ReadReturnCode return_code = packet.read(is);
339 if ( return_code != IcmpPacket::ReadReturnCode_OK )
340 is.setstate( std::ios::failbit );
346 std::ostream& operator<<(
348 const IcmpPacket& packet
356 std::string IcmpPacket::to_string() const
358 std::stringstream buf;
359 buf << "[IcmpPacket: ";
361 buf << ip_head_ptr->to_string() << ", ";
363 buf << "no ip header, ";
365 buf << icmp_header.to_string() << ", ";
368 buf << icmp_data_ptr->to_string();
370 buf << "no icmp data";