extended ICMP packet dumping to parts after packet creation
[pingcheck] / src / icmp / icmppacket.cpp
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