use std::{env::args, fmt::Display, net::IpAddr};
use futures_util::{pin_mut, StreamExt};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zlink::{proxy, ReplyError};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let mut connection = zlink::unix::connect("/run/systemd/resolve/io.systemd.Resolve").await?;
let args: Vec<_> = args().skip(1).collect();
if args.is_empty() {
eprintln!("Usage: resolved <hostname> [<hostname> ...]");
return Ok(());
}
let mut chain = connection.chain_resolve_hostname(&args[0])?;
for name in &args[1..] {
chain = chain.resolve_hostname(name)?;
}
let replies = chain.send::<ReplyParams, ReplyError>().await?;
pin_mut!(replies);
let mut i = 0;
while let Some(reply) = replies.next().await {
let name = &args[i];
i += 1;
let (result, _fds) = reply?;
match result {
Ok(reply) => {
println!("Results for '{name}':");
for address in reply.into_parameters().unwrap().addresses {
println!("\t{address}");
}
}
Err(e) => eprintln!("Error resolving '{name}': {e}"),
}
}
Ok(())
}
#[proxy("io.systemd.Resolve")]
trait ResolvedProxy {
#[allow(unused)]
async fn resolve_hostname(
&mut self,
name: &str,
) -> zlink::Result<Result<ReplyParams, ReplyError>>;
}
#[derive(Debug, serde::Deserialize)]
struct ReplyParams {
addresses: Vec<ResolvedAddress>,
#[serde(rename = "name")]
_name: String,
}
#[derive(Debug, serde::Deserialize)]
struct ResolvedAddress {
family: ProtocolFamily,
address: Vec<u8>,
}
impl Display for ResolvedAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ip = match self.family {
ProtocolFamily::Inet => {
let ip = <[u8; 4]>::try_from(self.address.as_slice())
.map(IpAddr::from)
.unwrap();
format!("IPv4: {ip}")
}
ProtocolFamily::Inet6 => {
let ip = <[u8; 16]>::try_from(self.address.as_slice())
.map(IpAddr::from)
.unwrap();
format!("IPv6: {ip}")
}
ProtocolFamily::Unspec => {
format!("Unspecified protocol family: {:?}", self.address)
}
};
write!(f, "{ip}")
}
}
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u8)]
enum ProtocolFamily {
Unspec = 0, Inet = 2, Inet6 = 10, }
#[derive(Debug, ReplyError)]
#[zlink(interface = "io.systemd.Resolve")]
enum ReplyError {
NoNameServers,
NoSuchResourceRecord,
QueryTimedOut,
MaxAttemptsReached,
InvalidReply,
QueryAborted,
DNSSECValidationFailed {
#[zlink(rename = "result")]
_result: String,
#[zlink(rename = "extendedDNSErrorCode")]
_extended_dns_error_code: Option<i32>,
#[zlink(rename = "extendedDNSErrorMessage")]
_extended_dns_error_message: Option<String>,
},
NoTrustAnchor,
ResourceRecordTypeUnsupported,
NetworkDown,
NoSource,
StubLoop,
DNSError {
#[zlink(rename = "rcode")]
_rcode: i32,
#[zlink(rename = "extendedDNSErrorCode")]
_extended_dns_error_code: Option<i32>,
#[zlink(rename = "extendedDNSErrorMessage")]
_extended_dns_error_message: Option<String>,
},
CNAMELoop,
BadAddressSize,
ResourceRecordTypeInvalidForQuery,
ZoneTransfersNotPermitted,
ResourceRecordTypeObsolete,
}
impl Display for ReplyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for ReplyError {}