Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
61d3ebd
Simplify interface to use Query
Mygod May 8, 2020
6f7bf16
Revert "Clean up"
Mygod May 8, 2020
f41c9d2
Refactoring for configurable upstreams
Mygod May 8, 2020
f898455
Failfast for empty IP rules
Mygod May 8, 2020
68cc835
Check only one list based on qtype
Mygod May 8, 2020
b5d59d9
Simplify checking empty IpRange
Mygod May 8, 2020
6795f21
Filter out non-IN queries
Mygod May 8, 2020
e801155
Respect default mode for fallback
Mygod May 9, 2020
2853477
Support PTR records
Mygod May 9, 2020
2f1d83e
Refine DNS relay for other query types
Mygod May 9, 2020
47bb6ae
Only return 0 when sequence is exhausted
Mygod May 9, 2020
f8087d2
Refine the futures
madeye May 8, 2020
cd462d0
Preserve errors in acl_lookup
Mygod May 9, 2020
4988305
Refine code style
Mygod May 9, 2020
d3a2d12
Fix the build
madeye May 9, 2020
cb62eff
Refine handling of ANY queries and CNAME records in answer
Mygod May 9, 2020
e5d9f85
Check for DNS RD bit
Mygod May 9, 2020
49966d9
Send DNS probes concurrently
Mygod May 9, 2020
10e8041
Refine code style
Mygod May 9, 2020
13bcc5d
Prevent resuming completed Futures
Mygod May 10, 2020
4062b1d
Move LocalUpstream to global config
Mygod May 9, 2020
9712a97
Handle hostname connections correctly on Android
Mygod May 10, 2020
c583b4b
Add else to tokio::select
Mygod May 10, 2020
391f94a
Merge remote-tracking branch 'shadowsocks/master' into dns-refine-pat…
Mygod May 10, 2020
5cc0100
Add missing cfg
Mygod May 10, 2020
e42cedd
Bypass dns_resolve if IP rules are empty
Mygod May 10, 2020
d1a3ac6
Fix check_target_bypassed()
madeye May 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ env_logger = { version = "0.7", default-features = false, features = ["termcolor
chrono = "0.4"
openssl = { version = "0.10", optional = true }
libc = "^0.2.68"
tokio = { version = "^0.2.7", features = ["net", "signal", "time", "sync", "process", "rt-threaded", "rt-core", "stream", "io-util"] }
tokio = { version = "^0.2.11", features = ["macros", "net", "signal", "time", "sync", "process", "rt-threaded", "rt-core", "stream", "io-util"] }
tokio-tls = { version = "0.3", optional = true }
native-tls = { version = "0.2", optional = true }
tokio-rustls = { version = "0.13", optional = true }
Expand Down
80 changes: 51 additions & 29 deletions src/acl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ impl Rules {
fn check_host_matched(&self, host: &str) -> bool {
self.rule.is_match(host)
}

/// Check if there are no rules for IPv4 addresses
fn is_ipv4_empty(&self) -> bool {
self.ipv4.iter().next().is_none()
}

/// Check if there are no rules for IPv6 addresses
fn is_ipv6_empty(&self) -> bool {
self.ipv6.iter().next().is_none()
}
}

/// ACL rules
Expand Down Expand Up @@ -269,59 +279,71 @@ impl AccessControl {

/// Check if domain name is in proxy_list.
/// If so, it should be resolved from remote (for Android's DNS relay)
pub fn check_qname_in_proxy_list(&self, addr: &Address) -> Option<bool> {
pub fn check_host_in_proxy_list(&self, host: &str) -> Option<bool> {
// Addresses in proxy_list will be proxied
if self.white_list.check_address_matched(addr) {
if self.white_list.check_host_matched(host) {
return Some(true);
}
if self.black_list.check_address_matched(addr) {
// Addresses in bypass_list will be bypassed
if self.black_list.check_host_matched(host) {
return Some(false);
}
None
}

/// If there are no IPv4 rules
pub fn is_ipv4_empty(&self) -> bool {
match self.mode {
Mode::BlackList => self.black_list.is_ipv4_empty(),
Mode::WhiteList => self.white_list.is_ipv4_empty(),
}
}

/// If there are no IPv4 rules
pub fn is_ipv6_empty(&self) -> bool {
match self.mode {
Mode::BlackList => self.black_list.is_ipv6_empty(),
Mode::WhiteList => self.white_list.is_ipv6_empty(),
}
}

pub fn check_ip_in_proxy_list(&self, ip: &IpAddr) -> bool {
match self.mode {
Mode::BlackList => !self.black_list.check_ip_matched(ip),
Mode::WhiteList => self.white_list.check_ip_matched(ip),
}
}

pub fn is_default_in_proxy_list(&self) -> bool {
match self.mode {
Mode::BlackList => true,
Mode::WhiteList => false,
}
}

/// Check if target address should be bypassed (for client)
///
/// FIXME: This function may perform a DNS resolution
/// This function may perform a DNS resolution
pub async fn check_target_bypassed(&self, context: &Context, addr: &Address) -> bool {
// Addresses in bypass_list will be bypassed
if self.black_list.check_address_matched(addr) {
return true;
}

// Addresses in proxy_list will be proxied
if self.white_list.check_address_matched(addr) {
return false;
}

// Resolve hostname and check the list
if cfg!(not(target_os = "android")) {
if let Address::DomainNameAddress(ref host, port) = *addr {
match *addr {
Address::SocketAddress(ref addr) => !self.check_ip_in_proxy_list(&addr.ip()),
// Resolve hostname and check the list
Address::DomainNameAddress(ref host, port) => {
if let Some(value) = self.check_host_in_proxy_list(host) {
return !value;
}
if self.is_ipv4_empty() && self.is_ipv6_empty() {
return !self.is_default_in_proxy_list();
}
if let Ok(vaddr) = context.dns_resolve(host, port).await {
for addr in vaddr {
if self.black_list.check_ip_matched(&addr.ip()) {
if !self.check_ip_in_proxy_list(&addr.ip()) {
return true;
}

if self.white_list.check_ip_matched(&addr.ip()) {
return false;
}
}
}
}
}

// default rule
match self.mode {
Mode::BlackList => false,
Mode::WhiteList => true,
false
},
}
}

Expand Down
59 changes: 30 additions & 29 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ use trust_dns_resolver::TokioAsyncResolver;
use crate::relay::dns_resolver::create_resolver;
#[cfg(feature = "local-flow-stat")]
use crate::relay::flow::ServerFlowStatistic;
#[cfg(feature = "local-dns-relay")]
use crate::relay::dnsrelay::upstream::LocalUpstream;
#[cfg(not(feature = "local-dns-relay"))]
use crate::relay::dns_resolver::resolve;
use crate::{
acl::AccessControl,
config::{Config, ConfigType, ServerConfig},
crypto::CipherType,
relay::{dns_resolver::resolve, socks5::Address},
relay::socks5::Address,
};

// Entries for server's bloom filter
Expand Down Expand Up @@ -175,6 +180,10 @@ pub struct Context {
// For DNS relay's ACL domain name reverse lookup -- whether the IP shall be forwarded
#[cfg(feature = "local-dns-relay")]
reverse_lookup_cache: Mutex<LruCache<IpAddr, bool>>,

// For local DNS upstream
#[cfg(feature = "local-dns-relay")]
local_dns: LocalUpstream,
}

/// Unique context thw whole server
Expand Down Expand Up @@ -212,6 +221,8 @@ impl Context {
let reverse_lookup_cache = Mutex::new(LruCache::<IpAddr, bool>::with_expiry_duration(Duration::from_secs(
3 * 24 * 60 * 60,
)));
#[cfg(feature = "local-dns-relay")]
let local_dns = LocalUpstream::new(&config);

Context {
config,
Expand All @@ -222,6 +233,8 @@ impl Context {
local_flow_statistic: ServerFlowStatistic::new(),
#[cfg(feature = "local-dns-relay")]
reverse_lookup_cache,
#[cfg(feature = "local-dns-relay")]
local_dns,
}
}

Expand Down Expand Up @@ -265,6 +278,9 @@ impl Context {

/// Perform a DNS resolution
pub async fn dns_resolve(&self, host: &str, port: u16) -> io::Result<Vec<SocketAddr>> {
#[cfg(feature = "local-dns-relay")]
return self.local_dns().lookup_ip(host, port).await;
#[cfg(not(feature = "local-dns-relay"))]
resolve(self, host, port).await
}

Expand Down Expand Up @@ -294,23 +310,23 @@ impl Context {

/// Check client ACL (for server)
pub fn check_client_blocked(&self, addr: &SocketAddr) -> bool {
match self.config.acl {
match self.acl() {
None => false,
Some(ref a) => a.check_client_blocked(addr),
}
}

/// Check outbound address ACL (for server)
pub fn check_outbound_blocked(&self, addr: &Address) -> bool {
match self.config.acl {
match self.acl() {
None => false,
Some(ref a) => a.check_outbound_blocked(addr),
}
}

/// Check resolved outbound address ACL (for server)
pub fn check_resolved_outbound_blocked(&self, addr: &SocketAddr) -> bool {
match self.config.acl {
match self.acl() {
None => false,
Some(ref a) => a.check_resolved_outbound_blocked(addr),
}
Expand All @@ -319,7 +335,11 @@ impl Context {
/// Add a record to the reverse lookup cache
#[cfg(feature = "local-dns-relay")]
pub fn add_to_reverse_lookup_cache(&self, addr: &IpAddr, forward: bool) {
let is_exception = self.check_ip_in_proxy_list(addr) != forward;
let is_exception = forward != match self.acl() {
// Proxy everything by default
None => true,
Some(ref a) => a.check_ip_in_proxy_list(addr)
};
let mut reverse_lookup_cache = self.reverse_lookup_cache.lock();
match reverse_lookup_cache.get_mut(addr) {
Some(value) => {
Expand All @@ -338,37 +358,18 @@ impl Context {
}
}

/// Check if domain name is in proxy_list.
/// If so, it should be resolved from remote (for Android's DNS relay)
pub fn check_qname_in_proxy_list(&self, qname: &Address) -> Option<bool> {
match self.config.acl {
// Proxy everything by default
None => None,
Some(ref a) => a.check_qname_in_proxy_list(qname),
}
pub fn acl(&self) -> &Option<AccessControl> {
&self.config.acl
}

#[cfg(feature = "local-dns-relay")]
pub fn check_ip_in_proxy_list(&self, ip: &IpAddr) -> bool {
match self.config.acl {
// Proxy everything by default
None => true,
Some(ref a) => {
// do the reverse lookup in our local cache
let mut reverse_lookup_cache = self.reverse_lookup_cache.lock();
// if a qname is found
if let Some(forward) = reverse_lookup_cache.get(ip) {
*forward
} else {
a.check_ip_in_proxy_list(ip)
}
}
}
pub fn local_dns(&self) -> &LocalUpstream {
&self.local_dns
}

/// Check target address ACL (for client)
pub async fn check_target_bypassed(&self, target: &Address) -> bool {
match self.config.acl {
match self.acl() {
// Proxy everything by default
None => false,
Some(ref a) => {
Expand Down
Loading