Commit | Line | Data |
---|---|---|
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 | ||
42 | using I2n::Logger::GlobalLogger; | |
8ddcec43 | 43 | |
0ba8adc0 CH |
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: | |
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 | |
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: | |
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 | 83 | Icmpv4Type 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 | 92 | Icmpv6Type 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 |
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 | ||
0ba8adc0 CH |
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 | { | |
81c26517 CH |
128 | return icmp_data_ptr->match_destination_unreachable(identifier, |
129 | sequence_number, | |
130 | destination_address); | |
0ba8adc0 CH |
131 | } |
132 | ||
15023b99 CH |
133 | bool 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 |
142 | bool 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 | */ | |
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 | ||
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 | 186 | IcmpPacket::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 |
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; | |
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 | */ | |
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 | ||
6d80c0be CH |
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 | ||
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 | */ | |
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 | } | |
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 |
378 | std::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 |