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" |
c120ad42 CH |
39 | |
40 | ||
41 | using I2n::Logger::GlobalLogger; | |
8ddcec43 | 42 | |
0ba8adc0 CH |
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: | |
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 | |
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: | |
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 | 82 | Icmpv4Type 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 | 91 | Icmpv6Type 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 |
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 | ||
0ba8adc0 CH |
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 | { | |
81c26517 CH |
127 | return icmp_data_ptr->match_destination_unreachable(identifier, |
128 | sequence_number, | |
129 | destination_address); | |
0ba8adc0 CH |
130 | } |
131 | ||
15023b99 CH |
132 | bool 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 |
141 | bool 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 | */ | |
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 | ||
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 | 185 | IcmpPacket::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 |
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; | |
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 | */ | |
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 | /** | |
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 | */ | |
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 | } | |
c120ad42 | 354 | |
747c13ca CH |
355 | |
356 | std::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 |