#[cfg(feature = "logging")]
use crate::log::error;
use crate::{dns_parser::split_sub_domain, Error, Result};
use if_addrs::{IfAddr, Interface};
use std::{
collections::{HashMap, HashSet},
convert::TryInto,
fmt,
net::{IpAddr, Ipv4Addr},
str::FromStr,
};
const DNS_HOST_TTL: u32 = 120; const DNS_OTHER_TTL: u32 = 4500;
#[derive(Debug, Clone)]
pub struct ServiceInfo {
ty_domain: String,
sub_domain: Option<String>,
fullname: String, server: String, addresses: HashSet<IpAddr>,
port: u16,
host_ttl: u32, other_ttl: u32, priority: u16,
weight: u16,
txt_properties: TxtProperties,
addr_auto: bool, }
impl ServiceInfo {
pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
ty_domain: &str,
my_name: &str,
host_name: &str,
ip: Ip,
port: u16,
properties: P,
) -> Result<Self> {
let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
let fullname = format!("{}.{}", my_name, ty_domain);
let ty_domain = ty_domain.to_string();
let sub_domain = sub_domain.map(str::to_string);
let server = normalize_hostname(host_name.to_string());
let addresses = ip.as_ip_addrs()?;
let txt_properties = properties.into_txt_properties();
for prop in txt_properties.iter() {
let key = prop.key();
if !key.is_ascii() {
return Err(Error::Msg(format!(
"TXT property key {} is not ASCII",
prop.key()
)));
}
if key.contains('=') {
return Err(Error::Msg(format!(
"TXT property key {} contains '='",
prop.key()
)));
}
}
let this = Self {
ty_domain,
sub_domain,
fullname,
server,
addresses,
port,
host_ttl: DNS_HOST_TTL,
other_ttl: DNS_OTHER_TTL,
priority: 0,
weight: 0,
txt_properties,
addr_auto: false,
};
Ok(this)
}
pub const fn enable_addr_auto(mut self) -> Self {
self.addr_auto = true;
self
}
pub const fn is_addr_auto(&self) -> bool {
self.addr_auto
}
#[inline]
pub fn get_type(&self) -> &str {
&self.ty_domain
}
#[inline]
pub const fn get_subtype(&self) -> &Option<String> {
&self.sub_domain
}
#[inline]
pub fn get_fullname(&self) -> &str {
&self.fullname
}
#[inline]
pub const fn get_properties(&self) -> &TxtProperties {
&self.txt_properties
}
pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
self.txt_properties.get(key)
}
pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
self.txt_properties.get_property_val(key)
}
pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
self.txt_properties.get_property_val_str(key)
}
#[inline]
pub fn get_hostname(&self) -> &str {
&self.server
}
#[inline]
pub const fn get_port(&self) -> u16 {
self.port
}
#[inline]
pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
&self.addresses
}
pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
let mut ipv4_addresses = HashSet::new();
for ip in &self.addresses {
if let IpAddr::V4(ipv4) = ip {
ipv4_addresses.insert(ipv4);
}
}
ipv4_addresses
}
#[inline]
pub const fn get_host_ttl(&self) -> u32 {
self.host_ttl
}
#[inline]
pub const fn get_other_ttl(&self) -> u32 {
self.other_ttl
}
#[inline]
pub const fn get_priority(&self) -> u16 {
self.priority
}
#[inline]
pub const fn get_weight(&self) -> u16 {
self.weight
}
pub(crate) fn get_addrs_on_intf(&self, intf: &Interface) -> Vec<IpAddr> {
self.addresses
.iter()
.filter(|a| valid_ip_on_intf(a, intf))
.copied()
.collect()
}
pub(crate) fn is_ready(&self) -> bool {
let some_missing = self.ty_domain.is_empty()
|| self.fullname.is_empty()
|| self.server.is_empty()
|| self.addresses.is_empty();
!some_missing
}
pub(crate) fn insert_ipaddr(&mut self, addr: IpAddr) {
self.addresses.insert(addr);
}
pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
self.addresses.remove(addr);
}
pub(crate) fn generate_txt(&self) -> Vec<u8> {
encode_txt(self.get_properties().iter())
}
pub(crate) fn set_port(&mut self, port: u16) {
self.port = port;
}
pub(crate) fn set_hostname(&mut self, hostname: String) {
self.server = normalize_hostname(hostname);
}
pub(crate) fn set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
let properties = decode_txt_unique(txt);
if self.txt_properties.properties != properties {
self.txt_properties = TxtProperties { properties };
true
} else {
false
}
}
pub(crate) fn set_subtype(&mut self, subtype: String) {
self.sub_domain = Some(subtype);
}
pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
self.host_ttl = ttl;
}
pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
self.other_ttl = ttl;
}
}
fn normalize_hostname(mut hostname: String) -> String {
if hostname.ends_with(".local.local.") {
let new_len = hostname.len() - "local.".len();
hostname.truncate(new_len);
}
hostname
}
pub trait AsIpAddrs {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
}
impl<T: AsIpAddrs> AsIpAddrs for &T {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
(*self).as_ip_addrs()
}
}
impl AsIpAddrs for &str {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
let mut addrs = HashSet::new();
if !self.is_empty() {
let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
for addr in iter {
let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
addrs.insert(addr);
}
}
Ok(addrs)
}
}
impl AsIpAddrs for String {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
self.as_str().as_ip_addrs()
}
}
impl<I: AsIpAddrs> AsIpAddrs for &[I] {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
let mut addrs = HashSet::new();
for result in self.iter().map(I::as_ip_addrs) {
addrs.extend(result?);
}
Ok(addrs)
}
}
impl AsIpAddrs for () {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
Ok(HashSet::new())
}
}
impl AsIpAddrs for std::net::IpAddr {
fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
let mut ips = HashSet::new();
ips.insert(*self);
Ok(ips)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TxtProperties {
properties: Vec<TxtProperty>,
}
impl TxtProperties {
pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
self.properties.iter()
}
pub fn len(&self) -> usize {
self.properties.len()
}
pub fn is_empty(&self) -> bool {
self.properties.is_empty()
}
pub fn get(&self, key: &str) -> Option<&TxtProperty> {
let key = key.to_lowercase();
self.properties
.iter()
.find(|&prop| prop.key.to_lowercase() == key)
}
pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
self.get(key).map(|x| x.val())
}
pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
self.get(key).map(|x| x.val_str())
}
}
impl fmt::Display for TxtProperties {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let delimiter = ", ";
let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
write!(f, "({})", props.join(delimiter))
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct TxtProperty {
key: String,
val: Option<Vec<u8>>,
}
impl TxtProperty {
pub fn key(&self) -> &str {
&self.key
}
pub fn val(&self) -> Option<&[u8]> {
self.val.as_deref()
}
pub fn val_str(&self) -> &str {
self.val
.as_ref()
.map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
}
}
impl<K, V> From<&(K, V)> for TxtProperty
where
K: ToString,
V: ToString,
{
fn from(prop: &(K, V)) -> Self {
Self {
key: prop.0.to_string(),
val: Some(prop.1.to_string().into_bytes()),
}
}
}
impl<K, V> From<(K, V)> for TxtProperty
where
K: ToString,
V: AsRef<[u8]>,
{
fn from(prop: (K, V)) -> Self {
Self {
key: prop.0.to_string(),
val: Some(prop.1.as_ref().into()),
}
}
}
impl From<&str> for TxtProperty {
fn from(key: &str) -> Self {
Self {
key: key.to_string(),
val: None,
}
}
}
impl fmt::Display for TxtProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.key, self.val_str())
}
}
impl fmt::Debug for TxtProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val_string = self.val.as_ref().map_or_else(
|| "None".to_string(),
|v| {
std::str::from_utf8(&v[..]).map_or_else(
|_| format!("Some({})", u8_slice_to_hex(&v[..])),
|s| format!("Some(\"{}\")", s),
)
},
);
write!(
f,
"TxtProperty {{key: \"{}\", val: {}}}",
&self.key, &val_string,
)
}
}
const HEX_TABLE: [u8; 16] = [
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f',
];
fn u8_slice_to_hex(slice: &[u8]) -> String {
let mut hex = Vec::with_capacity(slice.len() * 2 + 2);
hex.push(b'0');
hex.push(b'x');
for b in slice.iter() {
hex.push(HEX_TABLE[(b >> 4) as usize]);
hex.push(HEX_TABLE[(b & 0x0F) as usize]);
}
String::from_utf8(hex).unwrap()
}
pub trait IntoTxtProperties {
fn into_txt_properties(self) -> TxtProperties;
}
impl IntoTxtProperties for HashMap<String, String> {
fn into_txt_properties(mut self) -> TxtProperties {
let properties = self
.drain()
.map(|(key, val)| TxtProperty {
key,
val: Some(val.into_bytes()),
})
.collect();
TxtProperties { properties }
}
}
impl IntoTxtProperties for Option<HashMap<String, String>> {
fn into_txt_properties(self) -> TxtProperties {
self.map_or_else(
|| TxtProperties {
properties: Vec::new(),
},
|h| h.into_txt_properties(),
)
}
}
impl<'a, T: 'a> IntoTxtProperties for &'a [T]
where
TxtProperty: From<&'a T>,
{
fn into_txt_properties(self) -> TxtProperties {
let mut properties = Vec::new();
let mut keys = HashSet::new();
for t in self.iter() {
let prop = TxtProperty::from(t);
let key = prop.key.to_lowercase();
if keys.insert(key) {
properties.push(prop);
}
}
TxtProperties { properties }
}
}
fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
let mut bytes = Vec::new();
for prop in properties {
let mut s = prop.key.clone().into_bytes();
if let Some(v) = &prop.val {
s.extend(b"=");
s.extend(v);
}
bytes.push(s.len().try_into().unwrap());
bytes.extend(s);
}
if bytes.is_empty() {
bytes.push(0);
}
bytes
}
pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
let mut properties = Vec::new();
let mut offset = 0;
while offset < txt.len() {
let length = txt[offset] as usize;
if length == 0 {
break; }
offset += 1;
let offset_end = offset + length;
if offset_end > txt.len() {
error!("DNS TXT record contains invalid data: Size given for property would be out of range. (offset={}, length={}, offset_end={}, record length={})", offset, length, offset_end, txt.len());
break; }
let kv_bytes = &txt[offset..offset_end];
let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
|| (kv_bytes.to_vec(), None),
|idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
);
match String::from_utf8(k) {
Ok(k_string) => {
properties.push(TxtProperty {
key: k_string,
val: v,
});
}
Err(e) => error!("failed to convert to String from key: {}", e),
}
offset += length;
}
properties
}
fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
let mut properties = decode_txt(txt);
let mut keys = HashSet::new();
properties.retain(|p| {
let key = p.key().to_lowercase();
keys.insert(key) });
properties
}
pub fn valid_ip_on_intf(addr: &IpAddr, intf: &Interface) -> bool {
match (addr, &intf.addr) {
(IpAddr::V4(addr), IfAddr::V4(intf)) => {
let netmask = u32::from(intf.netmask);
let intf_net = u32::from(intf.ip) & netmask;
let addr_net = u32::from(*addr) & netmask;
addr_net == intf_net
}
(IpAddr::V6(addr), IfAddr::V6(intf)) => {
let netmask = u128::from(intf.netmask);
let intf_net = u128::from(intf.ip) & netmask;
let addr_net = u128::from(*addr) & netmask;
addr_net == intf_net
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
#[test]
fn test_txt_encode_decode() {
let properties = vec![
TxtProperty::from(&("key1", "value1")),
TxtProperty::from(&("key2", "value2")),
];
let property_count = properties.len();
let encoded = encode_txt(properties.iter());
assert_eq!(
encoded.len(),
"key1=value1".len() + "key2=value2".len() + property_count
);
assert_eq!(encoded[0] as usize, "key1=value1".len());
let decoded = decode_txt(&encoded);
assert!(properties[..] == decoded[..]);
let properties = vec![TxtProperty::from(&("key3", ""))];
let property_count = properties.len();
let encoded = encode_txt(properties.iter());
assert_eq!(encoded.len(), "key3=".len() + property_count);
let decoded = decode_txt(&encoded);
assert_eq!(properties, decoded);
let binary_val: Vec<u8> = vec![123, 234, 0];
let binary_len = binary_val.len();
let properties = vec![TxtProperty::from(("key4", binary_val))];
let property_count = properties.len();
let encoded = encode_txt(properties.iter());
assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
let decoded = decode_txt(&encoded);
assert_eq!(properties, decoded);
let properties = vec![TxtProperty::from(("key5", "val=5"))];
let property_count = properties.len();
let encoded = encode_txt(properties.iter());
assert_eq!(
encoded.len(),
"key5=".len() + "val=5".len() + property_count
);
let decoded = decode_txt(&encoded);
assert_eq!(properties, decoded);
let properties = vec![TxtProperty::from("key6")];
let property_count = properties.len();
let encoded = encode_txt(properties.iter());
assert_eq!(encoded.len(), "key6".len() + property_count);
let decoded = decode_txt(&encoded);
assert_eq!(properties, decoded);
}
#[test]
fn test_set_properties_from_txt() {
let properties = vec![
TxtProperty::from(&("one", "1")),
TxtProperty::from(&("ONE", "2")),
TxtProperty::from(&("One", "3")),
];
let encoded = encode_txt(properties.iter());
let decoded = decode_txt(&encoded);
assert_eq!(decoded.len(), 3);
let mut service_info =
ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
service_info.set_properties_from_txt(&encoded);
assert_eq!(service_info.get_properties().len(), 1);
let prop = service_info.get_properties().iter().next().unwrap();
assert_eq!(prop.key, "one");
assert_eq!(prop.val_str(), "1");
}
#[test]
fn test_u8_slice_to_hex() {
let bytes = [0x01u8, 0x02u8, 0x03u8];
let hex = u8_slice_to_hex(&bytes);
assert_eq!(hex.as_str(), "0x010203");
let slice = "abcdefghijklmnopqrstuvwxyz";
let hex = u8_slice_to_hex(slice.as_bytes());
assert_eq!(hex.len(), slice.len() * 2 + 2);
assert_eq!(
hex.as_str(),
"0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
);
}
#[test]
fn test_txt_property_debug() {
let prop_1 = TxtProperty {
key: "key1".to_string(),
val: Some("val1".to_string().into()),
};
let prop_1_debug = format!("{:?}", &prop_1);
assert_eq!(
prop_1_debug,
"TxtProperty {key: \"key1\", val: Some(\"val1\")}"
);
let prop_2 = TxtProperty {
key: "key2".to_string(),
val: Some(vec![150u8, 151u8, 152u8]),
};
let prop_2_debug = format!("{:?}", &prop_2);
assert_eq!(
prop_2_debug,
"TxtProperty {key: \"key2\", val: Some(0x969798)}"
);
}
#[test]
fn test_txt_decode_property_size_out_of_bounds() {
let encoded: Vec<u8> = vec![
0x0b, b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
b'1', 0x10, b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
b'2', ];
let decoded = decode_txt(&encoded);
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0].key, "key1");
}
}