@@ -11,15 +11,19 @@ use std::{
1111 ptr,
1212 slice,
1313 task:: { self , Poll } ,
14+ time:: { Duration , Instant } ,
1415} ;
1516
1617use bytes:: BytesMut ;
1718use log:: { error, warn} ;
19+ use lru_time_cache:: LruCache ;
20+ use once_cell:: sync:: Lazy ;
1821use pin_project:: pin_project;
1922use socket2:: { Domain , Protocol , SockAddr , Socket , TcpKeepalive , Type } ;
2023use tokio:: {
2124 io:: { AsyncRead , AsyncWrite , ReadBuf } ,
2225 net:: { TcpSocket , TcpStream as TokioTcpStream , UdpSocket } ,
26+ sync:: Mutex ,
2327} ;
2428use tokio_tfo:: TfoStream ;
2529use windows_sys:: {
@@ -83,7 +87,7 @@ impl TcpStream {
8387
8488 // Binds to a specific network interface (device)
8589 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 ?;
8791 }
8892
8993 set_common_sockopt_for_connect ( addr, & socket, opts) ?;
@@ -283,30 +287,51 @@ fn find_adapter_interface_index(addr: &SocketAddr, iface: &str) -> io::Result<Op
283287 Ok ( None )
284288}
285289
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 ) ;
288292
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 ) ) ) ;
305295
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" ) ) ;
307318 }
308- } ;
309319
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 {
310335 // https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
311336 let ret = match addr {
312337 SocketAddr :: V4 ( ..) => setsockopt (
@@ -470,7 +495,7 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, opts: &ConnectOpts
470495 let socket = Socket :: new ( Domain :: for_address ( * bind_addr) , Type :: DGRAM , Some ( Protocol :: UDP ) ) ?;
471496
472497 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 ?;
474499 }
475500
476501 // bind() should be called after IP_UNICAST_IF
0 commit comments