1
1
use std:: {
2
2
ascii:: escape_default,
3
- ffi:: c_void,
4
3
io,
5
4
net:: { IpAddr , Ipv4Addr } ,
6
5
ops:: { Range , RangeFrom } ,
7
- os:: fd:: { AsFd , AsRawFd , FromRawFd , IntoRawFd } ,
6
+ os:: fd:: { FromRawFd , IntoRawFd } ,
8
7
time:: Duration ,
9
8
} ;
10
9
11
10
use eyre:: { bail, ensure, eyre, OptionExt , WrapErr } ;
12
11
use futures:: { future:: pending, stream, StreamExt , TryFutureExt , TryStreamExt } ;
13
12
use match_cfg:: match_cfg;
14
- use nix:: libc:: setsockopt;
15
13
use pnet_packet:: {
16
14
icmp:: {
17
15
echo_request:: EchoRequestPacket , time_exceeded:: TimeExceededPacket , IcmpPacket , IcmpTypes ,
@@ -106,48 +104,59 @@ pub async fn try_run_leak_test(opt: &TracerouteOpt) -> eyre::Result<LeakStatus>
106
104
. set_nonblocking ( true )
107
105
. wrap_err ( "Failed to set icmp_socket to nonblocking" ) ?;
108
106
109
- let n = 1 ;
110
- unsafe {
111
- setsockopt (
112
- icmp_socket. as_fd ( ) . as_raw_fd ( ) ,
113
- nix:: libc:: SOL_IP ,
114
- nix:: libc:: IP_RECVERR ,
115
- & n as * const _ as * const c_void ,
116
- size_of_val ( & n) as u32 ,
117
- )
118
- } ;
107
+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
108
+ {
109
+ use std:: ffi:: c_void;
110
+ use std:: os:: fd:: { AsFd , AsRawFd } ;
111
+
112
+ let n = 1 ;
113
+ unsafe {
114
+ setsockopt (
115
+ icmp_socket. as_fd ( ) . as_raw_fd ( ) ,
116
+ nix:: libc:: SOL_IP ,
117
+ nix:: libc:: IP_RECVERR ,
118
+ & n as * const _ as * const std:: ffi:: c_void ,
119
+ size_of_val ( & n) as u32 ,
120
+ )
121
+ } ;
122
+ }
119
123
120
124
bind_socket_to_interface ( & icmp_socket, & opt. interface ) ?;
121
125
122
126
// HACK: Wrap the socket in a tokio::net::UdpSocket to be able to use it async
123
127
// SAFETY: `into_raw_fd()` consumes the socket and returns an owned & open file descriptor.
124
128
let icmp_socket = unsafe { std:: net:: UdpSocket :: from_raw_fd ( icmp_socket. into_raw_fd ( ) ) } ;
125
- let icmp_socket = UdpSocket :: from_std ( icmp_socket) ?;
129
+ let mut icmp_socket = UdpSocket :: from_std ( icmp_socket) ?;
126
130
127
131
// on Windows, we need to do some additional configuration of the raw socket
128
132
#[ cfg( target_os = "windows" ) ]
129
133
configure_listen_socket ( & icmp_socket, interface) ?;
130
134
131
- // create the socket used for sending the UDP probing packets
132
- let udp_socket = Socket :: new ( Domain :: IPV4 , Type :: DGRAM , Some ( Protocol :: UDP ) )
133
- . wrap_err ( "Failed to open UDP socket" ) ?;
134
- bind_socket_to_interface ( & udp_socket, & opt. interface )
135
- . wrap_err ( "Failed to bind UDP socket to interface" ) ?;
136
- udp_socket
137
- . set_nonblocking ( true )
138
- . wrap_err ( "Failed to set udp_socket to nonblocking" ) ?;
139
-
140
- // HACK: Wrap the socket in a tokio::net::UdpSocket to be able to use it async
141
- // SAFETY: `into_raw_fd()` consumes the socket and returns an owned & open file descriptor.
142
- let udp_socket = unsafe { std:: net:: UdpSocket :: from_raw_fd ( udp_socket. into_raw_fd ( ) ) } ;
143
- let udp_socket = UdpSocket :: from_std ( udp_socket) ?;
144
- drop ( udp_socket) ;
145
-
146
- let mut icmp_socket = icmp_socket;
147
- timeout ( SEND_TIMEOUT , send_icmp_probes ( & mut icmp_socket, opt) )
148
- . map_err ( |_timeout| eyre ! ( "Timed out while trying to send probe packet" ) )
149
- . await ??;
135
+ if opt. icmp {
136
+ timeout ( SEND_TIMEOUT , send_icmp_probes ( & mut icmp_socket, opt) )
137
+ . map_err ( |_timeout| eyre ! ( "Timed out while trying to send probe packet" ) )
138
+ . await ??;
139
+ } else {
140
+ // create the socket used for sending the UDP probing packets
141
+ let udp_socket = Socket :: new ( Domain :: IPV4 , Type :: DGRAM , Some ( Protocol :: UDP ) )
142
+ . wrap_err ( "Failed to open UDP socket" ) ?;
143
+ bind_socket_to_interface ( & udp_socket, & opt. interface )
144
+ . wrap_err ( "Failed to bind UDP socket to interface" ) ?;
145
+ udp_socket
146
+ . set_nonblocking ( true )
147
+ . wrap_err ( "Failed to set udp_socket to nonblocking" ) ?;
148
+
149
+ // HACK: Wrap the socket in a tokio::net::UdpSocket to be able to use it async
150
+ // SAFETY: `into_raw_fd()` consumes the socket and returns an owned & open file descriptor.
151
+ let udp_socket = unsafe { std:: net:: UdpSocket :: from_raw_fd ( udp_socket. into_raw_fd ( ) ) } ;
152
+ let mut udp_socket = UdpSocket :: from_std ( udp_socket) ?;
153
+
154
+ timeout ( SEND_TIMEOUT , send_udp_probes ( & mut udp_socket, opt) )
155
+ . map_err ( |_timeout| eyre ! ( "Timed out while trying to send probe packet" ) )
156
+ . await ??;
157
+ }
150
158
159
+ //let recv_task = read_probe_responses(&opt.interface, icmp_socket);
151
160
let recv_task = read_probe_responses ( & opt. interface , icmp_socket) ;
152
161
153
162
// wait until either task exits, or the timeout is reached
@@ -205,8 +214,6 @@ async fn send_icmp_probes(socket: &mut UdpSocket, opt: &TracerouteOpt) -> eyre::
205
214
packet. populate ( & echo) ;
206
215
packet. set_checksum ( checksum ( & IcmpPacket :: new ( packet. packet ( ) ) . unwrap ( ) ) ) ;
207
216
208
- log:: error!( "echo packet: {:02x?}" , packet. packet( ) ) ;
209
-
210
217
let result: io:: Result < ( ) > = stream:: iter ( 0 ..number_of_sends)
211
218
// call `send_to` `number_of_sends` times
212
219
. then ( |_| socket. send_to ( & packet. packet ( ) , ( opt. destination , port) ) )
@@ -227,7 +234,7 @@ async fn send_icmp_probes(socket: &mut UdpSocket, opt: &TracerouteOpt) -> eyre::
227
234
Ok ( ( ) )
228
235
}
229
236
230
- async fn send_udp_probes ( socket : UdpSocket , opt : & TracerouteOpt ) -> eyre:: Result < ( ) > {
237
+ async fn send_udp_probes ( socket : & mut UdpSocket , opt : & TracerouteOpt ) -> eyre:: Result < ( ) > {
231
238
// ensure we don't send anything to `opt.exclude_port`
232
239
let ports = DEFAULT_PORT_RANGE
233
240
// skip the excluded port
@@ -265,6 +272,120 @@ async fn send_udp_probes(socket: UdpSocket, opt: &TracerouteOpt) -> eyre::Result
265
272
Ok ( ( ) )
266
273
}
267
274
275
+ /// Experimental PoC of a linux implementation that doesn't need root.
276
+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
277
+ #[ allow( dead_code) ]
278
+ async fn read_probe_responses_no_root (
279
+ _interface : & str ,
280
+ socket : UdpSocket ,
281
+ ) -> eyre:: Result < LeakStatus > {
282
+ use nix:: libc:: { errno:: Errno , libc:: setsockopt, setsockopt, sock_extended_err} ;
283
+ use std:: ffi:: c_void;
284
+ use std:: mem:: transmute;
285
+ use std:: os:: fd:: AsRawFd ;
286
+
287
+ // the list of node IP addresses from which we received a response to our probe packets.
288
+ let mut reachable_nodes = vec ! [ ] ;
289
+
290
+ let mut read_buf = vec ! [ 0u8 ; usize :: from( u16 :: MAX ) ] . into_boxed_slice ( ) ;
291
+ loop {
292
+ log:: debug!( "Reading from ICMP socket" ) ;
293
+
294
+ // XXX: only works for ipv4
295
+ let mut msg_name: nix:: libc:: sockaddr_in = unsafe { std:: mem:: zeroed ( ) } ;
296
+ let mut msg_iov = vec ! [ nix:: libc:: iovec {
297
+ iov_base: read_buf. as_mut_ptr( ) as * mut _,
298
+ iov_len: read_buf. len( ) ,
299
+ } ] ;
300
+ let mut msg_control = vec ! [ 0u8 ; 2048 ] ;
301
+
302
+ let mut msg_header = nix:: libc:: msghdr {
303
+ msg_name : & mut msg_name as * mut _ as * mut c_void ,
304
+ msg_namelen : size_of_val ( & msg_name) as u32 ,
305
+ msg_iov : msg_iov. as_mut_ptr ( ) as * mut _ ,
306
+ msg_iovlen : msg_iov. len ( ) ,
307
+ msg_control : msg_control. as_mut_ptr ( ) as * mut _ ,
308
+ msg_controllen : msg_control. len ( ) ,
309
+ msg_flags : 0 ,
310
+ } ;
311
+ log:: debug!( "header: {msg_header:?}" ) ;
312
+
313
+ // Calling recvmsg with MSG_ERRQUEUE will prompt linux to tell us if we get any ICMP errorr
314
+ // replies to our Echos.
315
+ let flags = nix:: libc:: MSG_ERRQUEUE ;
316
+ let n = loop {
317
+ match unsafe { nix:: libc:: recvmsg ( socket. as_raw_fd ( ) , & mut msg_header, flags) } {
318
+ ..0 => match nix:: errno:: Errno :: last ( ) {
319
+ nix:: errno:: Errno :: EWOULDBLOCK => {
320
+ sleep ( Duration :: from_millis ( 10 ) ) . await ;
321
+ continue ;
322
+ }
323
+ e => bail ! ( "Faileed to read from socket {e}" ) ,
324
+ } ,
325
+ n => break n as usize ,
326
+ }
327
+ } ;
328
+
329
+ log:: debug!( "header after: {msg_header:?}" ) ;
330
+ msg_iov. truncate ( msg_header. msg_iovlen ) ;
331
+ msg_control. truncate ( msg_header. msg_controllen ) ;
332
+ let _ = msg_header;
333
+
334
+ log:: debug!( "msg_name: {msg_name:?}" ) ;
335
+ log:: debug!( "msg_iov: {msg_iov:?}" ) ;
336
+ log:: debug!( "msg_iov[0]: {:?}" , & read_buf[ ..n] ) ;
337
+ log:: debug!( "msg_control: {msg_control:?}" ) ;
338
+
339
+ let source = Ipv4Addr :: from_bits ( msg_name. sin_addr . s_addr ) ;
340
+ //let source = source.ip();
341
+ let ( control_header, rest) = msg_control
342
+ . split_first_chunk :: < { size_of :: < nix:: libc:: cmsghdr > ( ) } > ( )
343
+ . ok_or_eyre ( "Foo" ) ?;
344
+ let control_header: nix:: libc:: cmsghdr = unsafe { transmute ( * control_header) } ;
345
+ let _control_message_len = control_header
346
+ . cmsg_len
347
+ . saturating_sub ( size_of :: < nix:: libc:: cmsghdr > ( ) ) ;
348
+
349
+ debug_assert_eq ! ( control_header. cmsg_level, nix:: libc:: IPPROTO_IP ) ;
350
+ debug_assert_eq ! ( control_header. cmsg_type, nix:: libc:: IP_RECVERR ) ;
351
+
352
+ let ( control_message, rest) = rest
353
+ . split_first_chunk :: < { size_of :: < sock_extended_err > ( ) } > ( )
354
+ . ok_or_eyre ( "ASADAD" ) ?;
355
+ //debug_assert_eq!(control_message_len, control_message.len());
356
+
357
+ let control_message: sock_extended_err = unsafe { transmute ( * control_message) } ;
358
+
359
+ let result = parse_icmp_time_exceeded_raw ( & rest)
360
+ . map_err ( |e| eyre ! ( "Ignoring packet (len={n}, ip.src={source}): {e}" , ) ) ;
361
+
362
+ log:: debug!( "{control_header:?}" ) ;
363
+ log:: debug!( "{control_message:?}" ) ;
364
+ log:: debug!( "rest: {rest:?}" ) ;
365
+ log:: debug!( "{:?}" , Errno :: from_raw( control_message. ee_errno as i32 ) ) ;
366
+
367
+ let _original_icmp_echo = & read_buf[ ..n] ;
368
+
369
+ // contains the source address of the ICMP Time Exceeded packet
370
+ let _icmp_source/*: nix::libc::sockaddr */ = rest;
371
+
372
+ match result {
373
+ Ok ( ..) => {
374
+ log:: debug!( "Got a probe response, we are leaking!" ) ;
375
+ //timeout_at.get_or_insert_with(|| Instant::now() + RECV_TIMEOUT);
376
+ //let ip = IpAddr::from(ip);
377
+ let ip = IpAddr :: from ( Ipv4Addr :: new ( 1 , 3 , 3 , 7 ) ) ;
378
+ if !reachable_nodes. contains ( & ip) {
379
+ reachable_nodes. push ( ip) ;
380
+ }
381
+ }
382
+
383
+ // an error means the packet wasn't the ICMP/TimeExceeded we're listening for.
384
+ Err ( e) => log:: debug!( "{e}" ) ,
385
+ }
386
+ }
387
+ }
388
+
268
389
async fn read_probe_responses ( interface : & str , socket : UdpSocket ) -> eyre:: Result < LeakStatus > {
269
390
// the list of node IP addresses from which we received a response to our probe packets.
270
391
let mut reachable_nodes = vec ! [ ] ;
@@ -385,12 +506,16 @@ fn parse_ipv4(packet: &[u8]) -> eyre::Result<Ipv4Packet<'_>> {
385
506
/// If the packet fails to parse, or is not a reply to a packet sent by [send_probes], this
386
507
/// function returns an error.
387
508
fn parse_icmp_time_exceeded ( ip_packet : & Ipv4Packet < ' _ > ) -> eyre:: Result < Ipv4Addr > {
388
- let too_small = || eyre ! ( "Too small" ) ;
389
-
390
509
let ip_protocol = ip_packet. get_next_level_protocol ( ) ;
391
510
ensure ! ( ip_protocol == IpProtocol :: Icmp , "Not ICMP" ) ;
511
+ parse_icmp_time_exceeded_raw ( ip_packet. payload ( ) ) ?;
512
+ Ok ( ip_packet. get_source ( ) )
513
+ }
514
+
515
+ fn parse_icmp_time_exceeded_raw ( bytes : & [ u8 ] ) -> eyre:: Result < ( ) > {
516
+ let icmp_packet = IcmpPacket :: new ( bytes) . ok_or ( eyre ! ( "Too small" ) ) ?;
517
+ let too_small = || eyre ! ( "Too small" ) ;
392
518
393
- let icmp_packet = IcmpPacket :: new ( ip_packet. payload ( ) ) . ok_or_else ( too_small) ?;
394
519
let correct_type = icmp_packet. get_icmp_type ( ) == IcmpTypes :: TimeExceeded ;
395
520
ensure ! ( correct_type, "Not ICMP/TimeExceeded" ) ;
396
521
@@ -424,7 +549,7 @@ fn parse_icmp_time_exceeded(ip_packet: &Ipv4Packet<'_>) -> eyre::Result<Ipv4Addr
424
549
}
425
550
}
426
551
427
- Ok ( ip_packet . get_source ( ) )
552
+ Ok ( ( ) )
428
553
}
429
554
430
555
IpProtocol :: Icmp => {
@@ -449,7 +574,7 @@ fn parse_icmp_time_exceeded(ip_packet: &Ipv4Packet<'_>) -> eyre::Result<Ipv4Addr
449
574
bail ! ( "Wrong ICMP/Echo payload: {echo_payload:?}" ) ;
450
575
}
451
576
452
- Ok ( ip_packet . get_source ( ) )
577
+ Ok ( ( ) )
453
578
}
454
579
455
580
_ => bail ! ( "Not UDP/ICMP" ) ,
@@ -460,12 +585,13 @@ match_cfg! {
460
585
#[ cfg( any( target_os = "windows" , target_os = "android" ) ) ] => {
461
586
fn bind_socket_to_interface( socket: & Socket , interface: & str ) -> eyre:: Result <( ) > {
462
587
use crate :: util:: get_interface_ip;
588
+ use std:: net:: SocketAddr ;
463
589
464
590
let interface_ip = get_interface_ip( interface) ?;
465
591
466
592
log:: info!( "Binding socket to {interface_ip} ({interface:?})" ) ;
467
593
468
- socket. bind( & SocketAddrV4 :: new( interface_ip, 0 ) . into( ) )
594
+ socket. bind( & SocketAddr :: new( interface_ip, 0 ) . into( ) )
469
595
. wrap_err( "Failed to bind socket to interface address" ) ?;
470
596
471
597
return Ok ( ( ) ) ;
0 commit comments