@@ -13,6 +13,11 @@ use std::{
1313 net:: { SocketAddr , SocketAddrV6 } ,
1414} ;
1515
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+
1621/// `IcmpSocket` that is very similiar to `UdpSocket`
1722#[ derive( Debug ) ]
1823pub struct IcmpSocket {
@@ -50,7 +55,7 @@ impl IcmpSocket {
5055
5156impl SocketTrait for IcmpSocket {
5257 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 ) ?;
5459 let mut to_addr = * to;
5560 // in linux `send_to` on icmpv6 socket requires destination port to be zero
5661 to_addr. set_port ( 0 ) ;
@@ -115,7 +120,7 @@ impl NonBlockingSocketTrait for NonBlockingIcmpSocket {
115120 let dst_addr = self
116121 . connected_addr
117122 . 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 ) ?;
119124 self . icmp_socket . socket . send ( & packet)
120125 }
121126
@@ -137,30 +142,64 @@ fn craft_icmp_packet(
137142 payload : & [ u8 ] ,
138143 source_addr : & SocketAddr ,
139144 dst_addr : & SocketAddr ,
145+ is_echo_request : bool ,
140146) -> 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+ }
144175 } ;
145176
146177 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)
149184 . to_bytes ( )
150185 . to_vec ( )
151186 } 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+ } ;
153192 let source_ip = as_socket_addr_v6 ( * source_addr) . ip ( ) . octets ( ) ;
154193 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)
156195 . map_err ( |_| Into :: < io:: Error > :: into ( io:: ErrorKind :: InvalidInput ) ) ?
157196 . to_bytes ( )
158197 . to_vec ( )
159198 } ;
160199
161200 let mut header_and_payload = Vec :: with_capacity ( icmp_header. len ( ) + payload. len ( ) ) ;
162201 header_and_payload. extend_from_slice ( & icmp_header) ;
163- header_and_payload. extend_from_slice ( payload) ;
202+ header_and_payload. extend_from_slice ( & payload) ;
164203 Ok ( header_and_payload)
165204}
166205
@@ -187,29 +226,64 @@ pub fn parse_icmp_packet(packet: &mut [u8], is_ipv6: bool) -> Option<IcmpPacket<
187226 } ;
188227
189228 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
191230 // packet we receive we just ignore it
192- let correct_icmp_type = if is_ipv6 {
231+ let echo_request = if is_ipv6 {
193232 etherparse:: icmpv6:: TYPE_ECHO_REQUEST
194233 } else {
195234 etherparse:: icmpv4:: TYPE_ECHO_REQUEST
196235 } ;
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 {
198247 return None ;
199248 }
200249
201250 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 ] ] ) ;
209253
210254 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) } ;
213287
214288 Some ( IcmpPacket {
215289 payload,
0 commit comments