@@ -11,15 +11,19 @@ use std::{
11
11
ptr,
12
12
slice,
13
13
task:: { self , Poll } ,
14
+ time:: { Duration , Instant } ,
14
15
} ;
15
16
16
17
use bytes:: BytesMut ;
17
18
use log:: { error, warn} ;
19
+ use lru_time_cache:: LruCache ;
20
+ use once_cell:: sync:: Lazy ;
18
21
use pin_project:: pin_project;
19
22
use socket2:: { Domain , Protocol , SockAddr , Socket , TcpKeepalive , Type } ;
20
23
use tokio:: {
21
24
io:: { AsyncRead , AsyncWrite , ReadBuf } ,
22
25
net:: { TcpSocket , TcpStream as TokioTcpStream , UdpSocket } ,
26
+ sync:: Mutex ,
23
27
} ;
24
28
use tokio_tfo:: TfoStream ;
25
29
use windows_sys:: {
@@ -83,7 +87,7 @@ impl TcpStream {
83
87
84
88
// Binds to a specific network interface (device)
85
89
if let Some ( ref iface) = opts. bind_interface {
86
- set_ip_unicast_if ( & socket, & addr, iface) ?;
90
+ set_ip_unicast_if ( & socket, & addr, iface) . await ?;
87
91
}
88
92
89
93
set_common_sockopt_for_connect ( addr, & socket, opts) ?;
@@ -283,30 +287,51 @@ fn find_adapter_interface_index(addr: &SocketAddr, iface: &str) -> io::Result<Op
283
287
Ok ( None )
284
288
}
285
289
286
- fn set_ip_unicast_if < S : AsRawSocket > ( socket : & S , addr : & SocketAddr , iface : & str ) -> io:: Result < ( ) > {
287
- let handle = socket . as_raw_socket ( ) as SOCKET ;
290
+ async fn find_interface_index_cached ( addr : & SocketAddr , iface : & str ) -> io:: Result < u32 > {
291
+ const INDEX_EXPIRE_DURATION : Duration = Duration :: from_secs ( 5 ) ;
288
292
289
- unsafe {
290
- // GetAdaptersAddresses
291
- // XXX: It will check all the adapters every time. Would that become a performance issue?
292
- let if_index = match find_adapter_interface_index ( addr, iface) ? {
293
- Some ( idx) => idx,
294
- None => {
295
- // Windows if_nametoindex requires a C-string for interface name
296
- let ifname = CString :: new ( iface) . expect ( "iface" ) ;
297
-
298
- // https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff553788(v=vs.85)
299
- let if_index = if_nametoindex ( ifname. as_ptr ( ) as PCSTR ) ;
300
- if if_index == 0 {
301
- // If the if_nametoindex function fails and returns zero, it is not possible to determine an error code.
302
- error ! ( "if_nametoindex {} fails" , iface) ;
303
- return Err ( io:: Error :: new ( ErrorKind :: InvalidInput , "invalid interface name" ) ) ;
304
- }
293
+ static INTERFACE_INDEX_CACHE : Lazy < Mutex < LruCache < String , ( u32 , Instant ) > > > =
294
+ Lazy :: new ( || Mutex :: new ( LruCache :: with_expiry_duration ( INDEX_EXPIRE_DURATION ) ) ) ;
305
295
306
- if_index
296
+ let mut cache = INTERFACE_INDEX_CACHE . lock ( ) . await ;
297
+ if let Some ( ( idx, insert_time) ) = cache. get ( iface) {
298
+ // short-path, cache hit for most cases
299
+ let now = Instant :: now ( ) ;
300
+ if now - * insert_time < INDEX_EXPIRE_DURATION {
301
+ return Ok ( * idx) ;
302
+ }
303
+ }
304
+
305
+ // Get from API GetAdaptersAddresses
306
+ let idx = match find_adapter_interface_index ( addr, iface) ? {
307
+ Some ( idx) => idx,
308
+ None => unsafe {
309
+ // Windows if_nametoindex requires a C-string for interface name
310
+ let ifname = CString :: new ( iface) . expect ( "iface" ) ;
311
+
312
+ // https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff553788(v=vs.85)
313
+ let if_index = if_nametoindex ( ifname. as_ptr ( ) as PCSTR ) ;
314
+ if if_index == 0 {
315
+ // If the if_nametoindex function fails and returns zero, it is not possible to determine an error code.
316
+ error ! ( "if_nametoindex {} fails" , iface) ;
317
+ return Err ( io:: Error :: new ( ErrorKind :: InvalidInput , "invalid interface name" ) ) ;
307
318
}
308
- } ;
309
319
320
+ if_index
321
+ } ,
322
+ } ;
323
+
324
+ cache. insert ( iface. to_owned ( ) , ( idx, Instant :: now ( ) ) ) ;
325
+
326
+ Ok ( idx)
327
+ }
328
+
329
+ async fn set_ip_unicast_if < S : AsRawSocket > ( socket : & S , addr : & SocketAddr , iface : & str ) -> io:: Result < ( ) > {
330
+ let handle = socket. as_raw_socket ( ) as SOCKET ;
331
+
332
+ let if_index = find_interface_index_cached ( addr, iface) . await ?;
333
+
334
+ unsafe {
310
335
// https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
311
336
let ret = match addr {
312
337
SocketAddr :: V4 ( ..) => setsockopt (
@@ -470,7 +495,7 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, opts: &ConnectOpts
470
495
let socket = Socket :: new ( Domain :: for_address ( * bind_addr) , Type :: DGRAM , Some ( Protocol :: UDP ) ) ?;
471
496
472
497
if let Some ( ref iface) = opts. bind_interface {
473
- set_ip_unicast_if ( & socket, bind_addr, iface) ?;
498
+ set_ip_unicast_if ( & socket, bind_addr, iface) . await ?;
474
499
}
475
500
476
501
// bind() should be called after IP_UNICAST_IF
0 commit comments