remove the footer saying that vim is the best editor -- anyone knows anyway
[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
40
41 using I2n::Logger::GlobalLogger;
42
43 IcmpPacket::IcmpPacket(const icmp::socket::protocol_type &protocol)
44     : ip_head_ptr()
45     , icmp_header()
46     , icmp_data_ptr()
47 {
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:
53     {
54         GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
55         BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
56     }
57 }
58
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)
63     : ip_head_ptr()
64     , icmp_header( icmp_header_arg )
65     , icmp_data_ptr( icmp_data_arg )
66 {
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:
72     {
73         GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
74         BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
75     }
76
77     // create checksum
78     icmp_header.calc_checksum( icmp_data_ptr->calc_checksum_part() );
79 }
80
81 // returns Icmpv4Type_InvalidLast if is actually v6 and not v4
82 Icmpv4Type IcmpPacket::get_type_v4() const
83 {
84     if (ip_head_ptr->get_version() == 4)
85         return static_cast<Icmpv4Type>( icmp_header.get_type() );
86     else
87         return Icmpv4Type_InvalidLast;   // invalid
88 }
89
90 // returns Icmpv6Type_InvalidLast if is actually v4 and not v6
91 Icmpv6Type IcmpPacket::get_type_v6() const
92 {
93     if (ip_head_ptr->get_version() == 6)
94         return static_cast<Icmpv6Type>( icmp_header.get_type() );
95     else
96         return Icmpv6Type_InvalidLast;   // invalid
97 }
98
99 uint8_t IcmpPacket::get_icmp_code() const
100 {
101     return icmp_header.get_code();
102 }
103
104 std::size_t IcmpPacket::get_icmp_size() const
105 {
106     return static_cast<std::size_t>(icmp_header.get_header_length())
107              + icmp_data_ptr->get_size();
108 }
109
110 address IcmpPacket::get_source_address() const
111 {
112     return ip_head_ptr->get_source_address();
113 }
114
115 address IcmpPacket::get_destination_address() const
116 {
117     return ip_head_ptr->get_destination_address();
118 }
119
120
121 bool IcmpPacket::check_integrity() { return false; } // not implemented yet
122
123 bool IcmpPacket::match_destination_unreachable(const uint16_t identifier,
124                                    const uint16_t sequence_number,
125                                    const address &destination_address) const
126 {
127     return icmp_data_ptr->match_destination_unreachable(identifier,
128                                                         sequence_number,
129                                                         destination_address);
130 }
131
132 bool IcmpPacket::match_time_exceeded(const uint16_t identifier,
133                                      const uint16_t sequence_number,
134                                      const address &destination_address) const
135 {
136     return icmp_data_ptr->match_time_exceeded(identifier,
137                                               sequence_number,
138                                               destination_address);
139 }
140
141 bool IcmpPacket::match_echo_reply(const uint16_t identifier,
142                       const uint16_t sequence_number,
143                       const address &destination_address) const
144 {
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)
149         return false;
150     if (version == 6 && type != Icmpv6Type_EchoReply)
151         return false;
152
153     return ip_head_ptr->get_source_address() == destination_address
154        &&  icmp_data_ptr->match_echo_reply(identifier, sequence_number);
155 }
156
157 /**
158  * @brief print echo reply / destination unreachable message depending on
159  *    ICMP data type
160  *
161  * @param bytes_transferred Number of bytes transferred.
162  * @param time_packet_sent The time when this packet was sent.
163  *
164  * @return void
165  */
166 void IcmpPacket::print( const size_t &bytes_transferred,
167             const boost::posix_time::ptime &time_packet_sent    ) const
168 {
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);
176 }
177
178 /**
179  * @brief Read (part of) the ICMP packet from the input stream @a is
180  *
181  * @param is The input stream.
182  *
183  * @return result of the read, currently one of {ok, fail, not enough data}
184  */
185 IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is )
186 {
187     if ( !is.good() )
188         return IcmpPacket::ReadReturnCode_BAD_STREAM;
189
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() )
194
195     if ( version != ip_head_ptr->get_version() )
196     {
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!" );
202     }
203     if (version == 4)
204     {
205         Ipv4Header *new_header = new Ipv4Header();
206         is >> *new_header;
207         ip_head_ptr.reset( new_header );
208     }
209     else if (version == 6)
210     {
211         Ipv6Header *new_header = new Ipv6Header();
212         is >> *new_header;
213         ip_head_ptr.reset( new_header );
214     }
215     else
216     {   // pingcheck-type error throwing
217         GlobalLogger.error() << "Invalid IP version: "
218                              << static_cast<int>(version) << "!" << std::endl;
219         BOOST_ASSERT( !"Invalid IP version!" );
220     }
221
222     if ( is.eof() )
223         return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
224     else if ( !is.good() )
225         return IcmpPacket::ReadReturnCode_BAD_STREAM;
226
227     // try to read the icmp header
228     icmp_header.read(is);
229
230     if ( is.eof() )
231         return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
232     else if ( !is.good() )
233         return IcmpPacket::ReadReturnCode_BAD_STREAM;
234
235     // could check validity of icmp type here
236
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() );
242
243     // try to read that amount of data
244     if ( data_length < 0 )
245     {
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;
250     }
251     else if ( data_length > 0 )
252     {
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)) );
267         else
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)) );
273         }
274         icmp_data_ptr->read( is );
275     }
276
277     if ( is.eof() )
278         return IcmpPacket::ReadReturnCode_NOT_ENOUGH_DATA;
279     else if ( !is.good() )
280         return IcmpPacket::ReadReturnCode_BAD_STREAM;
281     else
282         return IcmpPacket::ReadReturnCode_OK;
283 }
284
285
286 std::string IcmpPacket::return_code_to_string( const IcmpPacket::ReadReturnCode &code )
287 {
288     switch (code)
289     {
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!";
299       break;
300     }
301
302     // if nothing matched
303     return "unknown return code!";
304 }
305
306 /**
307  * @brief Write the ICMP packet to the @c ostream.
308  *
309  * the IP header is not written to stream!
310  * (is usually all 0s anyway)
311  *
312  * @param os The output stream.
313  *
314  * @return @c true if the write was successful, or @c false if an error occurred.
315  */
316 bool IcmpPacket::write( std::ostream &os ) const
317 {
318     os.clear();
319
320     icmp_header.write(os);
321     icmp_data_ptr->write(os);
322
323     return !os.fail();
324 }
325
326 /**
327  * @brief create packet from data in @a is;  calls IcmpPacket::read
328  *
329  * @param is input stream with hopefully sufficient data
330  *
331  * @returns stream with less data; failbit is set if read returns other than ReadReturnCode_OK
332  */
333 std::istream& operator>>(
334         std::istream &is,
335         IcmpPacket &packet
336 )
337 {
338     IcmpPacket::ReadReturnCode return_code = packet.read(is);
339     if ( return_code != IcmpPacket::ReadReturnCode_OK )
340         is.setstate( std::ios::failbit );
341
342     return is;
343 }
344
345
346 std::ostream& operator<<(
347         std::ostream& os,
348         const IcmpPacket& packet
349 )
350 {
351     packet.write( os );
352     return os;
353 }
354
355
356 std::string IcmpPacket::to_string() const
357 {
358     std::stringstream buf;
359     buf << "[IcmpPacket: ";
360     if (ip_head_ptr)
361         buf << ip_head_ptr->to_string() << ", ";
362     else
363         buf << "no ip header, ";
364
365     buf << icmp_header.to_string() << ", ";
366
367     if (icmp_data_ptr)
368         buf << icmp_data_ptr->to_string();
369     else
370         buf << "no icmp data";
371     buf << "]";
372     return buf.str();
373 }
374