blob: bb7af3d387656639a56b1ca484abafb3af01baf3 [file] [log] [blame]
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! DNS Service Discovery
#![cfg(feature = "mdns")]
use std::borrow::Cow;
use std::collections::HashMap;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::Future;
use proto::rr::rdata::TXT;
use proto::rr::{Name, RecordType};
use proto::xfer::DnsRequestOptions;
use proto::DnsHandle;
use crate::error::*;
use crate::lookup::{ReverseLookup, ReverseLookupIter, TxtLookup};
use crate::name_server::ConnectionProvider;
use crate::AsyncResolver;
/// An extension for the Resolver to perform DNS Service Discovery
pub trait DnsSdHandle {
/// List all services available
///
/// https://tools.ietf.org/html/rfc6763#section-4.1
///
/// For registered service types, see: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
fn list_services(&self, name: Name) -> ListServicesFuture;
/// Retrieve service information
///
/// https://tools.ietf.org/html/rfc6763#section-6
fn service_info(&self, name: Name) -> ServiceInfoFuture;
}
impl<C: DnsHandle, P: ConnectionProvider<Conn = C>> DnsSdHandle for AsyncResolver<C, P> {
fn list_services(&self, name: Name) -> ListServicesFuture {
let this = self.clone();
let ptr_future = async move {
let options = DnsRequestOptions {
expects_multiple_responses: true,
};
this.inner_lookup(name, RecordType::PTR, options).await
};
ListServicesFuture(Box::pin(ptr_future))
}
fn service_info(&self, name: Name) -> ServiceInfoFuture {
let this = self.clone();
let ptr_future = async move { this.txt_lookup(name).await };
ServiceInfoFuture(Box::pin(ptr_future))
}
}
/// A DNS Service Discovery future of Services discovered through the list operation
pub struct ListServicesFuture(
Pin<Box<dyn Future<Output = Result<ReverseLookup, ResolveError>> + Send + 'static>>,
);
impl Future for ListServicesFuture {
type Output = Result<ListServices, ResolveError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match self.0.as_mut().poll(cx) {
Poll::Ready(Ok(lookup)) => Poll::Ready(Ok(ListServices(lookup))),
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
}
}
}
/// The list of Services discovered
pub struct ListServices(ReverseLookup);
impl ListServices {
/// Returns an iterator over the list of returned names of services.
///
/// Each name can be queried for additional information. To lookup service entries see [`AsyncResolver::lookup_srv`]. To get parameters associated with the service, see `DnsSdFuture::service_info`.
pub fn iter(&self) -> ListServicesIter {
ListServicesIter(self.0.iter())
}
}
/// An iterator over the Lookup type
pub struct ListServicesIter<'i>(ReverseLookupIter<'i>);
impl<'i> Iterator for ListServicesIter<'i> {
type Item = &'i Name;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
/// A Future that resolves to the TXT information for a service
pub struct ServiceInfoFuture(
Pin<Box<dyn Future<Output = Result<TxtLookup, ResolveError>> + Send + 'static>>,
);
impl Future for ServiceInfoFuture {
type Output = Result<ServiceInfo, ResolveError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match self.0.as_mut().poll(cx) {
Poll::Ready(Ok(lookup)) => Poll::Ready(Ok(ServiceInfo(lookup))),
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
}
}
}
/// The list of Services discovered
pub struct ServiceInfo(TxtLookup);
impl ServiceInfo {
/// Returns this as a map, this allocates a new hashmap
///
/// This converts the DNS-SD TXT record into a map following the rules specified in https://tools.ietf.org/html/rfc6763#section-6.4
pub fn to_map<'s>(&'s self) -> HashMap<Cow<'s, str>, Option<Cow<'s, str>>> {
self.0
.iter()
.flat_map(TXT::iter)
.filter_map(|bytes| {
let mut split = bytes.split(|byte| *byte == b'=');
let key = split.next().map(String::from_utf8_lossy);
let value = split.next().map(String::from_utf8_lossy);
if let Some(key) = key {
Some((key, value))
} else {
None
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use std::str::FromStr;
use tokio::runtime::Runtime;
use crate::config::*;
use crate::TokioAsyncResolver;
use super::*;
#[test]
#[ignore]
fn test_list_services() {
let mut io_loop = Runtime::new().unwrap();
let resolver = TokioAsyncResolver::new(
ResolverConfig::default(),
ResolverOpts {
ip_strategy: LookupIpStrategy::Ipv6thenIpv4,
..ResolverOpts::default()
},
io_loop.handle().clone(),
);
let resolver = io_loop
.block_on(resolver)
.expect("failed to create resolver");
let response = io_loop
.block_on(resolver.list_services(Name::from_str("_http._tcp.local.").unwrap()))
.expect("failed to run lookup");
for name in response.iter() {
println!("service: {}", name);
let srvs = io_loop
.block_on(resolver.srv_lookup(name.clone()))
.expect("failed to lookup name");
for srv in srvs.iter() {
println!("service: {:#?}", srv);
let info = io_loop
.block_on(resolver.service_info(name.clone()))
.expect("info failed");
let info = info.to_map();
println!("info: {:#?}", info);
}
for ip in srvs.ip_iter() {
println!("ip: {}", ip);
}
}
}
}