Update pingcheck to work with cmake 3.28
[pingcheck] / src / icmp / icmppacket.cpp
... / ...
CommitLineData
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
42using I2n::Logger::GlobalLogger;
43
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:
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
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:
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
83Icmpv4Type 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
92Icmpv6Type 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
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
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{
128 return icmp_data_ptr->match_destination_unreachable(identifier,
129 sequence_number,
130 destination_address);
131}
132
133bool 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
142bool 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 */
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
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 */
186IcmpPacket::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
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;
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 */
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
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
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 */
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}
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 */
378std::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