Skip to content

Commit

Permalink
Merge 37210db into f7366b8
Browse files Browse the repository at this point in the history
  • Loading branch information
cssivision committed Sep 23, 2017
2 parents f7366b8 + 37210db commit 707e8e9
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 7 deletions.
39 changes: 37 additions & 2 deletions integration-tests/tests/lookup_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern crate trust_dns_integration;

use std::net::*;
use std::str::FromStr;
use std::sync::Arc;

use tokio_core::reactor::Core;

Expand All @@ -14,8 +15,11 @@ use trust_dns::op::Query;
use trust_dns::rr::domain;
use trust_dns::rr::{RData, RecordType};
use trust_dns_server::authority::Catalog;
use trust_dns_resolver::lookup::InnerLookupFuture;
use trust_dns_resolver::lookup::{InnerLookupFuture, Lookup};
use trust_dns_resolver::lookup_ip::InnerLookupIpFuture;
use trust_dns_resolver::lookup_state::CachingClient;
use trust_dns_resolver::config::LookupIpStrategy;
use trust_dns_resolver::Hosts;

use trust_dns_integration::TestClientStream;
use trust_dns_integration::authority::create_example;
Expand Down Expand Up @@ -44,6 +48,37 @@ fn test_lookup() {
);
}

#[test]
fn test_lookup_hosts() {
let authority = create_example();
let mut catalog = Catalog::new();
catalog.upsert(authority.origin().clone(), authority);

let mut io_loop = Core::new().unwrap();
let (stream, sender) = TestClientStream::new(catalog);
let client = ClientFuture::new(stream, sender, &io_loop.handle(), None);

let mut hosts = Hosts::default();

hosts
.by_name
.insert(
domain::Name::from_str("www.example.com.").unwrap(),
Lookup::new(Arc::new(vec![RData::A(Ipv4Addr::new(10, 0, 1, 104))]))
);


let lookup = InnerLookupIpFuture::lookup(
vec![domain::Name::from_str("www.example.com.").unwrap()],
LookupIpStrategy::default(),
CachingClient::new(0, client),
Some(Arc::new(hosts)),
);
let lookup = io_loop.run(lookup).unwrap();

assert_eq!(lookup.iter().next().unwrap(), Ipv4Addr::new(10, 0, 1, 104));
}

#[test]
fn test_mock_lookup() {
let resp_query = Query::query(
Expand Down Expand Up @@ -241,4 +276,4 @@ fn test_max_chained_lookup_depth() {
*lookup.iter().next().unwrap(),
RData::A(Ipv4Addr::new(93, 184, 216, 34))
);
}
}
3 changes: 3 additions & 0 deletions resolver/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ pub struct ResolverOpts {
pub ip_strategy: LookupIpStrategy,
/// Cache size is in number of records (some records can be large)
pub cache_size: usize,
/// Check /ect/hosts file before dns requery (only works for unix like OS)
pub use_hosts_file: bool,
}

impl Default for ResolverOpts {
Expand All @@ -236,6 +238,7 @@ impl Default for ResolverOpts {
validate: false,
ip_strategy: LookupIpStrategy::default(),
cache_size: 32,
use_hosts_file: true,
}
}
}
196 changes: 196 additions & 0 deletions resolver/src/hosts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
//! Hosts result from a configuration of `/etc/hosts`

use std::collections::HashMap;
use std::io::{self, BufRead, BufReader};
use std::fs::File;
use std::net::IpAddr;
use std::str::FromStr;
use std::path::Path;
use std::sync::Arc;

use trust_dns::rr::{Name, RData};
use lookup::Lookup;

/// Configuration for the local `/etc/hosts`
#[derive(Debug, Default, Clone)]
pub struct Hosts {
/// Name -> RDatas map
pub by_name: HashMap<Name, Lookup>,
}

impl Hosts {
/// Creates a new configuration from /etc/hosts, only works for unix like OSes,
/// others will return empty configuration
pub fn new() -> Hosts {
read_hosts_conf("/etc/hosts").unwrap_or_default()
}

/// lookup_static_host looks up the addresses for the given host from /etc/hosts.
pub fn lookup_static_host(&self, name: &Name) -> Option<Lookup> {
if self.by_name.len() > 0 {
if let Some(val) = self.by_name.get(name) {
return Some(val.clone());
}
}
None
}
}

/// parse configuration from `/etc/hosts`
#[cfg(unix)]
pub fn read_hosts_conf<P: AsRef<Path>>(path: P) -> io::Result<Hosts> {
let mut hosts = Hosts {
by_name: HashMap::new(),
};

// lines in the file should have the form `addr host1 host2 host3 ...`
// line starts with `#` will be regarded with comments and ignored,
// also empty line also will be ignored,
// if line only include `addr` without `host` will be ignored,
// file will parsed to map in the form `Name -> LookUp`.
let file = File::open(path)?;

for line in BufReader::new(file).lines() {
let line = line.unwrap_or_default();
let line = if let Some(pos) = line.find('#') {
String::from(line.split_at(pos).0)
} else {
line
};
let line = line.trim();
if line.is_empty() {
continue;
}

let fields: Vec<String> = line.split_whitespace().map(|s| s.to_string()).collect();
if fields.len() < 2 {
continue;
}
let addr = if let Some(a) = parse_literal_ip(&fields[0]) {
a
} else {
continue;
};

for i in 1..fields.len() {
let domain = fields[i].to_lowercase();
if let Ok(name) = Name::from_str(&domain) {
let lookup = hosts
.by_name
.entry(name.clone())
.or_insert(Lookup::new(Arc::new(vec![])))
.append(Lookup::new(Arc::new(vec![addr.clone()])));

hosts.by_name.insert(name, lookup);
};
}
}

Ok(hosts)
}

#[cfg(not(unix))]
pub fn read_hosts_conf<P: AsRef<Path>>(path: P) -> io::Result<Hosts> {
Err(io::Error::new(
io::ErrorKind::Other,
"Non-Posix systems currently not supported".to_string(),
))
}

/// parse &str to RData::A or RData::AAAA
pub fn parse_literal_ip(addr: &str) -> Option<RData> {
match IpAddr::from_str(addr) {
Ok(IpAddr::V4(ip4)) => Some(RData::A(ip4)),
Ok(IpAddr::V6(ip6)) => Some(RData::AAAA(ip6)),
Err(e) => {
warn!("could not parse an IP from hosts file: {}", e);
None
},
}
}

#[cfg(unix)]
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::net::{Ipv4Addr, Ipv6Addr};

fn tests_dir() -> String {
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or(".".to_owned());
format!{"{}/../resolver/tests", server_path}
}

#[test]
fn test_parse_literal_ip() {
assert_eq!(
parse_literal_ip("127.0.0.1").expect("failed"),
RData::A(Ipv4Addr::new(127, 0, 0, 1))
);

assert_eq!(
parse_literal_ip("::1").expect("failed"),
RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))
);

assert!(parse_literal_ip("example.com").is_none());
}

#[test]
fn test_read_hosts_conf() {
let path = format!("{}/hosts", tests_dir());
let hosts = read_hosts_conf(&path).unwrap();

let name = Name::from_str("localhost").unwrap();
let rdatas = hosts
.lookup_static_host(&name)
.unwrap()
.iter()
.map(|r| r.to_owned())
.collect::<Vec<RData>>();

assert_eq!(
rdatas,
vec![
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
]
);

let name = Name::from_str("broadcasthost").unwrap();
let rdatas = hosts
.lookup_static_host(&name)
.unwrap()
.iter()
.map(|r| r.to_owned())
.collect::<Vec<RData>>();
assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(255, 255, 255, 255))]);

let name = Name::from_str("example.com").unwrap();
let rdatas = hosts
.lookup_static_host(&name)
.unwrap()
.iter()
.map(|r| r.to_owned())
.collect::<Vec<RData>>();
assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 102))]);

let name = Name::from_str("a.example.com").unwrap();
let rdatas = hosts
.lookup_static_host(&name)
.unwrap()
.iter()
.map(|r| r.to_owned())
.collect::<Vec<RData>>();
assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);

let name = Name::from_str("b.example.com").unwrap();
let rdatas = hosts
.lookup_static_host(&name)
.unwrap()
.iter()
.map(|r| r.to_owned())
.collect::<Vec<RData>>();
assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);
}
}
2 changes: 2 additions & 0 deletions resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ pub mod name_server_pool;
mod resolver;
pub mod system_conf;
mod resolver_future;
mod hosts;

pub use resolver::Resolver;
pub use resolver_future::ResolverFuture;
pub use hosts::Hosts;

/// returns a version as specified in Cargo.toml
pub fn version() -> &'static str {
Expand Down
3 changes: 2 additions & 1 deletion resolver/src/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ pub struct Lookup {
}

impl Lookup {
pub(crate) fn new(rdatas: Arc<Vec<RData>>) -> Self {
/// Return new instance with given rdatas
pub fn new(rdatas: Arc<Vec<RData>>) -> Self {
Lookup { rdatas }
}

Expand Down
20 changes: 17 additions & 3 deletions resolver/src/lookup_ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::error::Error;
use std::io;
use std::mem;
use std::net::IpAddr;
use std::sync::Arc;

use futures::{Async, future, Future, Poll, task};

Expand All @@ -25,6 +26,8 @@ use config::LookupIpStrategy;
use lookup::{Lookup, LookupEither, LookupIter};
use lookup_state::CachingClient;
use name_server_pool::StandardConnection;
use hosts::Hosts;


/// Result of a DNS query when querying for A or AAAA records.
///
Expand Down Expand Up @@ -71,6 +74,7 @@ pub struct InnerLookupIpFuture<C: ClientHandle + 'static> {
names: Vec<Name>,
strategy: LookupIpStrategy,
future: Box<Future<Item = Lookup, Error = io::Error>>,
hosts: Option<Arc<Hosts>>,
}

impl<C: ClientHandle + 'static> InnerLookupIpFuture<C> {
Expand All @@ -81,19 +85,21 @@ impl<C: ClientHandle + 'static> InnerLookupIpFuture<C> {
/// * `names` - a set of DNS names to attempt to resolve, they will be attempted in queue order, i.e. the first is `names.pop()`. Upon each failure, the next will be attempted.
/// * `strategy` - the lookup IP strategy to use
/// * `client_cache` - cache with a connection to use for performing all lookups
pub(crate) fn lookup(
pub fn lookup(
mut names: Vec<Name>,
strategy: LookupIpStrategy,
client_cache: CachingClient<C>,
hosts: Option<Arc<Hosts>>,
) -> Self {
let name = names.pop().expect("can not lookup IPs for no names");

let query = strategic_lookup(name, strategy, client_cache.clone());
let query = strategic_lookup(name, strategy, client_cache.clone(), hosts.clone());
InnerLookupIpFuture {
client_cache: client_cache,
names,
strategy,
future: Box::new(query),
hosts: hosts,
}
}

Expand All @@ -103,7 +109,7 @@ impl<C: ClientHandle + 'static> InnerLookupIpFuture<C> {
) -> Poll<LookupIp, io::Error> {
let name = self.names.pop();
if let Some(name) = name {
let query = strategic_lookup(name, self.strategy, self.client_cache.clone());
let query = strategic_lookup(name, self.strategy, self.client_cache.clone(), self.hosts.clone());

mem::replace(&mut self.future, Box::new(query));
// guarantee that we get scheduled for the next turn...
Expand All @@ -123,6 +129,7 @@ impl<C: ClientHandle + 'static> InnerLookupIpFuture<C> {
future: Box::new(future::err(
io::Error::new(io::ErrorKind::Other, format!("{}", error)),
)),
hosts: None,
};
}
}
Expand Down Expand Up @@ -153,7 +160,14 @@ fn strategic_lookup<C: ClientHandle + 'static>(
name: Name,
strategy: LookupIpStrategy,
client: CachingClient<C>,
hosts: Option<Arc<Hosts>>,
) -> Box<Future<Item = Lookup, Error = io::Error>> {
if let Some(hosts) = hosts {
if let Some(lookup) = hosts.lookup_static_host(&name) {
return Box::new(future::ok(lookup));
};
}

match strategy {
LookupIpStrategy::Ipv4Only => ipv4_only(name, client),
LookupIpStrategy::Ipv6Only => ipv6_only(name, client),
Expand Down

0 comments on commit 707e8e9

Please sign in to comment.