use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
pub fn ip_to_reverse_dns(ip: &IpAddr) -> String {
match ip {
IpAddr::V4(ipv4) => {
let octets = ipv4.octets();
format!(
"{}.{}.{}.{}.in-addr.arpa",
octets[3], octets[2], octets[1], octets[0]
)
}
IpAddr::V6(ipv6) => {
let octets = ipv6.octets();
let mut nibbles = Vec::with_capacity(32);
for &byte in octets.iter().rev() {
nibbles.push(format!("{:x}", byte & 0x0F));
nibbles.push(format!("{:x}", (byte >> 4) & 0x0F));
}
nibbles.join(".") + ".ip6.arpa"
}
}
}
pub fn reverse_dns_to_ip(dns_name: &str) -> Option<IpAddr> {
let dns_name = dns_name.to_lowercase();
if dns_name.ends_with(".in-addr.arpa") || dns_name.ends_with(".in-addr.arpa.") {
let parts: Vec<&str> = dns_name
.trim_end_matches('.')
.trim_end_matches(".in-addr.arpa")
.split('.')
.collect();
if parts.len() != 4 {
return None;
}
let mut octets = [0u8; 4];
for i in 0..4 {
octets[i] = parts[3 - i].parse().ok()?;
}
Some(IpAddr::V4(Ipv4Addr::from(octets)))
} else if dns_name.ends_with(".ip6.arpa") || dns_name.ends_with(".ip6.arpa.") {
let nibbles: Vec<u8> = dns_name
.trim_end_matches('.')
.trim_end_matches(".ip6.arpa")
.split('.')
.filter_map(|s| u8::from_str_radix(s, 16).ok())
.collect();
if nibbles.len() != 32 {
return None;
}
let mut reversed = nibbles;
reversed.reverse();
let mut octets = [0u8; 16];
for i in 0..16 {
octets[i] = (reversed[i * 2] << 4) | reversed[i * 2 + 1];
}
Some(IpAddr::V6(Ipv6Addr::from(octets)))
} else {
None }
}
pub fn reverse_dns_to_ipnet(dns_name: &str) -> Option<IpNet> {
let dns_name = dns_name.to_lowercase();
if dns_name.ends_with(".in-addr.arpa") || dns_name.ends_with(".in-addr.arpa.") {
let mut parts: Vec<&str> = dns_name
.trim_end_matches('.')
.trim_end_matches(".in-addr.arpa")
.split('.')
.collect();
parts.reverse();
let prefix_len = (parts.len() * 8) as u8;
if prefix_len > 32 {
return None;
}
let mut octets = [0u8; 4];
for (i, part) in parts.iter().enumerate() {
if i < 4 {
octets[i] = part.parse().ok()?;
}
}
let ip = Ipv4Addr::from(octets);
Ipv4Net::new(ip, prefix_len).map(IpNet::V4).ok()
} else if dns_name.ends_with(".ip6.arpa") || dns_name.ends_with(".ip6.arpa.") {
let mut nibbles: Vec<u8> = dns_name
.trim_end_matches('.')
.trim_end_matches(".ip6.arpa")
.split('.')
.filter_map(|s| u8::from_str_radix(s, 16).ok())
.collect();
nibbles.reverse();
let prefix_len = nibbles.len() * 4;
if prefix_len > 128 {
return None;
}
let mut padded: Vec<u8> = nibbles;
while padded.len() < 32 {
padded.push(0);
}
let mut octets = [0u8; 16];
for i in 0..16 {
octets[i] = (padded[i * 2] << 4) | padded[i * 2 + 1];
}
let ip = Ipv6Addr::from(octets);
Ipv6Net::new(ip, prefix_len as u8).map(IpNet::V6).ok()
} else {
None
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn test_rdns_to_ipv4() {
let v4_dns = "4.3.2.1.in-addr.arpa";
let actual = reverse_dns_to_ip(v4_dns).expect("reverse parse");
assert_eq!(actual, IpAddr::from_str("1.2.3.4").expect("ip address"));
}
#[test]
fn test_invalid_rdns_to_ipv4() {
let v4_dns = "4.3.2.500.in-addr.arpa";
let actual = reverse_dns_to_ip(v4_dns);
assert!(actual.is_none());
}
#[test]
fn test_rdns_to_ipv6() {
let v6_dns = "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa";
let actual = reverse_dns_to_ip(v6_dns).expect("reverse parse");
assert_eq!(
actual,
IpAddr::from_str("2001:db8::567:89ab").expect("ip address")
);
}
#[test]
fn test_invalid_rdns_to_ipv6() {
let v6_dns = "h.a.h.a.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa";
let actual = reverse_dns_to_ip(v6_dns);
assert!(actual.is_none());
}
#[test]
fn test_ipv4_to_reverse_dns() {
let ip = IpAddr::from_str("1.2.3.4").expect("ip address");
let actual = ip_to_reverse_dns(&ip);
assert_eq!(actual, "4.3.2.1.in-addr.arpa");
}
#[test]
fn test_ipv6_to_reverse_dns() {
let ip = IpAddr::from_str("2001:db8::567:89ab").expect("ip address");
let actual = ip_to_reverse_dns(&ip);
assert_eq!(
actual,
"b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"
);
}
#[test]
fn test_roundtrip_ipv4() {
let original_ip = IpAddr::from_str("192.168.1.100").expect("ip address");
let reverse_dns = ip_to_reverse_dns(&original_ip);
let converted_back = reverse_dns_to_ip(&reverse_dns);
assert_eq!(Some(original_ip), converted_back);
}
#[test]
fn test_roundtrip_ipv6() {
let original_ip = IpAddr::from_str("2001:db8::1").expect("ip address");
let reverse_dns = ip_to_reverse_dns(&original_ip);
let converted_back = reverse_dns_to_ip(&reverse_dns);
assert_eq!(Some(original_ip), converted_back);
}
#[test]
fn test_reverse_dns_to_ipnet_single_label_ipv4() {
let dns_name = "10.in-addr.arpa";
let expected: Ipv4Net = "10.0.0.0/8".parse().unwrap();
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V4(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V4");
}
}
#[test]
fn test_reverse_dns_to_ipnet_two_label_ipv4() {
let dns_name = "1.10.in-addr.arpa";
let expected: Ipv4Net = "10.1.0.0/16".parse().unwrap();
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V4(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V4");
}
}
#[test]
fn test_reverse_dns_to_ipnet_three_label_ipv4() {
let dns_name = "1.2.3.in-addr.arpa";
let expected: Ipv4Net = "3.2.1.0/24".parse().unwrap();
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V4(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V4");
}
}
#[test]
fn test_reverse_dns_to_ipnet_four_label_ipv4() {
let dns_name = "1.2.3.4.in-addr.arpa";
let expected: Ipv4Net = "4.3.2.1/32".parse().unwrap();
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V4(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V4");
}
}
#[test]
fn test_reverse_dns_to_ipnet_single_label_ipv6() {
let dns_name = "d.ip6.arpa";
let expected: Ipv6Net = "d000::/4".parse().unwrap();
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V6(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V6");
}
}
#[test]
fn test_reverse_dns_to_ipnet_four_label_ipv6() {
let dns_name = "d.c.b.a.ip6.arpa";
let expected: Ipv6Net = "abcd::/16".parse().unwrap();
eprintln!("expected = {expected:?}");
let result = reverse_dns_to_ipnet(dns_name).expect("should parse");
if let IpNet::V6(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V6");
}
}
#[test]
fn test_reverse_dns_to_ipnet_all_labels_ipv6() {
let addr_str = "2001:db8::567:89ab";
let ip = IpAddr::from_str(addr_str).expect("ip address");
let dns_name = ip_to_reverse_dns(&ip);
let expected: Ipv6Net = format!("{addr_str}/128").parse().unwrap();
let result = reverse_dns_to_ipnet(&dns_name).expect("should parse");
if let IpNet::V6(net) = result {
assert_eq!(net.network(), expected.network());
assert_eq!(net.prefix_len(), expected.prefix_len());
} else {
panic!("expected V6");
}
}
#[test]
fn test_reverse_dns_to_ipnet_not_rdns() {
let result = reverse_dns_to_ipnet("example.com");
assert!(result.is_none());
}
}