use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NssHostnameResult {
pub canonical: String,
pub addrs: Vec<NssAddr>,
pub ttl: u32,
pub secure: bool,
pub from_cache: bool,
pub rcode: u8,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NssAddr {
pub ip: IpAddr,
pub ifindex: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NssReverseResult {
pub names: Vec<String>,
pub ttl: u32,
pub secure: bool,
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum NssBackendError {
#[error("invalid name: {0}")]
InvalidName(String),
#[error("not found")]
NotFound,
#[error("no data")]
NoData,
#[error("timeout")]
Timeout,
#[error("dnssec failure")]
Dnssec,
#[error("unavailable: {0}")]
Unavailable(String),
#[error("internal: {0}")]
Internal(String),
}
pub fn name_to_wire_lower(name: &str) -> Result<Vec<u8>, NssBackendError> {
let raw = name.trim();
if raw.is_empty() {
return Err(NssBackendError::InvalidName("empty".into()));
}
let n = raw.trim_end_matches('.');
if n.is_empty() {
return Ok(vec![0]);
}
if n.len() > 253 {
return Err(NssBackendError::InvalidName("too long".into()));
}
let mut out = Vec::with_capacity(n.len() + 2);
for lab in n.split('.') {
if lab.is_empty() {
return Err(NssBackendError::InvalidName("empty label".into()));
}
if lab.len() > 63 {
return Err(NssBackendError::InvalidName("label too long".into()));
}
if lab.starts_with('-') || lab.ends_with('-') {
}
out.push(lab.len() as u8);
for b in lab.bytes() {
let c = if (b'A'..=b'Z').contains(&b) {
b + 32
} else {
b
};
out.push(c);
}
}
out.push(0);
if out.len() > 255 {
return Err(NssBackendError::InvalidName("wire too long".into()));
}
Ok(out)
}
pub fn wire_to_presentation(wire: &[u8]) -> Result<String, NssBackendError> {
let mut s = String::new();
let mut i = 0usize;
let mut labels = 0usize;
while i < wire.len() {
let l = wire[i] as usize;
if l == 0 {
if s.is_empty() {
return Ok(".".into());
}
return Ok(s);
}
if (l & 0xC0) != 0 {
return Err(NssBackendError::InvalidName("compressed wire".into()));
}
if l > 63 || i + 1 + l > wire.len() {
return Err(NssBackendError::InvalidName("bad label".into()));
}
if !s.is_empty() {
s.push('.');
}
s.push_str(&String::from_utf8_lossy(&wire[i + 1..i + 1 + l]));
i += 1 + l;
labels += 1;
if labels > 128 {
return Err(NssBackendError::InvalidName("too many labels".into()));
}
}
Err(NssBackendError::InvalidName("truncated wire".into()))
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct ShmAddrPack {
pub family: u8,
pub _pad: u8,
pub scope_id: u16,
pub addr: [u8; 16],
}
pub fn ip_to_shm(ip: IpAddr, ifindex: i32) -> ShmAddrPack {
match ip {
IpAddr::V4(v4) => {
let mut addr = [0u8; 16];
addr[..4].copy_from_slice(&v4.octets());
ShmAddrPack {
family: 4,
_pad: 0,
scope_id: ifindex.max(0) as u16,
addr,
}
}
IpAddr::V6(v6) => ShmAddrPack {
family: 6,
_pad: 0,
scope_id: ifindex.max(0) as u16,
addr: v6.octets(),
},
}
}
pub fn shm_to_ip(a: &ShmAddrPack) -> Option<IpAddr> {
match a.family {
4 => Some(IpAddr::V4(Ipv4Addr::new(
a.addr[0], a.addr[1], a.addr[2], a.addr[3],
))),
6 => Some(IpAddr::V6(Ipv6Addr::from(a.addr))),
_ => None,
}
}
pub fn parse_ip_list(ss: &[String]) -> Vec<IpAddr> {
ss.iter().filter_map(|s| s.parse().ok()).collect()
}
pub fn build_address_answer(
id: u16,
qname_wire: &[u8],
qtype: u16,
addrs: &[(IpAddr, i32)],
ttl: u32,
aa: bool,
) -> Vec<u8> {
let mut out = Vec::with_capacity(128);
out.extend_from_slice(&id.to_be_bytes());
let mut flags: u16 = 0x8000; if aa {
flags |= 0x0400;
}
flags |= 0x0080; out.extend_from_slice(&flags.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes()); let ancount = addrs
.iter()
.filter(|(ip, _)| match (qtype, ip) {
(1, IpAddr::V4(_)) | (28, IpAddr::V6(_)) | (255, _) => true,
_ => false,
})
.count() as u16;
out.extend_from_slice(&ancount.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(qname_wire);
out.extend_from_slice(&qtype.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
for (ip, _ifi) in addrs {
let (rtype, rdata): (u16, Vec<u8>) = match (qtype, ip) {
(1, IpAddr::V4(v)) => (1, v.octets().to_vec()),
(28, IpAddr::V6(v)) => (28, v.octets().to_vec()),
(255, IpAddr::V4(v)) => (1, v.octets().to_vec()),
(255, IpAddr::V6(v)) => (28, v.octets().to_vec()),
_ => continue,
};
out.extend_from_slice(&[0xC0, 0x0C]);
out.extend_from_slice(&rtype.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
out.extend_from_slice(&ttl.to_be_bytes());
out.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
out.extend_from_slice(&rdata);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wire_roundtrip() {
let w = name_to_wire_lower("ExAmPle.CoM").unwrap();
assert_eq!(wire_to_presentation(&w).unwrap(), "example.com");
assert_eq!(w.last().copied(), Some(0));
}
#[test]
fn build_a_answer() {
let w = name_to_wire_lower("a.test").unwrap();
let pkt = build_address_answer(
0xABCD,
&w,
1,
&[(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 0)],
60,
false,
);
assert!(pkt.len() > 12);
assert_eq!(pkt[0], 0xAB);
assert_eq!(pkt[2] & 0x80, 0x80);
}
}