Skip to content

Commit 4dfcc8e

Browse files
authored
Manage UDP associations with LFU strategy (#506)
* manage UDP associations with LFU - DNS associations could be evicted firstly then others like HTTP/3 * UDP redir caches inbound non-local socket - Optimization: prevent creating new sockets for the same remotes * updated libc to v0.2.94 for unified TCP options * make clippy happy * Pin lfu-cache to v1.2.1 for edward-shen/lfu-cache#2 * UDP target should cache with expiration 1hrs * UDP tunnel test with an echo server - CI crashes occasionally because of 8.8.8.8:53 doesn't respond
1 parent 89b329c commit 4dfcc8e

File tree

14 files changed

+226
-126
lines changed

14 files changed

+226
-126
lines changed

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/sslocal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ fn main() {
357357

358358
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
359359
if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") {
360-
config.outbound_bind_interface = Some(From::from(iface.to_owned()));
360+
config.outbound_bind_interface = Some(iface.to_owned());
361361
}
362362

363363
#[cfg(all(unix, not(target_os = "android")))]

bin/ssmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ fn main() {
163163
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
164164
}
165165

166-
#[cfg(any(target_os = "linux", target_os = "android"))]
166+
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
167167
if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") {
168-
config.outbound_bind_interface = Some(From::from(iface.to_owned()));
168+
config.outbound_bind_interface = Some(iface.to_owned());
169169
}
170170

171171
if let Some(m) = matches.value_of("MANAGER_ADDRESS") {

bin/ssserver.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,9 @@ fn main() {
204204
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
205205
}
206206

207-
#[cfg(any(target_os = "linux", target_os = "android"))]
207+
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
208208
if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") {
209-
config.outbound_bind_interface = Some(From::from(iface.to_owned()));
209+
config.outbound_bind_interface = Some(iface.to_owned());
210210
}
211211

212212
if let Some(m) = matches.value_of("MANAGER_ADDRESS") {

crates/shadowsocks-service/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ once_cell = "1.7"
7777
thiserror = "1.0"
7878

7979
spin = { version = "0.9", features = ["std"] }
80-
lru_time_cache = "0.11"
80+
lfu_cache = "1.2.1"
8181
bytes = "1.0"
8282
byte_string = "1.0"
8383
byteorder = "1.3"

crates/shadowsocks-service/src/local/context.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::Arc;
55
use std::{net::IpAddr, time::Duration};
66

77
#[cfg(feature = "local-dns")]
8-
use lru_time_cache::LruCache;
8+
use lfu_cache::TimedLfuCache;
99
use shadowsocks::{
1010
config::ServerType,
1111
context::{Context, SharedContext},
@@ -32,7 +32,7 @@ pub struct ServiceContext {
3232

3333
// For DNS relay's ACL domain name reverse lookup -- whether the IP shall be forwarded
3434
#[cfg(feature = "local-dns")]
35-
reverse_lookup_cache: Mutex<LruCache<IpAddr, bool>>,
35+
reverse_lookup_cache: Mutex<TimedLfuCache<IpAddr, bool>>,
3636
}
3737

3838
impl Default for ServiceContext {
@@ -51,7 +51,10 @@ impl ServiceContext {
5151
acl: None,
5252
flow_stat: Arc::new(FlowStat::new()),
5353
#[cfg(feature = "local-dns")]
54-
reverse_lookup_cache: Mutex::new(LruCache::with_expiry_duration(Duration::from_secs(3 * 24 * 60 * 60))),
54+
reverse_lookup_cache: Mutex::new(TimedLfuCache::with_capacity_and_expiration(
55+
10240, // XXX: It should be enough for a normal user.
56+
Duration::from_secs(3 * 24 * 60 * 60),
57+
)),
5558
}
5659
}
5760

crates/shadowsocks-service/src/local/http/client_cache.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::{sync::Arc, time::Duration};
44

55
use hyper::{Body, Client};
6-
use lru_time_cache::LruCache;
6+
use lfu_cache::TimedLfuCache;
77
use shadowsocks::config::ServerAddr;
88
use tokio::sync::Mutex;
99

@@ -14,14 +14,14 @@ use super::{connector::ProxyConnector, http_client::ProxyHttpClient};
1414
/// Cached HTTP client for remote servers
1515
pub struct ProxyClientCache {
1616
context: Arc<ServiceContext>,
17-
cache: Mutex<LruCache<ServerAddr, ProxyHttpClient>>,
17+
cache: Mutex<TimedLfuCache<ServerAddr, ProxyHttpClient>>,
1818
}
1919

2020
impl ProxyClientCache {
2121
pub fn new(context: Arc<ServiceContext>) -> ProxyClientCache {
2222
ProxyClientCache {
2323
context,
24-
cache: Mutex::new(LruCache::with_expiry_duration_and_capacity(Duration::from_secs(60), 5)),
24+
cache: Mutex::new(TimedLfuCache::with_capacity_and_expiration(5, Duration::from_secs(60))),
2525
}
2626
}
2727

crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl AutoProxyClientStream {
5959
let addr = addr.into();
6060
let stream =
6161
TcpStream::connect_remote_with_opts(context.context_ref(), &addr, context.connect_opts_ref()).await?;
62-
Ok(AutoProxyClientStream::Bypassed(stream.into()))
62+
Ok(AutoProxyClientStream::Bypassed(stream))
6363
}
6464

6565
/// Connect to target `addr` via shadowsocks' server configured by `svr_cfg`

crates/shadowsocks-service/src/local/net/udp/association.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use std::{
1010
use async_trait::async_trait;
1111
use bytes::Bytes;
1212
use futures::future::{self, AbortHandle};
13+
use lfu_cache::TimedLfuCache;
1314
use log::{debug, error, trace, warn};
14-
use lru_time_cache::{Entry, LruCache};
1515
use shadowsocks::{
1616
lookup_then,
1717
net::UdpSocket as ShadowUdpSocket,
@@ -42,14 +42,17 @@ pub trait UdpInboundWrite {
4242
async fn send_to(&self, peer_addr: SocketAddr, remote_addr: &Address, data: &[u8]) -> io::Result<()>;
4343
}
4444

45+
type AssociationMap<W> = TimedLfuCache<SocketAddr, UdpAssociation<W>>;
46+
type SharedAssociationMap<W> = Arc<Mutex<AssociationMap<W>>>;
47+
4548
/// UDP association manager
4649
pub struct UdpAssociationManager<W>
4750
where
4851
W: UdpInboundWrite + Clone + Send + Sync + Unpin + 'static,
4952
{
5053
respond_writer: W,
5154
context: Arc<ServiceContext>,
52-
assoc_map: Arc<Mutex<LruCache<SocketAddr, UdpAssociation<W>>>>,
55+
assoc_map: SharedAssociationMap<W>,
5356
cleanup_abortable: AbortHandle,
5457
balancer: PingBalancer,
5558
}
@@ -77,8 +80,8 @@ where
7780
) -> UdpAssociationManager<W> {
7881
let time_to_live = time_to_live.unwrap_or(crate::DEFAULT_UDP_EXPIRY_DURATION);
7982
let assoc_map = Arc::new(Mutex::new(match capacity {
80-
Some(capacity) => LruCache::with_expiry_duration_and_capacity(time_to_live, capacity),
81-
None => LruCache::with_expiry_duration(time_to_live),
83+
Some(capacity) => TimedLfuCache::with_capacity_and_expiration(capacity, time_to_live),
84+
None => TimedLfuCache::with_expiration(time_to_live),
8285
}));
8386

8487
let cleanup_abortable = {
@@ -87,8 +90,8 @@ where
8790
loop {
8891
time::sleep(time_to_live).await;
8992

90-
// iter() will trigger a cleanup of expired associations
91-
let _ = assoc_map.lock().await.iter();
93+
// cleanup expired associations
94+
let _ = assoc_map.lock().await.evict_expired();
9295
}
9396
});
9497
tokio::spawn(cleanup_task);
@@ -107,23 +110,27 @@ where
107110
/// Sends `data` from `peer_addr` to `target_addr`
108111
pub async fn send_to(&self, peer_addr: SocketAddr, target_addr: Address, data: &[u8]) -> io::Result<()> {
109112
// Check or (re)create an association
110-
match self.assoc_map.lock().await.entry(peer_addr) {
111-
Entry::Occupied(occ) => {
112-
let assoc = occ.into_mut();
113-
assoc.try_send((target_addr, Bytes::copy_from_slice(data)))
114-
}
115-
Entry::Vacant(vac) => {
116-
let assoc = vac.insert(UdpAssociation::new(
117-
self.context.clone(),
118-
peer_addr,
119-
self.assoc_map.clone(),
120-
self.balancer.clone(),
121-
self.respond_writer.clone(),
122-
));
123-
trace!("created udp association for {}", peer_addr);
124-
assoc.try_send((target_addr, Bytes::copy_from_slice(data)))
125-
}
113+
114+
let mut assoc_map = self.assoc_map.lock().await;
115+
116+
if let Some(assoc) = assoc_map.get(&peer_addr) {
117+
return assoc.try_send((target_addr, Bytes::copy_from_slice(data)));
126118
}
119+
120+
let assoc = UdpAssociation::new(
121+
self.context.clone(),
122+
peer_addr,
123+
self.assoc_map.clone(),
124+
self.balancer.clone(),
125+
self.respond_writer.clone(),
126+
);
127+
128+
trace!("created udp association for {}", peer_addr);
129+
130+
assoc.try_send((target_addr, Bytes::copy_from_slice(data)))?;
131+
assoc_map.insert(peer_addr, assoc);
132+
133+
Ok(())
127134
}
128135
}
129136

@@ -153,7 +160,7 @@ where
153160
fn new(
154161
context: Arc<ServiceContext>,
155162
peer_addr: SocketAddr,
156-
assoc_map: Arc<Mutex<LruCache<SocketAddr, UdpAssociation<W>>>>,
163+
assoc_map: SharedAssociationMap<W>,
157164
balancer: PingBalancer,
158165
respond_writer: W,
159166
) -> UdpAssociation<W> {
@@ -245,7 +252,7 @@ where
245252
bypassed_ipv4_socket: SpinMutex<UdpAssociationBypassState>,
246253
bypassed_ipv6_socket: SpinMutex<UdpAssociationBypassState>,
247254
proxied_socket: SpinMutex<UdpAssociationProxyState>,
248-
assoc_map: Arc<Mutex<LruCache<SocketAddr, UdpAssociation<W>>>>,
255+
assoc_map: SharedAssociationMap<W>,
249256
balancer: PingBalancer,
250257
respond_writer: W,
251258
}
@@ -266,7 +273,7 @@ where
266273
fn new(
267274
context: Arc<ServiceContext>,
268275
peer_addr: SocketAddr,
269-
assoc_map: Arc<Mutex<LruCache<SocketAddr, UdpAssociation<W>>>>,
276+
assoc_map: SharedAssociationMap<W>,
270277
balancer: PingBalancer,
271278
respond_writer: W,
272279
) -> (Arc<UdpAssociationContext<W>>, mpsc::Sender<(Address, Bytes)>) {

0 commit comments

Comments
 (0)