Skip to content

Commit 0f942ea

Browse files
committed
check cached DnsClient connection state before query
- ref #567
1 parent d4b1057 commit 0f942ea

File tree

7 files changed

+151
-22
lines changed

7 files changed

+151
-22
lines changed

Cargo.lock

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

crates/shadowsocks-service/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ shadowsocks = { version = "1.11.2", path = "../shadowsocks" }
114114
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
115115
nix = "0.21"
116116

117+
[target.'cfg(windows)'.dependencies]
118+
winapi = { version = "0.3", features = ["mswsock", "winsock2"] }
119+
117120
[dev-dependencies]
118121
byteorder = "1.3"
119122
env_logger = "0.8"

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
time::Duration,
1111
};
1212

13-
use log::trace;
13+
use log::{debug, trace};
1414
use shadowsocks::{config::ServerConfig, net::ConnectOpts, relay::socks5::Address};
1515
use tokio::sync::Mutex;
1616
use trust_dns_resolver::proto::{error::ProtoError, op::Message};
@@ -123,6 +123,13 @@ impl DnsClientCache {
123123
let mut last_err = None;
124124

125125
for _ in 0..self.retry_count {
126+
// UNIX stream won't keep connection alive
127+
//
128+
// https://github.com/shadowsocks/shadowsocks-rust/pull/567
129+
//
130+
// 1. The cost of recreating UNIX stream sockets are very low
131+
// 2. This feature is only used by shadowsocks-android, and it doesn't support connection reuse
132+
126133
let mut client = match DnsClient::connect_unix_stream(ns).await {
127134
Ok(client) => client,
128135
Err(err) => {
@@ -245,8 +252,14 @@ impl DnsClientCache {
245252
{
246253
// Check if there already is a cached client
247254
if let Some(q) = self.cache.lock().await.get_mut(key) {
248-
if let Some(c) = q.pop_front() {
255+
while let Some(mut c) = q.pop_front() {
249256
trace!("take cached DNS client for {:?}", key);
257+
258+
if !c.check_connected().await {
259+
debug!("cached DNS client for {:?} is lost", key);
260+
continue;
261+
}
262+
250263
return Ok(c);
251264
}
252265
}

crates/shadowsocks-service/src/local/dns/upstream.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
33
#[cfg(unix)]
44
use std::path::Path;
5-
use std::{io, net::SocketAddr, sync::Arc, time::Duration};
5+
use std::{
6+
io::{self, ErrorKind},
7+
net::SocketAddr,
8+
sync::Arc,
9+
time::Duration,
10+
};
611

712
use byteorder::{BigEndian, ByteOrder};
813
use bytes::{BufMut, BytesMut};
@@ -145,6 +150,92 @@ impl DnsClient {
145150
}
146151
}
147152
}
153+
154+
/// Check if the underlying connection is still connecting
155+
///
156+
/// This will only work for TCP and UNIX Stream connections.
157+
/// UDP clients will always return `true`.
158+
pub async fn check_connected(&mut self) -> bool {
159+
#[cfg(unix)]
160+
fn check_peekable<F: std::os::unix::io::AsRawFd>(fd: &mut F) -> bool {
161+
let fd = fd.as_raw_fd();
162+
163+
unsafe {
164+
let mut peek_buf = [0u8; 1];
165+
166+
let ret = libc::recv(
167+
fd,
168+
peek_buf.as_mut_ptr() as *mut libc::c_void,
169+
peek_buf.len(),
170+
libc::MSG_PEEK | libc::MSG_DONTWAIT,
171+
);
172+
173+
if ret == 0 {
174+
// EOF, connection lost
175+
false
176+
} else if ret > 0 {
177+
// Data in buffer
178+
true
179+
} else {
180+
let err = io::Error::last_os_error();
181+
if err.kind() == ErrorKind::WouldBlock {
182+
// EAGAIN, EWOULDBLOCK
183+
// Still connected.
184+
true
185+
} else {
186+
false
187+
}
188+
}
189+
}
190+
}
191+
192+
#[cfg(windows)]
193+
fn check_peekable<F: std::os::windows::io::AsRawSocket>(s: &mut F) -> bool {
194+
use winapi::{
195+
ctypes::{c_char, c_int},
196+
um::winsock2::{recv, MSG_PEEK},
197+
};
198+
199+
let sock = s.as_raw_socket();
200+
201+
unsafe {
202+
let mut peek_buf = [0u8; 1];
203+
204+
let ret = recv(
205+
sock,
206+
peek_buf.as_mut_ptr() as *mut c_char,
207+
peek_buf.len() as c_int,
208+
MSG_PEEK,
209+
);
210+
211+
if ret == 0 {
212+
// EOF, connection lost
213+
false
214+
} else if ret > 0 {
215+
// Data in buffer
216+
true
217+
} else {
218+
let err = io::Error::last_os_error();
219+
if err.kind() == ErrorKind::WouldBlock {
220+
// I have to trust the `s` have already set to non-blocking mode
221+
// Becuase windows doesn't have MSG_DONTWAIT
222+
true
223+
} else {
224+
false
225+
}
226+
}
227+
}
228+
}
229+
230+
match *self {
231+
DnsClient::TcpLocal { ref mut stream } => check_peekable(stream),
232+
DnsClient::UdpLocal { .. } => true,
233+
#[cfg(unix)]
234+
DnsClient::UnixStream { ref mut stream } => check_peekable(stream),
235+
DnsClient::TcpRemote { ref mut stream } => check_peekable(stream.get_mut().get_mut()),
236+
DnsClient::UdpRemote { .. } => true,
237+
}
238+
}
148239
}
149240

150241
pub async fn stream_query<S>(stream: &mut S, r: &Message) -> Result<Message, ProtoError>

crates/shadowsocks-service/src/net/mon_stream.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ impl<S> MonProxyStream<S> {
3030
pub fn get_ref(&self) -> &S {
3131
&self.stream
3232
}
33+
34+
#[inline]
35+
pub fn get_mut(&mut self) -> &mut S {
36+
&mut self.stream
37+
}
3338
}
3439

3540
impl<S> AsyncRead for MonProxyStream<S>

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,11 @@ impl AsyncWrite for TcpStream {
281281
}
282282

283283
TcpStreamState::FastOpenConnecting(ref mut overlapped) => {
284-
let n = ready!(inner.poll_write_io(cx, || {
284+
let stream = inner.get_mut();
285+
286+
let n = ready!(stream.poll_write_io(cx, || {
285287
unsafe {
286-
let sock = inner.as_raw_socket() as SOCKET;
288+
let sock = stream.as_raw_socket() as SOCKET;
287289

288290
let mut bytes_sent: DWORD = 0;
289291
let mut flags: DWORD = 0;

crates/shadowsocks/src/net/tcp.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! TcpStream wrappers that supports connecting with options
22
33
#[cfg(unix)]
4-
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
4+
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
55
#[cfg(windows)]
6-
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket};
6+
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
77
use std::{
88
io::{self, ErrorKind},
99
net::SocketAddr,
@@ -335,3 +335,17 @@ fn setsockopt_with_opt(f: &tokio::net::TcpStream, opts: &AcceptOpts) -> io::Resu
335335
f.set_nodelay(opts.tcp.nodelay)?;
336336
Ok(())
337337
}
338+
339+
#[cfg(unix)]
340+
impl AsRawFd for TcpStream {
341+
fn as_raw_fd(&self) -> RawFd {
342+
self.0.as_raw_fd()
343+
}
344+
}
345+
346+
#[cfg(windows)]
347+
impl AsRawSocket for TcpStream {
348+
fn as_raw_socket(&self) -> RawSocket {
349+
self.0.as_raw_socket()
350+
}
351+
}

0 commit comments

Comments
 (0)