remove the footer saying that vim is the best editor -- anyone knows anyway
[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"
c120ad42
CH
39
40
41using I2n::Logger::GlobalLogger;
8ddcec43 42
0ba8adc0
CH
43IcmpPacket::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:
7edd33cf
CH
53 {
54 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
0ba8adc0 55 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
7edd33cf 56 }
0ba8adc0
CH
57}
58
59// IP header is created with only 0s, is not used in write, anyway
60IcmpPacket::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:
7edd33cf
CH
72 {
73 GlobalLogger.error() << "Invalid IP version, need 4 or 6!";
0ba8adc0 74 BOOST_ASSERT( !"Invalid IP version, need 4 or 6!" );
7edd33cf 75 }
0ba8adc0
CH
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
aadc7032 82Icmpv4Type IcmpPacket::get_type_v4() const
0ba8adc0
CH
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
aadc7032 91Icmpv6Type IcmpPacket::get_type_v6() const
0ba8adc0
CH
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
aadc7032
CH
99uint8_t IcmpPacket::get_icmp_code() const
100{
101 return icmp_header.get_code();
102}
103
104std::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
110address IcmpPacket::get_source_address() const
111{
112 return ip_head_ptr->get_source_address();
113}
114
115address IcmpPacket::get_destination_address() const
116{
117 return ip_head_ptr->get_destination_address();
118}
119
0ba8adc0
CH
120
121bool IcmpPacket::check_integrity() { return false; } // not implemented yet
122
123bool IcmpPacket::match_destination_unreachable(const uint16_t identifier,
124 const uint16_t sequence_number,
125 const address &destination_address) const
126{
81c26517
CH
127 return icmp_data_ptr->match_destination_unreachable(identifier,
128 sequence_number,
129 destination_address);
0ba8adc0
CH
130}
131
15023b99
CH
132bool IcmpPacket::match_time_exceeded(const uint16_t identifier,
133 const uint16_t sequence_number,
134 const address &destination_address) const
135{
24fdf496
CH
136 return icmp_data_ptr->match_time_exceeded(identifier,
137 sequence_number,
138 destination_address);
15023b99
CH
139}
140
0ba8adc0
CH
141bool IcmpPacket::match_echo_reply(const uint16_t identifier,
142 const uint16_t sequence_number,
143 const address &destination_address) const
144{
aadc7032
CH
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
0ba8adc0
CH
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 */
166void 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
b6464c7f 178/**
c120ad42
CH
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}
b6464c7f 184 */
c120ad42 185IcmpPacket::ReadReturnCode IcmpPacket::read( std::istream &is )
8ddcec43 186{
c120ad42
CH
187 if ( !is.good() )
188 return IcmpPacket::ReadReturnCode_BAD_STREAM;
189
190 // try to read ip header
747c13ca
CH
191 uint8_t version_with_ihl = static_cast<uint8_t>( is.peek() );
192 uint8_t version = (version_with_ihl & 0xF0) >> 4;
aadc7032 193 // ( this is the same as icmp_data_ptr->get_ip_version() )
747c13ca
CH
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 "
15023b99 199 << "match the one in input stream (" << static_cast<int>(version)
747c13ca
CH
200 << ")!" << std::endl;
201 BOOST_ASSERT( !"Inconsistent IP version!" );
202 }
c120ad42
CH
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 }
747c13ca
CH
215 else
216 { // pingcheck-type error throwing
217 GlobalLogger.error() << "Invalid IP version: "
218 << static_cast<int>(version) << "!" << std::endl;
c120ad42 219 BOOST_ASSERT( !"Invalid IP version!" );
747c13ca 220 }
c120ad42
CH
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
15023b99
CH
235 // could check validity of icmp type here
236
c120ad42 237 // calculate the size of the ICMP data
747c13ca
CH
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() );
c120ad42
CH
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)) );
15023b99
CH
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)) );
c120ad42 267 else
eb8ded5f
CH
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 }
c120ad42
CH
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;
8ddcec43 283}
059138a5 284
059138a5 285
d9bbc1d7
CH
286std::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;
c120ad42
CH
297 case IcmpPacket::ReadReturnCode_UNKNOWN_ICMP_TYPE:
298 return "unknown icmp type, expect echo request/reply or destination unreachable!";
299 break;
d9bbc1d7 300 }
059138a5 301
d9bbc1d7
CH
302 // if nothing matched
303 return "unknown return code!";
059138a5
CH
304}
305
306/**
c120ad42
CH
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 */
316bool 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/**
059138a5
CH
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 */
333std::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
346std::ostream& operator<<(
347 std::ostream& os,
348 const IcmpPacket& packet
349)
350{
351 packet.write( os );
352 return os;
353}
c120ad42 354
747c13ca
CH
355
356std::string IcmpPacket::to_string() const
357{
358 std::stringstream buf;
359 buf << "[IcmpPacket: ";
7edd33cf
CH
360 if (ip_head_ptr)
361 buf << ip_head_ptr->to_string() << ", ";
362 else
363 buf << "no ip header, ";
364
747c13ca 365 buf << icmp_header.to_string() << ", ";
7edd33cf
CH
366
367 if (icmp_data_ptr)
368 buf << icmp_data_ptr->to_string();
369 else
370 buf << "no icmp data";
747c13ca
CH
371 buf << "]";
372 return buf.str();
373}
374