Refine handling of ANY queries and CNAME records in answer

This commit is contained in:
Mygod
2020-05-10 04:18:07 +08:00
parent d3a2d12f8e
commit cb62effd94
3 changed files with 160 additions and 132 deletions
+26 -58
View File
@@ -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
View File
@@ -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
View File
@@ -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() {