├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── chromecast_discovery.rs ├── http_discovery.rs └── resolve_hosts.rs └── src ├── discover.rs ├── errors.rs ├── io.rs ├── lib.rs ├── mdns.rs ├── resolve.rs └── response.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - nightly 5 | - beta 6 | - stable 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mdns" 3 | version = "3.0.0" 4 | authors = ["Dylan McKay "] 5 | edition = "2018" 6 | 7 | description = """ 8 | A multicast DNS client library. 9 | 10 | Supports discovery of any mDNS device on a LAN. 11 | """ 12 | 13 | license = "MIT" 14 | documentation = "https://docs.rs/mdns" 15 | repository = "https://github.com/dylanmckay/mdns" 16 | 17 | categories = ["network-programming"] 18 | keywords = ["mdns", "dns", "multicast", "chromecast", "discovery"] 19 | 20 | [dependencies] 21 | dns-parser = "0.8.0" 22 | net2 = "0.2" 23 | err-derive = "0.2.1" 24 | futures-core = "0.3.1" 25 | futures-util = "0.3.1" 26 | log = "0.4" 27 | async-stream = "0.2.0" 28 | async-std = { version = "1.6.2", features = ["unstable", "attributes"] } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Dylan McKay 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mdns 2 | 3 | [![Build Status](https://travis-ci.org/dylanmckay/mdns.svg?branch=master)](https://travis-ci.org/dylanmckay/mdns) 4 | [![crates.io](https://img.shields.io/crates/v/mdns.svg)](https://crates.io/crates/mdns) 5 | [![MIT license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 6 | 7 | [Documentation](https://docs.rs/mdns) 8 | 9 | An multicast DNS client in Rust. 10 | 11 | Error logging is handled with the `log` library. 12 | 13 | [Wikipedia](https://en.wikipedia.org/wiki/Multicast_DNS) 14 | 15 | ## Example 16 | 17 | Find IP addresses for all Chromecasts on the local network. 18 | 19 | ```rust 20 | use futures_util::{pin_mut, stream::StreamExt}; 21 | use mdns::{Error, Record, RecordKind}; 22 | use std::{net::IpAddr, time::Duration}; 23 | 24 | 25 | const SERVICE_NAME: &'static str = "_googlecast._tcp.local"; 26 | 27 | #[async_std::main] 28 | async fn main() -> Result<(), Error> { 29 | // Iterate through responses from each Cast device, asking for new devices every 15s 30 | let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(15))?.listen(); 31 | pin_mut!(stream); 32 | 33 | while let Some(Ok(response)) = stream.next().await { 34 | let addr = response.records() 35 | .filter_map(self::to_ip_addr) 36 | .next(); 37 | 38 | if let Some(addr) = addr { 39 | println!("found cast device at {}", addr); 40 | } else { 41 | println!("cast device does not advertise address"); 42 | } 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | fn to_ip_addr(record: &Record) -> Option { 49 | match record.kind { 50 | RecordKind::A(addr) => Some(addr.into()), 51 | RecordKind::AAAA(addr) => Some(addr.into()), 52 | _ => None, 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /examples/chromecast_discovery.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{pin_mut, stream::StreamExt}; 2 | use mdns::Error; 3 | use std::time::Duration; 4 | 5 | const SERVICE_NAME: &'static str = "_googlecast._tcp.local"; 6 | 7 | #[async_std::main] 8 | async fn main() -> Result<(), Error> { 9 | let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(15))?.listen(); 10 | pin_mut!(stream); 11 | while let Some(Ok(response)) = stream.next().await { 12 | let addr = response.socket_address(); 13 | let host = response.hostname(); 14 | 15 | if let (Some(host), Some(addr)) = (host, addr) { 16 | println!("found cast device {} at {}", host, addr); 17 | } else { 18 | println!("cast device does not advertise address"); 19 | } 20 | } 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/http_discovery.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{pin_mut, stream::StreamExt}; 2 | use mdns::Error; 3 | use std::time::Duration; 4 | 5 | const SERVICE_NAME: &'static str = "_http._tcp.local"; 6 | 7 | #[async_std::main] 8 | async fn main() -> Result<(), Error> { 9 | let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(15))?.listen(); 10 | pin_mut!(stream); 11 | while let Some(Ok(response)) = stream.next().await { 12 | let addr = response.ip_addr(); 13 | 14 | if let Some(addr) = addr { 15 | println!("found cast device at {}", addr); 16 | } else { 17 | println!("cast device does not advertise address"); 18 | } 19 | } 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /examples/resolve_hosts.rs: -------------------------------------------------------------------------------- 1 | use mdns::Error; 2 | use std::time::Duration; 3 | 4 | const SERVICE_NAME: &'static str = "_http._tcp.local"; 5 | const HOSTS: [&'static str; 2] = ["server1._http._tcp.local", "server2._http._tcp.local"]; 6 | 7 | #[async_std::main] 8 | async fn main() -> Result<(), Error> { 9 | let responses = mdns::resolve::multiple(SERVICE_NAME, &HOSTS, Duration::from_secs(15)).await?; 10 | 11 | for response in responses { 12 | if let (Some(host), Some(ip)) = (response.hostname(), response.ip_addr()) { 13 | println!("found host {} at {}", host, ip) 14 | } 15 | } 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /src/discover.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for discovering devices on the LAN. 2 | //! 3 | //! Examples 4 | //! 5 | //! ```rust,no_run 6 | //! use futures_util::{pin_mut, stream::StreamExt}; 7 | //! use mdns::{Error, Record, RecordKind}; 8 | //! use std::time::Duration; 9 | //! 10 | //! const SERVICE_NAME: &'static str = "_googlecast._tcp.local"; 11 | //! 12 | //! #[async_std::main] 13 | //! async fn main() -> Result<(), Error> { 14 | //! let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(15))?.listen(); 15 | //! pin_mut!(stream); 16 | //! 17 | //! while let Some(Ok(response)) = stream.next().await { 18 | //! println!("{:?}", response); 19 | //! } 20 | //! 21 | //! Ok(()) 22 | //! } 23 | //! ``` 24 | 25 | use crate::{mDNSListener, Error, Response}; 26 | 27 | use std::time::Duration; 28 | 29 | use crate::mdns::{mDNSSender, mdns_interface}; 30 | use futures_core::Stream; 31 | use futures_util::{future::ready, stream::select, StreamExt}; 32 | use std::net::Ipv4Addr; 33 | 34 | /// A multicast DNS discovery request. 35 | /// 36 | /// This represents a single lookup of a single service name. 37 | /// 38 | /// This object can be iterated over to yield the received mDNS responses. 39 | pub struct Discovery { 40 | service_name: String, 41 | 42 | mdns_sender: mDNSSender, 43 | mdns_listener: mDNSListener, 44 | 45 | /// Whether we should ignore empty responses. 46 | ignore_empty: bool, 47 | 48 | /// The interval we should send mDNS queries. 49 | send_request_interval: Duration, 50 | } 51 | 52 | /// Gets an iterator over all responses for a given service on all interfaces. 53 | pub fn all(service_name: S, mdns_query_interval: Duration) -> Result 54 | where 55 | S: AsRef, 56 | { 57 | interface(service_name, mdns_query_interval, Ipv4Addr::new(0, 0, 0, 0)) 58 | } 59 | 60 | /// Gets an iterator over all responses for a given service on a given interface. 61 | pub fn interface( 62 | service_name: S, 63 | mdns_query_interval: Duration, 64 | interface_addr: Ipv4Addr, 65 | ) -> Result 66 | where 67 | S: AsRef, 68 | { 69 | let service_name = service_name.as_ref().to_string(); 70 | let (mdns_listener, mdns_sender) = mdns_interface(service_name.clone(), interface_addr)?; 71 | 72 | Ok(Discovery { 73 | service_name, 74 | mdns_sender, 75 | mdns_listener, 76 | ignore_empty: true, 77 | send_request_interval: mdns_query_interval, 78 | }) 79 | } 80 | 81 | impl Discovery { 82 | /// Sets whether or not we should ignore empty responses. 83 | /// 84 | /// Defaults to `true`. 85 | pub fn ignore_empty(mut self, ignore: bool) -> Self { 86 | self.ignore_empty = ignore; 87 | self 88 | } 89 | 90 | pub fn listen(self) -> impl Stream> { 91 | let ignore_empty = self.ignore_empty; 92 | let service_name = self.service_name; 93 | let response_stream = self.mdns_listener.listen().map(StreamResult::Response); 94 | let sender = self.mdns_sender.clone(); 95 | 96 | let interval_stream = async_std::stream::interval(self.send_request_interval) 97 | // I don't like the double clone, I can't find a prettier way to do this 98 | .map(move |_| { 99 | let mut sender = sender.clone(); 100 | async_std::task::spawn(async move { 101 | let _ = sender.send_request().await; 102 | }); 103 | StreamResult::Interval 104 | }); 105 | 106 | let stream = select(response_stream, interval_stream); 107 | stream 108 | .filter_map(|stream_result| { 109 | async { 110 | match stream_result { 111 | StreamResult::Interval => None, 112 | StreamResult::Response(res) => Some(res), 113 | } 114 | } 115 | }) 116 | .filter(move |res| { 117 | ready(match res { 118 | Ok(response) => { 119 | (!response.is_empty() || !ignore_empty) 120 | && response 121 | .answers 122 | .iter() 123 | .any(|record| record.name == service_name) 124 | } 125 | Err(_) => true, 126 | }) 127 | }) 128 | } 129 | } 130 | 131 | enum StreamResult { 132 | Interval, 133 | Response(Result), 134 | } 135 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use err_derive::Error; 2 | 3 | #[derive(Debug, Error)] 4 | pub enum Error { 5 | #[error(display = "_0")] 6 | Io(#[error(source)] std::io::Error), 7 | #[error(display = "_0")] 8 | Dns(#[error(source)] dns_parser::Error), 9 | #[error(display = "_0")] 10 | TimeoutError(#[error(source)] async_std::future::TimeoutError), 11 | } 12 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, mDNS}; 2 | 3 | use std::time::Duration; 4 | use mio; 5 | 6 | pub struct Io 7 | { 8 | pub poll: mio::Poll, 9 | pub events: mio::Events, 10 | token_accumulator: usize, 11 | } 12 | 13 | impl Io 14 | { 15 | pub fn new() -> Result { 16 | let poll = mio::Poll::new()?; 17 | let events = mio::Events::with_capacity(1024); 18 | 19 | Ok(Io { 20 | poll, 21 | events, 22 | token_accumulator: 0, 23 | }) 24 | } 25 | 26 | pub fn poll(&mut self, 27 | mdns: &mut mDNS, 28 | timeout: Option) 29 | -> Result<(), Error> { 30 | self.poll.poll(&mut self.events, timeout)?; 31 | 32 | for event in self.events.iter() { 33 | if event.readiness().is_readable() { mdns.recv(event.token())? }; 34 | if event.readiness().is_writable() { mdns.send(event.token())? }; 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | pub fn create_token(&mut self) -> mio::Token { 41 | let token = mio::Token(self.token_accumulator); 42 | self.token_accumulator += 1; 43 | token 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [Multicast DNS](https://en.wikipedia.org/wiki/Multicast_DNS) library with built-in networking. 2 | //! 3 | //! This crate can be used to discover mDNS devices that are listening 4 | //! on a network. 5 | //! 6 | //! # Basic usage 7 | //! 8 | //! This example finds all [Chromecast](https://en.wikipedia.org/wiki/Chromecast) devices on the 9 | //! same LAN as the executing computer. 10 | //! 11 | //! Once the devices are discovered, they respond with standard DNS records, with a few minor 12 | //! low-level protocol differences. 13 | //! 14 | //! The only Chromecast-specific piece of code here is the `SERVICE_NAME`. In order to discover 15 | //! other types of devices, simply change the service name to the one your device uses. 16 | //! 17 | //! This example obtains the IP addresses of the cast devices by looking up `A`/`AAAA` records. 18 | //! 19 | //! ```rust,no_run 20 | //! use futures_util::{pin_mut, stream::StreamExt}; 21 | //! use mdns::{Error, Record, RecordKind}; 22 | //! use std::{net::IpAddr, time::Duration}; 23 | //! 24 | //! /// The hostname of the devices we are searching for. 25 | //! /// Every Chromecast will respond to the service name in this example. 26 | //! const SERVICE_NAME: &'static str = "_googlecast._tcp.local"; 27 | //! 28 | //! #[async_std::main] 29 | //! async fn main() -> Result<(), Error> { 30 | //! // Iterate through responses from each Cast device, asking for new devices every 15s 31 | //! let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(15))?.listen(); 32 | //! pin_mut!(stream); 33 | //! 34 | //! while let Some(Ok(response)) = stream.next().await { 35 | //! let addr = response.records() 36 | //! .filter_map(self::to_ip_addr) 37 | //! .next(); 38 | //! 39 | //! if let Some(addr) = addr { 40 | //! println!("found cast device at {}", addr); 41 | //! } else { 42 | //! println!("cast device does not advertise address"); 43 | //! } 44 | //! } 45 | //! 46 | //! Ok(()) 47 | //! } 48 | //! 49 | //! fn to_ip_addr(record: &Record) -> Option { 50 | //! match record.kind { 51 | //! RecordKind::A(addr) => Some(addr.into()), 52 | //! RecordKind::AAAA(addr) => Some(addr.into()), 53 | //! _ => None, 54 | //! } 55 | //! } 56 | //! ``` 57 | 58 | #![recursion_limit = "1024"] 59 | 60 | pub use self::errors::Error; 61 | pub use self::response::{Record, RecordKind, Response}; 62 | 63 | pub mod discover; 64 | pub mod resolve; 65 | 66 | mod errors; 67 | mod mdns; 68 | mod response; 69 | 70 | pub use self::mdns::mDNSListener; 71 | -------------------------------------------------------------------------------- /src/mdns.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Response}; 2 | 3 | use std::{io, net::Ipv4Addr}; 4 | 5 | use async_stream::try_stream; 6 | use futures_core::Stream; 7 | use std::sync::Arc; 8 | use async_std::net::UdpSocket; 9 | 10 | #[cfg(not(target_os = "windows"))] 11 | use net2::unix::UnixUdpBuilderExt; 12 | use std::net::SocketAddr; 13 | 14 | /// The IP address for the mDNS multicast socket. 15 | const MULTICAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251); 16 | const MULTICAST_PORT: u16 = 5353; 17 | 18 | pub fn mdns_interface( 19 | service_name: String, 20 | interface_addr: Ipv4Addr, 21 | ) -> Result<(mDNSListener, mDNSSender), Error> { 22 | let socket = create_socket()?; 23 | 24 | socket.set_multicast_loop_v4(false)?; 25 | socket.join_multicast_v4(&MULTICAST_ADDR, &interface_addr)?; 26 | 27 | let socket = Arc::new(UdpSocket::from(socket)); 28 | 29 | let recv_buffer = vec![0; 4096]; 30 | 31 | Ok(( 32 | mDNSListener { recv: socket.clone(), recv_buffer }, 33 | mDNSSender { service_name, send: socket }, 34 | )) 35 | } 36 | 37 | const ADDR_ANY: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); 38 | 39 | #[cfg(not(target_os = "windows"))] 40 | fn create_socket() -> io::Result { 41 | net2::UdpBuilder::new_v4()? 42 | .reuse_address(true)? 43 | .reuse_port(true)? 44 | .bind((ADDR_ANY, MULTICAST_PORT)) 45 | } 46 | 47 | #[cfg(target_os = "windows")] 48 | fn create_socket() -> io::Result { 49 | net2::UdpBuilder::new_v4()? 50 | .reuse_address(true)? 51 | .bind((ADDR_ANY, MULTICAST_PORT)) 52 | } 53 | 54 | /// An mDNS sender on a specific interface. 55 | #[derive(Debug, Clone)] 56 | #[allow(non_camel_case_types)] 57 | pub struct mDNSSender<> { 58 | service_name: String, 59 | send: Arc, 60 | } 61 | 62 | impl mDNSSender { 63 | /// Send multicasted DNS queries. 64 | pub async fn send_request(&mut self) -> Result<(), Error> { 65 | let mut builder = dns_parser::Builder::new_query(0, false); 66 | let prefer_unicast = false; 67 | builder.add_question( 68 | &self.service_name, 69 | prefer_unicast, 70 | dns_parser::QueryType::PTR, 71 | dns_parser::QueryClass::IN, 72 | ); 73 | let packet_data = builder.build().unwrap(); 74 | 75 | let addr = SocketAddr::new(MULTICAST_ADDR.into(), MULTICAST_PORT); 76 | 77 | self.send.send_to(&packet_data, addr).await?; 78 | Ok(()) 79 | } 80 | } 81 | 82 | /// An mDNS listener on a specific interface. 83 | #[derive(Debug, Clone)] 84 | #[allow(non_camel_case_types)] 85 | pub struct mDNSListener { 86 | recv: Arc, 87 | recv_buffer: Vec, 88 | } 89 | 90 | impl mDNSListener { 91 | pub fn listen(mut self) -> impl Stream> { 92 | try_stream! { 93 | loop { 94 | let (count, _) = self.recv.recv_from(&mut self.recv_buffer).await?; 95 | 96 | if count > 0 { 97 | match dns_parser::Packet::parse(&self.recv_buffer[..count]) { 98 | Ok(raw_packet) => yield Response::from_packet(&raw_packet), 99 | Err(e) => log::warn!("{}, {:?}", e, &self.recv_buffer[..count]), 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/resolve.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for resolving a devices on the LAN. 2 | //! 3 | //! Examples 4 | //! 5 | //! ```rust,no_run 6 | //! use mdns::Error; 7 | //! use std::time::Duration; 8 | //! 9 | //! const SERVICE_NAME: &'static str = "_googlecast._tcp.local"; 10 | //! const HOST: &'static str = "mycast._googlecast._tcp.local"; 11 | //! 12 | //! #[async_std::main] 13 | //! async fn main() -> Result<(), Error> { 14 | //! if let Some(response) = mdns::resolve::one(SERVICE_NAME, HOST, Duration::from_secs(15)).await? { 15 | //! println!("{:?}", response); 16 | //! } 17 | //! 18 | //! Ok(()) 19 | //! } 20 | //! ``` 21 | 22 | use crate::{Error, Response}; 23 | use futures_util::{StreamExt, pin_mut, TryFutureExt}; 24 | use std::time::Duration; 25 | 26 | /// Resolve a single device by hostname 27 | pub async fn one( 28 | service_name: &str, 29 | host_name: S, 30 | timeout: Duration, 31 | ) -> Result, Error> 32 | where 33 | S: AsRef, 34 | { 35 | // by setting the query interval higher than the timeout we ensure we only make one query 36 | let stream = crate::discover::all(service_name, timeout * 2)?.listen(); 37 | pin_mut!(stream); 38 | 39 | let process = async { 40 | while let Some(Ok(response)) = stream.next().await { 41 | match response.hostname() { 42 | Some(found_host) if found_host == host_name.as_ref() => return Some(response), 43 | _ => {} 44 | } 45 | } 46 | 47 | None 48 | }; 49 | 50 | async_std::future::timeout(timeout, process).map_err(|e| e.into()).await 51 | } 52 | 53 | /// Resolve multiple devices by hostname 54 | pub async fn multiple( 55 | service_name: &str, 56 | host_names: &[S], 57 | timeout: Duration, 58 | ) -> Result, Error> 59 | where 60 | S: AsRef, 61 | { 62 | // by setting the query interval higher than the timeout we ensure we only make one query 63 | let stream = crate::discover::all(service_name, timeout * 2)?.listen(); 64 | pin_mut!(stream); 65 | 66 | let mut found = Vec::new(); 67 | 68 | let process = async { 69 | while let Some(Ok(response)) = stream.next().await { 70 | match response.hostname() { 71 | Some(found_host) if host_names.iter().any(|s| s.as_ref() == found_host) => { 72 | found.push(response); 73 | 74 | if found.len() == host_names.len() { 75 | return; 76 | } 77 | } 78 | _ => {} 79 | } 80 | } 81 | }; 82 | 83 | match async_std::future::timeout(timeout, process).await { 84 | Ok(()) => Ok(found), 85 | Err(e) => Err(e.into()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | use std::net; 2 | use std::net::{IpAddr, SocketAddr}; 3 | 4 | /// A DNS response. 5 | #[derive(Clone, Debug, PartialEq, Eq)] 6 | pub struct Response { 7 | pub answers: Vec, 8 | pub nameservers: Vec, 9 | pub additional: Vec, 10 | } 11 | 12 | /// Any type of DNS record. 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct Record { 15 | pub name: String, 16 | pub class: dns_parser::Class, 17 | pub ttl: u32, 18 | pub kind: RecordKind, 19 | } 20 | 21 | /// A specific DNS record variant. 22 | #[derive(Clone, Debug, PartialEq, Eq)] 23 | pub enum RecordKind { 24 | A(net::Ipv4Addr), 25 | AAAA(net::Ipv6Addr), 26 | CNAME(String), 27 | MX { 28 | preference: u16, 29 | exchange: String, 30 | }, 31 | NS(String), 32 | SRV { 33 | priority: u16, 34 | weight: u16, 35 | port: u16, 36 | target: String, 37 | }, 38 | TXT(Vec), 39 | PTR(String), 40 | /// A record kind that hasn't been implemented by this library yet. 41 | Unimplemented(Vec), 42 | } 43 | 44 | impl Response { 45 | pub fn from_packet(packet: &dns_parser::Packet) -> Self { 46 | Response { 47 | answers: packet 48 | .answers 49 | .iter() 50 | .map(Record::from_resource_record) 51 | .collect(), 52 | nameservers: packet 53 | .nameservers 54 | .iter() 55 | .map(Record::from_resource_record) 56 | .collect(), 57 | additional: packet 58 | .additional 59 | .iter() 60 | .map(Record::from_resource_record) 61 | .collect(), 62 | } 63 | } 64 | 65 | pub fn records(&self) -> impl Iterator { 66 | self.answers 67 | .iter() 68 | .chain(self.nameservers.iter()) 69 | .chain(self.additional.iter()) 70 | } 71 | 72 | pub fn is_empty(&self) -> bool { 73 | self.answers.is_empty() && self.nameservers.is_empty() && self.additional.is_empty() 74 | } 75 | 76 | pub fn ip_addr(&self) -> Option { 77 | self.records().find_map(|record| match record.kind { 78 | RecordKind::A(addr) => Some(addr.into()), 79 | RecordKind::AAAA(addr) => Some(addr.into()), 80 | _ => None, 81 | }) 82 | } 83 | 84 | pub fn hostname(&self) -> Option<&str> { 85 | self.records().find_map(|record| match record.kind { 86 | RecordKind::PTR(ref host) => Some(host.as_str()), 87 | _ => None, 88 | }) 89 | } 90 | 91 | pub fn port(&self) -> Option { 92 | self.records().find_map(|record| match record.kind { 93 | RecordKind::SRV { port, .. } => Some(port), 94 | _ => None, 95 | }) 96 | } 97 | 98 | pub fn socket_address(&self) -> Option { 99 | Some((self.ip_addr()?, self.port()?).into()) 100 | } 101 | 102 | pub fn txt_records(&self) -> impl Iterator { 103 | self.records() 104 | .filter_map(|record| match record.kind { 105 | RecordKind::TXT(ref txt) => Some(txt), 106 | _ => None, 107 | }) 108 | .flat_map(|txt| txt.iter()) 109 | .map(|txt| txt.as_str()) 110 | } 111 | } 112 | 113 | impl Record { 114 | fn from_resource_record(rr: &dns_parser::ResourceRecord) -> Self { 115 | Record { 116 | name: rr.name.to_string(), 117 | class: rr.cls, 118 | ttl: rr.ttl, 119 | kind: RecordKind::from_rr_data(&rr.data), 120 | } 121 | } 122 | } 123 | 124 | impl RecordKind { 125 | fn from_rr_data(data: &dns_parser::RData) -> Self { 126 | use dns_parser::RData; 127 | 128 | match *data { 129 | RData::A(dns_parser::rdata::a::Record(addr)) => RecordKind::A(addr), 130 | RData::AAAA(dns_parser::rdata::aaaa::Record(addr)) => RecordKind::AAAA(addr), 131 | RData::CNAME(ref name) => RecordKind::CNAME(name.to_string()), 132 | RData::MX(dns_parser::rdata::mx::Record { 133 | preference, 134 | ref exchange, 135 | }) => RecordKind::MX { 136 | preference, 137 | exchange: exchange.to_string(), 138 | }, 139 | RData::NS(ref name) => RecordKind::NS(name.to_string()), 140 | RData::PTR(ref name) => RecordKind::PTR(name.to_string()), 141 | RData::SRV(dns_parser::rdata::srv::Record { 142 | priority, 143 | weight, 144 | port, 145 | ref target, 146 | }) => RecordKind::SRV { 147 | priority, 148 | weight, 149 | port, 150 | target: target.to_string(), 151 | }, 152 | RData::TXT(ref txt) => RecordKind::TXT( 153 | txt.iter() 154 | .map(|bytes| String::from_utf8_lossy(bytes).into_owned()) 155 | .collect(), 156 | ), 157 | RData::SOA(..) => { 158 | RecordKind::Unimplemented("SOA record handling is not implemented".into()) 159 | } 160 | RData::Unknown(data) => RecordKind::Unimplemented(data.to_owned()), 161 | } 162 | } 163 | } 164 | --------------------------------------------------------------------------------