use anyhow::Result;
use byteorder::{BigEndian, WriteBytesExt};
use clap::Parser;
use socket2::{Domain, Protocol, Socket, Type};
use std::io::Write;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
#[derive(Debug, Clone)]
pub enum DnsRecord {
Ptr { name: String, target: String, ttl: u32 },
Srv { name: String, priority: u16, weight: u16, port: u16, target: String, ttl: u32 },
Txt { name: String, entries: Vec<String>, ttl: u32 },
A { name: String, addr: Ipv4Addr, ttl: u32 },
Aaaa { name: String, addr: Ipv6Addr, ttl: u32 },
}
#[derive(Debug, Clone, Default)]
pub struct MdnsResponseBuilder {
answers: Vec<DnsRecord>,
additional: Vec<DnsRecord>,
}
impl MdnsResponseBuilder {
pub fn new() -> Self {
Self {
answers: Vec::new(),
additional: Vec::new(),
}
}
pub fn add_answer(mut self, record: DnsRecord) -> Self {
self.answers.push(record);
self
}
pub fn add_additional(mut self, record: DnsRecord) -> Self {
self.additional.push(record);
self
}
pub fn encode(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(512);
out.write_u16::<BigEndian>(0)?; out.write_u16::<BigEndian>(0x8400)?; out.write_u16::<BigEndian>(0)?; out.write_u16::<BigEndian>(self.answers.len() as u16)?; out.write_u16::<BigEndian>(0)?; out.write_u16::<BigEndian>(self.additional.len() as u16)?;
let name_offsets = &mut std::collections::HashMap::new();
for record in &self.answers {
encode_record(record, &mut out, name_offsets)?;
}
for record in &self.additional {
encode_record(record, &mut out, name_offsets)?;
}
Ok(out)
}
}
fn encode_record(record: &DnsRecord, out: &mut Vec<u8>, name_offsets: &mut std::collections::HashMap<String, usize>) -> Result<()> {
match record {
DnsRecord::Ptr { name, target, ttl } => {
matc::mdns::encode_label_compressed(name, out, name_offsets)?;
out.write_u16::<BigEndian>(matc::mdns::TYPE_PTR)?;
out.write_u16::<BigEndian>(0x0001)?;
out.write_u32::<BigEndian>(*ttl)?;
let mut rdata = Vec::new();
matc::mdns::encode_label(target, &mut rdata)?;
out.write_u16::<BigEndian>(rdata.len() as u16)?;
out.write_all(&rdata)?;
}
DnsRecord::Srv { name, priority, weight, port, target, ttl } => {
matc::mdns::encode_label_compressed(name, out, name_offsets)?;
out.write_u16::<BigEndian>(matc::mdns::TYPE_SRV)?;
out.write_u16::<BigEndian>(0x0001)?;
out.write_u32::<BigEndian>(*ttl)?;
let mut rdata = Vec::new();
rdata.write_u16::<BigEndian>(*priority)?;
rdata.write_u16::<BigEndian>(*weight)?;
rdata.write_u16::<BigEndian>(*port)?;
matc::mdns::encode_label(target, &mut rdata)?;
out.write_u16::<BigEndian>(rdata.len() as u16)?;
out.write_all(&rdata)?;
}
DnsRecord::Txt { name, entries, ttl } => {
matc::mdns::encode_label_compressed(name, out, name_offsets)?;
out.write_u16::<BigEndian>(matc::mdns::TYPE_TXT)?;
out.write_u16::<BigEndian>(0x0001)?;
out.write_u32::<BigEndian>(*ttl)?;
let mut rdata = Vec::new();
for entry in entries {
let bytes = entry.as_bytes();
if bytes.len() > 255 {
anyhow::bail!("TXT entry exceeds 255 bytes: {}", bytes.len());
}
rdata.write_u8(bytes.len() as u8)?;
rdata.write_all(bytes)?;
}
out.write_u16::<BigEndian>(rdata.len() as u16)?;
out.write_all(&rdata)?;
}
DnsRecord::A { name, addr, ttl } => {
matc::mdns::encode_label_compressed(name, out, name_offsets)?;
out.write_u16::<BigEndian>(matc::mdns::TYPE_A)?;
out.write_u16::<BigEndian>(0x0001)?;
out.write_u32::<BigEndian>(*ttl)?;
let octets = addr.octets();
out.write_u16::<BigEndian>(4)?; out.write_all(&octets)?;
}
DnsRecord::Aaaa { name, addr, ttl } => {
matc::mdns::encode_label_compressed(name, out, name_offsets)?;
out.write_u16::<BigEndian>(matc::mdns::TYPE_AAAA)?;
out.write_u16::<BigEndian>(0x0001)?;
out.write_u32::<BigEndian>(*ttl)?;
let octets = addr.octets();
out.write_u16::<BigEndian>(16)?; out.write_all(&octets)?;
}
}
Ok(())
}
#[derive(Parser)]
#[command(about = "Send a raw mDNS response packet over UDP multicast")]
struct Args {
packet_hex: Option<String>,
}
fn sample_response() -> Vec<u8> {
MdnsResponseBuilder::new()
.add_answer(DnsRecord::Ptr {
name: "_services._dns-sd._udp.local".to_string(),
target: "_matter._tcp.local".to_string(),
ttl: 4500,
})
.add_answer(DnsRecord::Ptr {
name: "_matter._tcp.local".to_string(),
target: "mymatterdevice._matter._tcp.local".to_string(),
ttl: 120,
})
.add_answer(DnsRecord::Ptr {
name: "_matter._tcp.local".to_string(),
target: "mymatterdevice2._matter._tcp.local".to_string(),
ttl: 120,
})
.add_additional(DnsRecord::Srv {
name: "mymatterdevice._matter._tcp.local".to_string(),
priority: 0,
weight: 0,
port: 5540,
target: "mymatterdevice.local".to_string(),
ttl: 120,
})
.add_additional(DnsRecord::Srv {
name: "mymatterdevice2._matter._tcp.local".to_string(),
priority: 0,
weight: 0,
port: 5540,
target: "mymatterdevice2.local".to_string(),
ttl: 120,
})
.add_additional(DnsRecord::A {
name: "mymatterdevice.local".to_string(),
addr: Ipv4Addr::new(192, 168, 1, 100),
ttl: 120,
})
.add_additional(DnsRecord::A {
name: "mymatterdevice2.local".to_string(),
addr: Ipv4Addr::new(192, 168, 1, 101),
ttl: 120,
})
.add_additional(DnsRecord::A {
name: "mymatterdevice2.local".to_string(),
addr: Ipv4Addr::new(192, 168, 1, 102),
ttl: 120,
})
.add_additional(DnsRecord::Aaaa {
name: "mymatterdevice.local".to_string(),
addr: Ipv6Addr::new(0xfe80, 0, 0, 0, 0x1234, 0x5678, 0xabcd, 0xef00),
ttl: 120,
})
.add_additional(DnsRecord::Txt {
name: "mymatterdevice._matter._tcp.local".to_string(),
entries: vec![
"D=840".to_string(), "VP=1234+5678".to_string(), "CM=1".to_string(), "DT=259".to_string(), ],
ttl: 120,
})
.add_additional(DnsRecord::Txt {
name: "mymatterdevice2._matter._tcp.local".to_string(),
entries: vec![
"D=840".to_string(), "VP=1234+5678".to_string(), "CM=1".to_string(), "DT=259".to_string(), ],
ttl: 120,
})
.encode().unwrap()
}
fn main() -> Result<()> {
let args = Args::parse();
let data = {
if let Some(ref hex) = args.packet_hex {
hex::decode(hex).unwrap()
} else {
sample_response()
}
};
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.set_reuse_address(true)?;
socket.set_reuse_port(true)?;
socket.bind(&SocketAddr::from(([0, 0, 0, 0], 5353)).into())?;
let std_socket: UdpSocket = socket.into();
let dest: SocketAddr = "224.0.0.251:5353".parse()?;
let sent = std_socket.send_to(&data, dest)?;
println!("Sent {sent} bytes to {dest}");
Ok(())
}