@@ -13,6 +13,11 @@ use std::{
13
13
net:: { SocketAddr , SocketAddrV6 } ,
14
14
} ;
15
15
16
+ /// magic bytes that are injected to end of icmp echo reply packets that
17
+ /// we craft and it get discarded later when parsing, it's purpose is to
18
+ /// detect automatic echo reply packets of kernel and ignore them
19
+ const ECHO_REPLY_MAGIC : [ u8 ; 3 ] = [ 0x24 , 0x74 , 0x33 ] ;
20
+
16
21
/// `IcmpSocket` that is very similiar to `UdpSocket`
17
22
#[ derive( Debug ) ]
18
23
pub struct IcmpSocket {
@@ -50,7 +55,7 @@ impl IcmpSocket {
50
55
51
56
impl SocketTrait for IcmpSocket {
52
57
fn send_to ( & self , buffer : & [ u8 ] , to : & SocketAddr ) -> io:: Result < usize > {
53
- let packet = craft_icmp_packet ( buffer, & self . udp_socket_addr , to) ?;
58
+ let packet = craft_icmp_packet ( buffer, & self . udp_socket_addr , to, false ) ?;
54
59
let mut to_addr = * to;
55
60
// in linux `send_to` on icmpv6 socket requires destination port to be zero
56
61
to_addr. set_port ( 0 ) ;
@@ -115,7 +120,7 @@ impl NonBlockingSocketTrait for NonBlockingIcmpSocket {
115
120
let dst_addr = self
116
121
. connected_addr
117
122
. ok_or_else ( || Into :: < io:: Error > :: into ( io:: ErrorKind :: NotConnected ) ) ?;
118
- let packet = craft_icmp_packet ( buffer, & self . icmp_socket . udp_socket_addr , & dst_addr) ?;
123
+ let packet = craft_icmp_packet ( buffer, & self . icmp_socket . udp_socket_addr , & dst_addr, true ) ?;
119
124
self . icmp_socket . socket . send ( & packet)
120
125
}
121
126
@@ -137,30 +142,64 @@ fn craft_icmp_packet(
137
142
payload : & [ u8 ] ,
138
143
source_addr : & SocketAddr ,
139
144
dst_addr : & SocketAddr ,
145
+ is_echo_request : bool ,
140
146
) -> io:: Result < Vec < u8 > > {
141
- let echo_header = IcmpEchoHeader {
142
- id : dst_addr. port ( ) ,
143
- seq : source_addr. port ( ) ,
147
+ // when we are sending echo reply we inject few magic bytes to the
148
+ // end of payload so when receiving reply packets we can determine
149
+ // if the echo reply packet is automatically sent from kernel
150
+ // (in case /proc/sys/net/ipv4/icmp_echo_ignore_all is not turned off)
151
+ // or we actually sent it
152
+ let payload = if !is_echo_request {
153
+ let payload_with_magic_len = payload. len ( ) + ECHO_REPLY_MAGIC . len ( ) ;
154
+ // TODO: this allocation is really bad, find another way for it
155
+ let mut buffer = vec ! [ 0u8 ; payload_with_magic_len] ;
156
+ buffer[ ..payload. len ( ) ] . copy_from_slice ( payload) ;
157
+ buffer[ payload. len ( ) ..] . copy_from_slice ( & ECHO_REPLY_MAGIC ) ;
158
+ buffer
159
+ } else {
160
+ payload. to_vec ( )
161
+ } ;
162
+
163
+ // read comments on `receiver::parse_icmp_packet` on why the
164
+ // source and destination place changes based on echo reply or request
165
+ let echo_header = if is_echo_request {
166
+ IcmpEchoHeader {
167
+ id : source_addr. port ( ) ,
168
+ seq : dst_addr. port ( ) ,
169
+ }
170
+ } else {
171
+ IcmpEchoHeader {
172
+ id : dst_addr. port ( ) ,
173
+ seq : source_addr. port ( ) ,
174
+ }
144
175
} ;
145
176
146
177
let icmp_header = if source_addr. is_ipv4 ( ) {
147
- let icmp_type = Icmpv4Type :: EchoRequest ( echo_header) ;
148
- Icmpv4Header :: with_checksum ( icmp_type, payload)
178
+ let icmp_type = if is_echo_request {
179
+ Icmpv4Type :: EchoRequest ( echo_header)
180
+ } else {
181
+ Icmpv4Type :: EchoReply ( echo_header)
182
+ } ;
183
+ Icmpv4Header :: with_checksum ( icmp_type, & payload)
149
184
. to_bytes ( )
150
185
. to_vec ( )
151
186
} else {
152
- let icmp_type = Icmpv6Type :: EchoRequest ( echo_header) ;
187
+ let icmp_type = if is_echo_request {
188
+ Icmpv6Type :: EchoRequest ( echo_header)
189
+ } else {
190
+ Icmpv6Type :: EchoReply ( echo_header)
191
+ } ;
153
192
let source_ip = as_socket_addr_v6 ( * source_addr) . ip ( ) . octets ( ) ;
154
193
let destination_ip = as_socket_addr_v6 ( * dst_addr) . ip ( ) . octets ( ) ;
155
- Icmpv6Header :: with_checksum ( icmp_type, source_ip, destination_ip, payload)
194
+ Icmpv6Header :: with_checksum ( icmp_type, source_ip, destination_ip, & payload)
156
195
. map_err ( |_| Into :: < io:: Error > :: into ( io:: ErrorKind :: InvalidInput ) ) ?
157
196
. to_bytes ( )
158
197
. to_vec ( )
159
198
} ;
160
199
161
200
let mut header_and_payload = Vec :: with_capacity ( icmp_header. len ( ) + payload. len ( ) ) ;
162
201
header_and_payload. extend_from_slice ( & icmp_header) ;
163
- header_and_payload. extend_from_slice ( payload) ;
202
+ header_and_payload. extend_from_slice ( & payload) ;
164
203
Ok ( header_and_payload)
165
204
}
166
205
@@ -187,29 +226,64 @@ pub fn parse_icmp_packet(packet: &mut [u8], is_ipv6: bool) -> Option<IcmpPacket<
187
226
} ;
188
227
189
228
let icmp = IcmpSlice :: from_slice ( is_ipv6, & packet[ payload_start_index..] ) ?;
190
- // we only work with icmp echo requests so if any other type of icmp
229
+ // we only work with icmp echo requests and replies so if any other type of icmp
191
230
// packet we receive we just ignore it
192
- let correct_icmp_type = if is_ipv6 {
231
+ let echo_request = if is_ipv6 {
193
232
etherparse:: icmpv6:: TYPE_ECHO_REQUEST
194
233
} else {
195
234
etherparse:: icmpv4:: TYPE_ECHO_REQUEST
196
235
} ;
197
- if icmp. type_u8 ( ) != correct_icmp_type || icmp. code_u8 ( ) != 0 {
236
+ let echo_reply = if is_ipv6 {
237
+ etherparse:: icmpv6:: TYPE_ECHO_REPLY
238
+ } else {
239
+ etherparse:: icmpv4:: TYPE_ECHO_REPLY
240
+ } ;
241
+
242
+ let is_echo_request = icmp. type_u8 ( ) == echo_request;
243
+ let is_echo_reply = icmp. type_u8 ( ) == echo_reply;
244
+
245
+ let is_correct_icmp_type = is_echo_request || is_echo_reply;
246
+ if !is_correct_icmp_type || icmp. code_u8 ( ) != 0 {
198
247
return None ;
199
248
}
200
249
201
250
let bytes5to8 = icmp. bytes5to8 ( ) ;
202
- // icmp is on layer 3 so it has no idea about ports
203
- // we use identification part of icmp packet as destination port
204
- // to identify packets that are really meant for us
205
- let dst_port = u16:: from_be_bytes ( [ bytes5to8[ 0 ] , bytes5to8[ 1 ] ] ) ;
206
-
207
- // we also use sequence part of icmp packet as source port
208
- let src_port = u16:: from_be_bytes ( [ bytes5to8[ 2 ] , bytes5to8[ 3 ] ] ) ;
251
+ let id = u16:: from_be_bytes ( [ bytes5to8[ 0 ] , bytes5to8[ 1 ] ] ) ;
252
+ let seq = u16:: from_be_bytes ( [ bytes5to8[ 2 ] , bytes5to8[ 3 ] ] ) ;
209
253
210
254
let payload_len = icmp. payload ( ) . len ( ) ;
211
- let total_len = packet. len ( ) ;
212
- let payload = & mut packet[ total_len - payload_len..] ;
255
+ let packet_len = packet. len ( ) ;
256
+
257
+ let payload = if is_echo_request {
258
+ & mut packet[ packet_len - payload_len..]
259
+ } else {
260
+ // filter the reply packets that doesn't have the magic bytes
261
+ let payload = & mut packet[ packet_len - payload_len..] ;
262
+ let magic_len = ECHO_REPLY_MAGIC . len ( ) ;
263
+ if payload_len < magic_len {
264
+ return None ;
265
+ }
266
+ if payload[ payload_len - magic_len..] != ECHO_REPLY_MAGIC {
267
+ return None ;
268
+ }
269
+ // striping magic bytes off the payload
270
+ & mut payload[ ..payload_len - magic_len]
271
+ } ;
272
+
273
+ // icmp is on layer 3 so it has no idea about ports so we use
274
+ // identification and sequence part of icmp packet as src and dst port
275
+ // but the problem is that if for example port 1010 sends a packet to 8000
276
+ // the id and seq is like this:
277
+ // | ID: 1010 | SEQ: 8000 |
278
+ // now if the server wants to send echo reply from 8000 to 1010 the packet
279
+ // will be like this:
280
+ // | ID: 8000 | SEQ: 1010 |
281
+ // because now the sender is 8000 and the receiver is 1010
282
+ // the important part is the ID, if the ID of echo reply is different than
283
+ // the echo request then NAT has no clue how to forward this packet, so we
284
+ // swap the id and seq position based on the packet being reply or request
285
+ // so the ID field will not get changed
286
+ let ( src_port, dst_port) = if is_echo_reply { ( seq, id) } else { ( id, seq) } ;
213
287
214
288
Some ( IcmpPacket {
215
289
payload,
0 commit comments