Skip to content

Commit fddf50a

Browse files
committed
feat(shadowsocks): Cache windows adapter index
ref #1266
1 parent 366a932 commit fddf50a

File tree

1 file changed

+47
-22
lines changed
  • crates/shadowsocks/src/net/sys/windows

1 file changed

+47
-22
lines changed

crates/shadowsocks/src/net/sys/windows/mod.rs

+47-22
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@ use std::{
1111
ptr,
1212
slice,
1313
task::{self, Poll},
14+
time::{Duration, Instant},
1415
};
1516

1617
use bytes::BytesMut;
1718
use log::{error, warn};
19+
use lru_time_cache::LruCache;
20+
use once_cell::sync::Lazy;
1821
use pin_project::pin_project;
1922
use socket2::{Domain, Protocol, SockAddr, Socket, TcpKeepalive, Type};
2023
use tokio::{
2124
io::{AsyncRead, AsyncWrite, ReadBuf},
2225
net::{TcpSocket, TcpStream as TokioTcpStream, UdpSocket},
26+
sync::Mutex,
2327
};
2428
use tokio_tfo::TfoStream;
2529
use 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

Comments
 (0)