Skip to content

Commit

Permalink
Initial lookup command
Browse files Browse the repository at this point in the history
  • Loading branch information
tertsdiepraam committed May 10, 2024
1 parent d181fbc commit a8bfd73
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 0 deletions.
128 changes: 128 additions & 0 deletions src/commands/lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! The lookup command of _dnsi._

use crate::error::Error;
use domain::base::name::UncertainName;
use domain::resolv::stub::StubResolver;
use std::net::IpAddr;
use std::str::FromStr;

//------------ Lookup --------------------------------------------------------

#[derive(Clone, Debug, clap::Args)]
pub struct Lookup {
/// The host or address to look up.
#[arg(value_name = "HOST_OR_ADDR")]
names: Vec<ServerName>,
}

/// # Executing the command
///
impl Lookup {
pub fn execute(self) -> Result<(), Error> {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(self.async_execute())
}

pub async fn async_execute(self) -> Result<(), Error> {
let resolver = StubResolver::new();

let mut res = Ok(());
let mut names = self.names.iter();

if let Some(name) = names.next() {
res = res.and(self.execute_one_name(&resolver, name).await);
}

for name in names {
println!();
res = res.and(self.execute_one_name(&resolver, name).await);
}

res.map_err(|_| "not all lookups have succeeded".into())
}

async fn execute_one_name(&self, resolver: &StubResolver, name: &ServerName) -> Result<(), ()> {
let res = match name {
ServerName::Name(host) => forward(resolver, host).await,
ServerName::Addr(addr) => reverse(resolver, *addr).await,
};

if let Err(err) = res {
eprintln!("{err}");
return Err(());
}

Ok(())
}
}

async fn forward(resolver: &StubResolver, name: &UncertainName<Vec<u8>>) -> Result<(), Error> {
let answer = match name {
UncertainName::Absolute(ref name) => resolver.lookup_host(name).await?,
UncertainName::Relative(ref name) => resolver.search_host(name).await?,
};

print!("{name}");

let canon = answer.canonical_name();
if canon != answer.qname() {
print!(" (alias for {canon})");
}

println!();

if answer.is_empty() {
println!(" <no addresses found>");
return Ok(());
}

for addr in answer.iter() {
println!(" {addr}");
}

Ok(())
}

async fn reverse(resolver: &StubResolver, addr: IpAddr) -> Result<(), Error> {
let answer = resolver.lookup_addr(addr).await?;
println!("{addr}");

// Little workaround for not having an is_empty method on FoundAddrs
let mut answer = answer.iter().peekable();
let is_empty = answer.peek().is_none();
if is_empty {
println!(" <no hosts found>");
return Ok(());
}

for name in answer {
println!(" {name}");
}

Ok(())
}

//------------ ServerName ---------------------------------------------------

#[derive(Clone, Debug)]
enum ServerName {
Name(UncertainName<Vec<u8>>),
Addr(IpAddr),
}

impl FromStr for ServerName {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(addr) = IpAddr::from_str(s) {
Ok(ServerName::Addr(addr))
} else {
UncertainName::from_str(s)
.map(Self::Name)
.map_err(|_| "illegal host name or address")
}
}
}
5 changes: 5 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod help;
pub mod query;
pub mod lookup;


use super::error::Error;
Expand All @@ -12,6 +13,9 @@ pub enum Commands {
/// Query the DNS.
Query(self::query::Query),

/// Lookup a host or address.
Lookup(self::lookup::Lookup),

/// Show the manual pages.
Man(self::help::Help),
}
Expand All @@ -20,6 +24,7 @@ impl Commands {
pub fn execute(self) -> Result<(), Error> {
match self {
Self::Query(query) => query.execute(),
Self::Lookup(lookup) => lookup.execute(),
Self::Man(help) => help.execute(),
}
}
Expand Down

0 comments on commit a8bfd73

Please sign in to comment.