extended ICMP packet dumping to parts after packet creation
[pingcheck] / src / icmp / icmppacket.cpp
CommitLineData
8ddcec43
GMF
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.
c120ad42
CH
19
20 Christian Herdtweck, Intra2net AG 2015
057e6cc9
CH
21 Based on an example in Boost Documentation (by Christopher M. Kohlhoff)
22 and adaptation by Guilherme M. Ferreira
8ddcec43
GMF
23 */
24
25#include "icmp/icmppacket.h"
c120ad42 26
059138a5 27#include <iostream>
747c13ca 28#include <sstream>
c120ad42 29#include <boost/scoped_array.hpp>
8ddcec43 30
c120ad42 31#include <logfunc.hpp>
8ddcec43 32
c120ad42 33#include "boost_assert_handler.h"
0ba8adc0
CH
34#include "ip/ipv4header.h"
35#include "ip/ipv6header.h"
c120ad42
CH
36#include "icmp/icmpechodata.h"
37#include "icmp/icmpdestinationunreachabledata.h"
15023b99 38#include "icmp/icmptimeexceededdata.h"
6d80c0be 39#include "icmp/icmppacketfactory.h"
c120ad42
CH
40
41
42using I2n::Logger::GlobalLogger;
8ddcec43 43
0ba8adc0
CH
44IcmpPacket::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:
7edd33cf
CH
54 {
55 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
0ba8adc0 56 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
7edd33cf 57 }
0ba8adc0
CH
58}
59
60// IP header is created with only 0s, is not used in write, anyway
61IcmpPacket::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:
7edd33cf
CH
73 {
74 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
0ba8adc0 75 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
7edd33cf 76 }
0ba8adc0
CH
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
aadc7032 83Icmpv4Type IcmpPacket::get_type_v4() const
0ba8adc0
CH
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
aadc7032 92Icmpv6Type IcmpPacket::get_type_v6() const
0ba8adc0
CH
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
aadc7032
CH
100uint8_t IcmpPacket::get_icmp_code() const
101{
102 return icmp_header.get_code();
103}
104
105std::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
111address IcmpPacket::get_source_address() const
112{
113 return ip_head_ptr->get_source_address();
114}
115
116address IcmpPacket::get_destination_address() const
117{
118 return ip_head_ptr->get_destination_address();
119}
120
0ba8adc0
CH
121
122bool IcmpPacket::check_integrity() { return false; } // not implemented yet
123
124bool IcmpPacket::match_destination_unreachable(const uint16_t identifier,
125 const uint16_t sequence_number,
126 const address &destination_address) const
127{
81c26517
CH
128 return icmp_data_ptr->match_destination_unreachable(identifier,
129 sequence_number,
130 destination_address);
0ba8adc0
CH
131}
132
15023b99
CH
133bool IcmpPacket::match_time_exceeded(const uint16_t identifier,
134 const uint16_t sequence_number,
135 const address &destination_address) const
136{
24fdf496
CH
137 return icmp_data_ptr->match_time_exceeded(identifier,
138 sequence_number,
139 destination_address);
15023b99
CH
140}
141
0ba8adc0
CH
142bool IcmpPacket::match_echo_reply(const uint16_t identifier,
143 const uint16_t sequence_number,
144 const address &destination_address) const
145{
aadc7032
CH
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
0ba8adc0
CH
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 */
167void 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
b6464c7f 179/**
c120ad42
CH
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}
b6464c7f 185 */
c120ad42 186IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is )
8ddcec43 187{
c120ad42
CH
188 if ( !is.good() )
189 return IcmpPacket::ReadReturnCode_BAD_STREAM;
190
191 // try to read ip header
747c13ca
CH
192 uint8_t version_with_ihl = static_cast<uint8_t>( is.peek() );
193 uint8_t version = (version_with_ihl & 0xF0) >> 4;
aadc7032 194 // ( this is the same as icmp_data_ptr->get_ip_version() )
747c13ca
CH
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 "
15023b99 200 << "match the one in input stream (" << static_cast<int>(version)
747c13ca
CH
201 << ")!" << std::endl;
202 BOOST_ASSERT( !"Inconsistent IP version!" );
203 }
c120ad42
CH
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 }
747c13ca
CH
216 else
217 { // pingcheck-type error throwing
218 GlobalLogger.error() << "Invalid IP version: "
219 << static_cast<int>(version) << "!" << std::endl;
c120ad42 220 BOOST_ASSERT( !"Invalid IP version!" );
747c13ca 221 }
c120ad42
CH
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
15023b99
CH
236 // could check validity of icmp type here
237
c120ad42 238 // calculate the size of the ICMP data
747c13ca
CH
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() );
c120ad42
CH
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)) );
15023b99
CH
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)) );
c120ad42 268 else
eb8ded5f
CH
269 { // unspecified icmp data type, will consume right amount of data
270 // from stream but return false for match_echo_request and
6d80c0be 271 // match_destination_unreachable
eb8ded5f
CH
272 icmp_data_ptr.reset( new IcmpData(
273 static_cast<std::size_t>(data_length)) );
274 }
c120ad42
CH
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;
8ddcec43 284}
059138a5 285
059138a5 286
d9bbc1d7
CH
287std::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;
c120ad42
CH
298 case IcmpPacket::ReadReturnCode_UNKNOWN_ICMP_TYPE:
299 return "unknown icmp type, expect echo request/reply or destination unreachable!";
300 break;
d9bbc1d7 301 }
059138a5 302
d9bbc1d7
CH
303 // if nothing matched
304 return "unknown return code!";
059138a5
CH
305}
306
307/**
c120ad42
CH
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 */
317bool 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
6d80c0be
CH
327/** @brief like write but also writes the IP header
328 *
329 * this is required in packet dumping for debugging
330 */
331bool 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
c120ad42 342/**
059138a5
CH
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 */
349std::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
362std::ostream& operator<<(
363 std::ostream& os,
364 const IcmpPacket& packet
365)
366{
367 packet.write( os );
368 return os;
369}
c120ad42 370
747c13ca 371
6d80c0be
CH
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 */
747c13ca
CH
378std::string IcmpPacket::to_string() const
379{
380 std::stringstream buf;
381 buf << "[IcmpPacket: ";
7edd33cf
CH
382 if (ip_head_ptr)
383 buf << ip_head_ptr->to_string() << ", ";
384 else
385 buf << "no ip header, ";
386
747c13ca 387 buf << icmp_header.to_string() << ", ";
7edd33cf
CH
388
389 if (icmp_data_ptr)
390 buf << icmp_data_ptr->to_string();
391 else
392 buf << "no icmp data";
747c13ca
CH
393 buf << "]";
394 return buf.str();
395}
396