mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 16:14:11 +00:00
Refine handling of ANY queries and CNAME records in answer
This commit is contained in:
+26
-58
@@ -6,17 +6,14 @@ use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader, Error, ErrorKind},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
||||
use iprange::IpRange;
|
||||
use regex::{RegexSet, RegexSetBuilder};
|
||||
use trust_dns_proto::{
|
||||
op::Query,
|
||||
rr::{DNSClass, Name, RecordType},
|
||||
};
|
||||
use trust_dns_proto::rr::Name;
|
||||
|
||||
use crate::{context::Context, relay::socks5::Address};
|
||||
|
||||
@@ -89,14 +86,14 @@ impl Rules {
|
||||
self.rule.is_match(host)
|
||||
}
|
||||
|
||||
/// Check if there are no rules for the corresponding DNS query type
|
||||
fn is_rule_empty_for_qtype(&self, qtype: RecordType) -> bool {
|
||||
match qtype {
|
||||
RecordType::A => self.ipv4.iter().next().is_none(),
|
||||
RecordType::AAAA => self.ipv6.iter().next().is_none(),
|
||||
RecordType::PTR => panic!("PTR records should not reach here"),
|
||||
_ => true
|
||||
}
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,16 +280,13 @@ 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_query_in_proxy_list(&self, query: &Query) -> Option<bool> {
|
||||
if query.query_class() != DNSClass::IN || !query.name().is_fqdn() {
|
||||
// unconditionally use default for all non-IN queries and PQDNs
|
||||
pub fn check_name_in_proxy_list(&self, name: &Name) -> Option<bool> {
|
||||
if !name.is_fqdn() {
|
||||
// unconditionally use default for PQDNs
|
||||
return Some(self.is_default_in_proxy_list());
|
||||
}
|
||||
if query.query_type() == RecordType::PTR {
|
||||
return Some(self.check_ptr_qname_in_proxy_list(query.name()));
|
||||
}
|
||||
// remove the last dot from fqdn name
|
||||
let mut name = query.name().to_ascii();
|
||||
let mut name = name.to_ascii();
|
||||
name.pop();
|
||||
let addr = Address::DomainNameAddress(name, 0);
|
||||
// Addresses in proxy_list will be proxied
|
||||
@@ -302,48 +296,22 @@ impl AccessControl {
|
||||
if self.black_list.check_address_matched(&addr) {
|
||||
return Some(false);
|
||||
}
|
||||
match self.mode {
|
||||
Mode::BlackList => if self.black_list.is_rule_empty_for_qtype(query.query_type()) {
|
||||
return Some(true);
|
||||
},
|
||||
Mode::WhiteList => if self.white_list.is_rule_empty_for_qtype(query.query_type()) {
|
||||
return Some(false);
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn check_ptr_qname_in_proxy_list(&self, name: &Name) -> bool {
|
||||
let mut iter = name.iter().rev();
|
||||
let mut next = || std::str::from_utf8(iter.next().unwrap_or(&[48])).unwrap_or("*");
|
||||
if !"arpa".eq_ignore_ascii_case(next()) {
|
||||
return self.is_default_in_proxy_list();
|
||||
/// 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(),
|
||||
}
|
||||
match &next().to_ascii_lowercase()[..] {
|
||||
"in-addr" => {
|
||||
let mut octets: [u8; 4] = [0; 4];
|
||||
for octet in octets.iter_mut() {
|
||||
match next().parse() {
|
||||
Ok(result) => *octet = result,
|
||||
Err(_) => return self.is_default_in_proxy_list(),
|
||||
}
|
||||
}
|
||||
self.check_ip_in_proxy_list(&IpAddr::V4(Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3])))
|
||||
}
|
||||
"ip6" => {
|
||||
let mut segments: [u16; 8] = [0; 8];
|
||||
for segment in segments.iter_mut() {
|
||||
match u16::from_str_radix(&[next(), next(), next(), next()].concat(), 16) {
|
||||
Ok(result) => *segment = result,
|
||||
Err(_) => return self.is_default_in_proxy_list(),
|
||||
}
|
||||
}
|
||||
self.check_ip_in_proxy_list(&IpAddr::V6(Ipv6Addr::new(
|
||||
segments[0], segments[1], segments[2], segments[3],
|
||||
segments[4], segments[5], segments[6], segments[7]
|
||||
)))
|
||||
}
|
||||
_ => self.is_default_in_proxy_list(),
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-40
@@ -17,7 +17,6 @@ use bloomfilter::Bloom;
|
||||
use lru_time_cache::LruCache;
|
||||
use spin::Mutex;
|
||||
use tokio::runtime::Handle;
|
||||
use trust_dns_proto::op::Query;
|
||||
#[cfg(feature = "trust-dns")]
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
@@ -26,6 +25,7 @@ use crate::relay::dns_resolver::create_resolver;
|
||||
#[cfg(feature = "local-flow-stat")]
|
||||
use crate::relay::flow::ServerFlowStatistic;
|
||||
use crate::{
|
||||
acl::AccessControl,
|
||||
config::{Config, ConfigType, ServerConfig},
|
||||
relay::{dns_resolver::resolve, socks5::Address},
|
||||
};
|
||||
@@ -269,7 +269,7 @@ 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),
|
||||
}
|
||||
@@ -277,7 +277,7 @@ impl Context {
|
||||
|
||||
/// 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),
|
||||
}
|
||||
@@ -285,7 +285,7 @@ impl Context {
|
||||
|
||||
/// 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),
|
||||
}
|
||||
@@ -294,7 +294,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) => {
|
||||
@@ -313,45 +317,13 @@ 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_query_in_proxy_list(&self, query: &Query) -> Option<bool> {
|
||||
match self.config.acl {
|
||||
// Proxy everything by default
|
||||
None => Some(true),
|
||||
Some(ref a) => a.check_query_in_proxy_list(query),
|
||||
}
|
||||
}
|
||||
|
||||
#[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 is_default_in_proxy_list(&self) -> bool {
|
||||
match self.config.acl {
|
||||
// Proxy everything by default
|
||||
None => true,
|
||||
Some(ref a) => a.is_default_in_proxy_list(),
|
||||
}
|
||||
pub fn acl(&self) -> &Option<AccessControl> {
|
||||
&self.config.acl
|
||||
}
|
||||
|
||||
/// 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) => {
|
||||
|
||||
+122
-34
@@ -1,24 +1,20 @@
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, SocketAddr},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
use trust_dns_proto::{
|
||||
op::{header::MessageType, response_code::ResponseCode, Message, Query},
|
||||
rr::RData,
|
||||
rr::{DNSClass, Name, RData, RecordType},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
acl::AccessControl,
|
||||
config::ConfigType,
|
||||
context::SharedContext,
|
||||
relay::{
|
||||
@@ -30,8 +26,111 @@ use crate::{
|
||||
|
||||
mod upstream;
|
||||
|
||||
fn should_forward_by_ptr_name(acl: &AccessControl, name: &Name) -> bool {
|
||||
let mut iter = name.iter().rev();
|
||||
let mut next = || std::str::from_utf8(iter.next().unwrap_or(&[48])).unwrap_or("*");
|
||||
if !"arpa".eq_ignore_ascii_case(next()) {
|
||||
return acl.is_default_in_proxy_list();
|
||||
}
|
||||
match &next().to_ascii_lowercase()[..] {
|
||||
"in-addr" => {
|
||||
let mut octets: [u8; 4] = [0; 4];
|
||||
for octet in octets.iter_mut() {
|
||||
match next().parse() {
|
||||
Ok(result) => *octet = result,
|
||||
Err(_) => return acl.is_default_in_proxy_list(),
|
||||
}
|
||||
}
|
||||
acl.check_ip_in_proxy_list(&IpAddr::V4(Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3])))
|
||||
}
|
||||
"ip6" => {
|
||||
let mut segments: [u16; 8] = [0; 8];
|
||||
for segment in segments.iter_mut() {
|
||||
match u16::from_str_radix(&[next(), next(), next(), next()].concat(), 16) {
|
||||
Ok(result) => *segment = result,
|
||||
Err(_) => return acl.is_default_in_proxy_list(),
|
||||
}
|
||||
}
|
||||
acl.check_ip_in_proxy_list(&IpAddr::V6(Ipv6Addr::new(
|
||||
segments[0], segments[1], segments[2], segments[3], segments[4], segments[5], segments[6], segments[7]
|
||||
)))
|
||||
}
|
||||
_ => acl.is_default_in_proxy_list(),
|
||||
}
|
||||
}
|
||||
|
||||
/// given the query, determine whether remote/local query should be used, or inconclusive
|
||||
fn should_forward_by_query(acl: &Option<AccessControl>, query: &Query) -> Option<bool> {
|
||||
if let Some(acl) = acl {
|
||||
if query.query_class() != DNSClass::IN {
|
||||
// unconditionally use default for all non-IN queries
|
||||
Some(acl.is_default_in_proxy_list())
|
||||
} else if query.query_type() == RecordType::PTR {
|
||||
Some(should_forward_by_ptr_name(acl, query.name()))
|
||||
} else {
|
||||
let result = acl.check_name_in_proxy_list(query.name());
|
||||
if result == None && match query.query_type() {
|
||||
RecordType::A => acl.is_ipv4_empty(),
|
||||
RecordType::AAAA => acl.is_ipv6_empty(),
|
||||
RecordType::ANY => acl.is_ipv4_empty() && acl.is_ipv6_empty(),
|
||||
RecordType::PTR => panic!("PTR records should not reach here"),
|
||||
_ => true,
|
||||
} {
|
||||
Some(acl.is_default_in_proxy_list())
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// given the local response, determine whether remote response should be used instead
|
||||
fn should_forward_by_response(
|
||||
acl: &Option<AccessControl>,
|
||||
local_response: &io::Result<Message>,
|
||||
query: &Query,
|
||||
) -> bool {
|
||||
if let Some(acl) = acl {
|
||||
macro_rules! examine_record {
|
||||
($rec:ident, $is_answer:expr) => {
|
||||
if let RData::CNAME(ref name) = $rec.rdata() {
|
||||
match acl.check_name_in_proxy_list(name) {
|
||||
Some(value) => return value,
|
||||
None => continue,
|
||||
}
|
||||
} else if $is_answer && !query.query_type().is_any() && $rec.record_type() != query.query_type() {
|
||||
warn!("local DNS response has inconsistent answer type {} for query {}", $rec.record_type(), query);
|
||||
return true;
|
||||
}
|
||||
let forward = match $rec.rdata() {
|
||||
RData::A(ref ip) => acl.check_ip_in_proxy_list(&IpAddr::from(*ip)),
|
||||
RData::AAAA(ref ip) => acl.check_ip_in_proxy_list(&IpAddr::from(*ip)),
|
||||
RData::PTR(_) => panic!("PTR records should not reach here"),
|
||||
_ => acl.is_default_in_proxy_list(),
|
||||
};
|
||||
if !forward {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Ok(ref local_response) = local_response {
|
||||
for rec in local_response.answers() {
|
||||
examine_record!(rec, true);
|
||||
}
|
||||
for rec in local_response.additionals() {
|
||||
examine_record!(rec, false);
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
panic!("should not reach here")
|
||||
}
|
||||
}
|
||||
|
||||
async fn acl_lookup<Local, Remote>(
|
||||
context: SharedContext,
|
||||
acl: &Option<AccessControl>,
|
||||
local: Arc<Local>,
|
||||
remote: Arc<Remote>,
|
||||
query: &Query
|
||||
@@ -49,42 +148,31 @@ async fn acl_lookup<Local, Remote>(
|
||||
let remote_response_fut = try_timeout(remote.lookup(query), Some(Duration::new(3, 0)));
|
||||
let local_response_fut = try_timeout(local.lookup(query), Some(Duration::new(3, 0)));
|
||||
|
||||
match context.check_query_in_proxy_list(query) {
|
||||
match should_forward_by_query(acl, query) {
|
||||
Some(true) => {
|
||||
let remote_response = remote_response_fut.await;
|
||||
debug!("pick remote response (qname): {:?}", remote_response);
|
||||
debug!("pick remote response (query): {:?}", remote_response);
|
||||
return (remote_response, true);
|
||||
}
|
||||
Some(false) => {
|
||||
let local_response = local_response_fut.await;
|
||||
debug!("pick local response (qname): {:?}", local_response);
|
||||
debug!("pick local response (query): {:?}", local_response);
|
||||
return (local_response, false);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
// FIXME: spawn(remote_response_fut)
|
||||
let local_response = local_response_fut.await.unwrap_or_else(|_| Message::new());
|
||||
for rec in local_response.answers() {
|
||||
if rec.record_type() != query.query_type() {
|
||||
warn!("local DNS response has inconsistent answer type {} for query {}", rec.record_type(), query);
|
||||
break
|
||||
}
|
||||
let forward = match rec.rdata() {
|
||||
RData::A(ref ip) => context.check_ip_in_proxy_list(&IpAddr::from(*ip)),
|
||||
RData::AAAA(ref ip) => context.check_ip_in_proxy_list(&IpAddr::from(*ip)),
|
||||
RData::PTR(_) => panic!("PTR records should not reach here"),
|
||||
_ => context.is_default_in_proxy_list(),
|
||||
};
|
||||
if !forward {
|
||||
debug!("pick local response (response): {:?}", local_response);
|
||||
return (Ok(local_response), false);
|
||||
}
|
||||
}
|
||||
let local_response = local_response_fut.await;
|
||||
|
||||
let remote_response = remote_response_fut.await;
|
||||
debug!("pick remote response (response): {:?}", remote_response);
|
||||
(remote_response, true)
|
||||
if should_forward_by_response(acl, &local_response, query) {
|
||||
let remote_response = remote_response_fut.await;
|
||||
debug!("pick remote response (response): {:?}", remote_response);
|
||||
(remote_response, true)
|
||||
} else {
|
||||
debug!("pick local response (response): {:?}", local_response);
|
||||
(local_response, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a DNS relay local server
|
||||
@@ -123,11 +211,11 @@ pub async fn run(context: SharedContext) -> io::Result<()> {
|
||||
|
||||
let config = context.config();
|
||||
#[cfg(target_os = "android")]
|
||||
let local_upstream = Arc::new(upstream::UnixSocketUpstream {
|
||||
let local_upstream = Arc::new(upstream::UnixSocketUpstream {
|
||||
path: config.local_dns_path.clone().expect("local query DNS path"),
|
||||
});
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let local_upstream = Arc::new(upstream::UdpUpstream {
|
||||
let local_upstream = Arc::new(upstream::UdpUpstream {
|
||||
server: config.local_dns_addr.clone().expect("local query DNS address"),
|
||||
});
|
||||
// FIXME: We use TCP to send remote queries by default, which should be configuable.
|
||||
@@ -174,7 +262,7 @@ pub async fn run(context: SharedContext) -> io::Result<()> {
|
||||
message.set_response_code(ResponseCode::FormErr);
|
||||
} else {
|
||||
let question = &request.queries()[0];
|
||||
let (r, forward) = acl_lookup(context.clone(), local_upstream, remote_upstream, question).await;
|
||||
let (r, forward) = acl_lookup(context.acl(), local_upstream, remote_upstream, question).await;
|
||||
|
||||
if let Ok(result) = r {
|
||||
for rec in result.answers() {
|
||||
|
||||
Reference in New Issue
Block a user