diff --git a/src/config.rs b/src/config.rs index 9388b71d..595721fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1524,6 +1524,12 @@ impl Config { Ok(()) } + + /// Check if DNS Relay is enabled + #[cfg(feature = "local-dns-relay")] + pub(crate) fn is_local_dns_relay(&self) -> bool { + self.config_type == ConfigType::DnsLocal || self.local_dns_addr.is_some() || self.local_dns_path.is_some() + } } impl fmt::Display for Config { diff --git a/src/context.rs b/src/context.rs index 96990403..588087dd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -27,8 +27,6 @@ use trust_dns_resolver::TokioAsyncResolver; use crate::crypto::CipherType; #[cfg(feature = "trust-dns")] use crate::relay::dns_resolver::create_resolver; -#[cfg(not(feature = "local-dns-relay"))] -use crate::relay::dns_resolver::resolve; #[cfg(feature = "local-dns-relay")] use crate::relay::dnsrelay::upstream::LocalUpstream; #[cfg(feature = "local-flow-stat")] @@ -36,7 +34,7 @@ use crate::relay::flow::ServerFlowStatistic; use crate::{ acl::AccessControl, config::{Config, ConfigType, ServerConfig}, - relay::socks5::Address, + relay::{dns_resolver::resolve, socks5::Address}, }; // Entries for server's bloom filter @@ -186,7 +184,7 @@ pub struct Context { // For local DNS upstream #[cfg(feature = "local-dns-relay")] - local_dns: LocalUpstream, + local_dns: Option, } /// Unique context thw whole server @@ -229,7 +227,11 @@ impl Context { let nonce_ppbloom = SpinMutex::new(PingPongBloom::new(config.config_type)); #[cfg(feature = "local-dns-relay")] - let local_dns = LocalUpstream::new(&config); + let local_dns = if config.is_local_dns_relay() { + Some(LocalUpstream::new(&config)) + } else { + None + }; Context { config, @@ -318,7 +320,10 @@ impl Context { #[cfg(feature = "local-dns-relay")] #[inline(always)] async fn dns_resolve_impl(&self, host: &str, port: u16) -> io::Result> { - self.local_dns().lookup_ip(host, port).await + match self.local_dns { + Some(ref local_dns) => local_dns.lookup_ip(host, port).await, + None => resolve(self, host, port).await, + } } #[cfg(not(feature = "local-dns-relay"))] @@ -402,7 +407,7 @@ impl Context { /// Get local DNS connector #[cfg(feature = "local-dns-relay")] pub fn local_dns(&self) -> &LocalUpstream { - &self.local_dns + &self.local_dns.as_ref().expect("local DNS uninitialized") } /// Check target address ACL (for client) diff --git a/src/relay/dnsrelay/mod.rs b/src/relay/dnsrelay/mod.rs index 4ffdd610..654af69a 100644 --- a/src/relay/dnsrelay/mod.rs +++ b/src/relay/dnsrelay/mod.rs @@ -304,7 +304,7 @@ async fn run_tcp( let listener = TcpListener::bind(&bind_addr).await?; let actual_local_addr = listener.local_addr()?; - info!("shadowsocks DNS relay listening on tcp:{}", actual_local_addr); + info!("shadowsocks DNS relay (TCP) listening on {}", actual_local_addr); loop { let (mut stream, src) = listener.accept().await?; @@ -332,7 +332,7 @@ async fn run_udp( let socket = create_udp_socket(&bind_addr).await?; let actual_local_addr = socket.local_addr()?; - info!("shadowsocks DNS relay listening on udp:{}", actual_local_addr); + info!("shadowsocks DNS relay (UDP) listening on {}", actual_local_addr); let rx = Arc::new(socket); let tx = rx.clone(); diff --git a/src/relay/dnsrelay/upstream.rs b/src/relay/dnsrelay/upstream.rs index 2a484bb7..cad089b7 100644 --- a/src/relay/dnsrelay/upstream.rs +++ b/src/relay/dnsrelay/upstream.rs @@ -94,12 +94,12 @@ fn generate_query_message(query: &Query) -> Message { } pub async fn read_message(stream: &mut T) -> io::Result { - let mut res_buffer = vec![0; 2]; - stream.read_exact(&mut res_buffer[0..2]).await?; + let mut res_buffer = [0; 2]; + stream.read_exact(&mut res_buffer).await?; - let size = BigEndian::read_u16(&res_buffer[0..2]) as usize; + let size = BigEndian::read_u16(&res_buffer) as usize; let mut res_buffer = vec![0; size]; - stream.read_exact(&mut res_buffer[0..size]).await?; + stream.read_exact(&mut res_buffer).await?; Ok(Message::from_vec(&res_buffer)?) } diff --git a/src/relay/local.rs b/src/relay/local.rs index 237ccd06..35f4df39 100644 --- a/src/relay/local.rs +++ b/src/relay/local.rs @@ -118,24 +118,20 @@ pub async fn run(mut config: Config) -> io::Result<()> { } #[cfg(feature = "local-dns-relay")] - { - if context.config().dns_local_addr.is_some() { - use crate::relay::dnsrelay::run as run_dns; + if context.config().is_local_dns_relay() { + use crate::relay::dnsrelay::run as run_dns; - // DNS relay local server - let dns_relay = run_dns(context.clone()); - vf.push(dns_relay.boxed()); - } + // DNS relay local server + let dns_relay = run_dns(context.clone()); + vf.push(dns_relay.boxed()); } #[cfg(feature = "local-flow-stat")] - { - if context.config().stat_path.is_some() { - // For Android's flow statistic + if context.config().stat_path.is_some() { + // For Android's flow statistic - let report_fut = flow_report_task(context.clone()); - vf.push(report_fut.boxed()); - } + let report_fut = flow_report_task(context.clone()); + vf.push(report_fut.boxed()); } let (res, ..) = select_all(vf.into_iter()).await; diff --git a/src/relay/socks4.rs b/src/relay/socks4.rs index d10a1bb5..a8b30da0 100644 --- a/src/relay/socks4.rs +++ b/src/relay/socks4.rs @@ -56,7 +56,7 @@ impl Command { } /// SOCKS4 Result Code -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Copy, Eq, PartialEq)] pub enum ResultCode { /// 90: request granted RequestGranted, @@ -324,8 +324,11 @@ impl HandshakeResponse { let _ = r.read_exact(&mut buf).await?; let vn = buf[0]; - if vn != consts::SOCKS4_VERSION { - let err = Error::new(ErrorKind::InvalidData, format!("unsupported socks version {:#x}", vn)); + if vn != 0 { + let err = Error::new( + ErrorKind::InvalidData, + format!("unsupported socks4 response version {:#x}", vn), + ); return Err(err); } diff --git a/tests/dns.rs b/tests/dns.rs new file mode 100644 index 00000000..0a0308c8 --- /dev/null +++ b/tests/dns.rs @@ -0,0 +1,90 @@ +#![cfg(feature = "local-dns-relay")] + +use std::time::Duration; + +use byteorder::{BigEndian, ByteOrder}; +use tokio::{ + net::{TcpStream, UdpSocket}, + prelude::*, + time, +}; + +use shadowsocks::{ + config::{Config, ConfigType}, + run_local, + run_server, +}; + +#[tokio::test] +async fn dns_relay() { + let _ = env_logger::try_init(); + + let mut local_config = Config::load_from_str( + r#"{ + "local_port": 6110, + "local_address": "127.0.0.1", + "server": "127.0.0.1", + "server_port": 6120, + "password": "password", + "method": "aes-256-gcm" + }"#, + ConfigType::DnsLocal, + ) + .unwrap(); + + local_config.local_dns_addr = Some("114.114.114.114:53".parse().unwrap()); + local_config.remote_dns_addr = Some("8.8.8.8:53".parse().unwrap()); + + let server_config = Config::load_from_str( + r#"{ + "server": "127.0.0.1", + "server_port": 6120, + "password": "password", + "method": "aes-256-gcm", + "mode": "tcp_and_udp" + }"#, + ConfigType::Server, + ) + .unwrap(); + + tokio::spawn(run_local(local_config)); + tokio::spawn(run_server(server_config)); + + time::sleep(Duration::from_secs(1)).await; + + // Query firefox.com, TransactionID: 0x1234 + static DNS_QUERY: &[u8] = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07firefox\x03com\x00\x00\x01\x00\x01"; + + // 1. DoT + { + let mut c = TcpStream::connect("127.0.0.1:6110").await.unwrap(); + + let mut len_buf = [0u8; 2]; + BigEndian::write_u16(&mut len_buf, DNS_QUERY.len() as u16); + c.write_all(&len_buf).await.unwrap(); + + c.write_all(DNS_QUERY).await.unwrap(); + c.flush().await.unwrap(); + + c.read_exact(&mut len_buf).await.unwrap(); + let resp_len = BigEndian::read_u16(&len_buf); + + let mut buf = vec![0u8; resp_len as usize]; + c.read_exact(&mut buf).await.unwrap(); + + assert!(buf.starts_with(b"\x12\x34")); + } + + // 2. DoU + { + let c = UdpSocket::bind("0.0.0.0:0").await.unwrap(); + c.send_to(DNS_QUERY, "127.0.0.1:6110").await.unwrap(); + + let mut buf = [0u8; 65536]; + let n = c.recv(&mut buf).await.unwrap(); + assert!(n >= 12); + + let pkt = &buf[..n]; + assert_eq!(&pkt[..2], b"\x12\x34"); + } +} diff --git a/tests/http.rs b/tests/http.rs new file mode 100644 index 00000000..cb530603 --- /dev/null +++ b/tests/http.rs @@ -0,0 +1,76 @@ +#![cfg(feature = "local-http")] + +use std::time::Duration; + +use tokio::{net::TcpStream, prelude::*, time}; + +use shadowsocks::{ + config::{Config, ConfigType}, + run_local, + run_server, +}; + +#[tokio::test] +async fn http_proxy() { + let _ = env_logger::try_init(); + + let local_config = Config::load_from_str( + r#"{ + "local_port": 5110, + "local_address": "127.0.0.1", + "server": "127.0.0.1", + "server_port": 5120, + "password": "password", + "method": "aes-256-gcm" + }"#, + ConfigType::HttpLocal, + ) + .unwrap(); + + let server_config = Config::load_from_str( + r#"{ + "server": "127.0.0.1", + "server_port": 5120, + "password": "password", + "method": "aes-256-gcm" + }"#, + ConfigType::Server, + ) + .unwrap(); + + tokio::spawn(run_local(local_config)); + tokio::spawn(run_server(server_config)); + + time::sleep(Duration::from_secs(1)).await; + + { + let mut c = TcpStream::connect("127.0.0.1:5110").await.unwrap(); + c.write_all(b"GET http://www.example.com/ HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n") + .await + .unwrap(); + c.flush().await.unwrap(); + + let mut buf = Vec::new(); + c.read_to_end(&mut buf).await.unwrap(); + + assert!(buf.starts_with(b"HTTP/1.0 200 OK\r\n")); + } + + { + let mut c = TcpStream::connect("127.0.0.1:5110").await.unwrap(); + c.write_all(b"CONNECT http://www.example.com/ HTTP/1.0\r\n\r\n") + .await + .unwrap(); + c.flush().await.unwrap(); + + c.write_all(b"GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n") + .await + .unwrap(); + c.flush().await.unwrap(); + + let mut buf = Vec::new(); + c.read_to_end(&mut buf).await.unwrap(); + + assert!(buf.starts_with(b"HTTP/1.0 200 OK\r\n")); + } +} diff --git a/tests/socks4.rs b/tests/socks4.rs new file mode 100644 index 00000000..c683e25f --- /dev/null +++ b/tests/socks4.rs @@ -0,0 +1,110 @@ +#![cfg(feature = "local-socks4")] + +use std::{ + net::{SocketAddr, ToSocketAddrs}, + str, +}; + +use tokio::{ + net::TcpStream, + prelude::*, + time::{self, Duration}, +}; + +use shadowsocks::{ + config::{Config, ConfigType, ServerAddr, ServerConfig}, + crypto::CipherType, + relay::socks4::{Address, Command, HandshakeRequest, HandshakeResponse, ResultCode}, + run_local, + run_server, +}; + +pub struct Socks4TestServer { + local_addr: SocketAddr, + svr_config: Config, + cli_config: Config, +} + +impl Socks4TestServer { + pub fn new(svr_addr: S, local_addr: L, pwd: &str, method: CipherType) -> Socks4TestServer + where + S: ToSocketAddrs, + L: ToSocketAddrs, + { + let svr_addr = svr_addr.to_socket_addrs().unwrap().next().unwrap(); + let local_addr = local_addr.to_socket_addrs().unwrap().next().unwrap(); + + Socks4TestServer { + local_addr, + svr_config: { + let mut cfg = Config::new(ConfigType::Server); + cfg.server = vec![ServerConfig::basic(svr_addr, pwd.to_owned(), method)]; + cfg + }, + cli_config: { + let mut cfg = Config::new(ConfigType::Socks4Local); + cfg.local_addr = Some(ServerAddr::from(local_addr)); + cfg.server = vec![ServerConfig::basic(svr_addr, pwd.to_owned(), method)]; + cfg + }, + } + } + + pub fn client_addr(&self) -> &SocketAddr { + &self.local_addr + } + + pub async fn run(&self) { + let svr_cfg = self.svr_config.clone(); + tokio::spawn(run_server(svr_cfg)); + + let client_cfg = self.cli_config.clone(); + tokio::spawn(run_local(client_cfg)); + + time::sleep(Duration::from_secs(1)).await; + } +} + +#[tokio::test] +async fn socks4_relay_connect() { + let _ = env_logger::try_init(); + + const SERVER_ADDR: &str = "127.0.0.1:7100"; + const LOCAL_ADDR: &str = "127.0.0.1:7200"; + + const PASSWORD: &str = "test-password"; + const METHOD: CipherType = CipherType::Aes128Gcm; + + let svr = Socks4TestServer::new(SERVER_ADDR, LOCAL_ADDR, PASSWORD, METHOD); + svr.run().await; + + static HTTP_REQUEST: &[u8] = b"GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n"; + + let mut c = TcpStream::connect(LOCAL_ADDR).await.unwrap(); + + let req = HandshakeRequest { + cd: Command::Connect, + dst: Address::from(("www.example.com".to_owned(), 80)), + user_id: Vec::new(), + }; + + let mut handshake_buf = Vec::new(); + req.write_to_buf(&mut handshake_buf); + + c.write_all(&handshake_buf).await.unwrap(); + c.flush().await.unwrap(); + + let rsp = HandshakeResponse::read_from(&mut c).await.unwrap(); + assert_eq!(rsp.cd, ResultCode::RequestGranted); + + c.write_all(HTTP_REQUEST).await.unwrap(); + c.flush().await.unwrap(); + + let mut buf = Vec::new(); + c.read_to_end(&mut buf).await.unwrap(); + + println!("Got reply from server: {}", str::from_utf8(&buf).unwrap()); + + let http_status = b"HTTP/1.0 200 OK\r\n"; + buf.starts_with(http_status); +} diff --git a/tests/socks5.rs b/tests/socks5.rs index 653d33b1..5ed086fc 100644 --- a/tests/socks5.rs +++ b/tests/socks5.rs @@ -72,7 +72,7 @@ async fn socks5_relay_stream() { const LOCAL_ADDR: &str = "127.0.0.1:8200"; const PASSWORD: &str = "test-password"; - const METHOD: CipherType = CipherType::Aes128Gcm; + const METHOD: CipherType = CipherType::Aes128Cfb; let svr = Socks5TestServer::new(SERVER_ADDR, LOCAL_ADDR, PASSWORD, METHOD, false); svr.run().await;