async_dnssd/service/
resolve_host.rs

1use futures_util::{
2	StreamExt,
3	TryStreamExt,
4};
5use std::{
6	fmt,
7	io,
8	net::{
9		IpAddr,
10		Ipv4Addr,
11		Ipv6Addr,
12		SocketAddr,
13		SocketAddrV4,
14		SocketAddrV6,
15	},
16	pin::Pin,
17	task::{
18		Context,
19		Poll,
20	},
21};
22
23use crate::{
24	dns_consts::{
25		Class,
26		Type,
27	},
28	ffi,
29	interface::Interface,
30	service::{
31		query_record_extended,
32		QueryRecordData,
33		QueryRecordFlags,
34		QueryRecordResult,
35	},
36};
37
38fn decode_a(a: QueryRecordResult, port: u16) -> Option<ResolveHostResult> {
39	if a.rr_class == Class::IN && a.rr_type == Type::A && a.rdata.len() == 4 {
40		let mut octets = [0u8; 4];
41		octets.clone_from_slice(&a.rdata);
42		let ip = IpAddr::V4(Ipv4Addr::from(octets));
43		let addr = ScopedSocketAddr::new(ip, port, a.interface.scope_id());
44		Some(ResolveHostResult {
45			flags: ResolvedHostFlags::from_bits_truncate(a.flags.bits()),
46			address: addr,
47		})
48	} else {
49		println!("Invalid A response: {:?}", a);
50		None
51	}
52}
53
54fn decode_aaaa(a: QueryRecordResult, port: u16) -> Option<ResolveHostResult> {
55	if a.rr_class == Class::IN && a.rr_type == Type::AAAA && a.rdata.len() == 16 {
56		let mut octets = [0u8; 16];
57		octets.clone_from_slice(&a.rdata);
58		let ip = IpAddr::V6(Ipv6Addr::from(octets));
59		let addr = ScopedSocketAddr::new(ip, port, a.interface.scope_id());
60		Some(ResolveHostResult {
61			flags: ResolvedHostFlags::from_bits_truncate(a.flags.bits()),
62			address: addr,
63		})
64	} else {
65		println!("Invalid AAAA response: {:?}", a);
66		None
67	}
68}
69
70bitflags::bitflags! {
71	/// Flags for [`ResolveHostResult`](struct.ResolveHostResult.html)
72	///
73	/// Doesn't include `MORE_COMING` as there are two underlying streams.
74	#[derive(Default)]
75	pub struct ResolvedHostFlags: ffi::DNSServiceFlags {
76		/// Indicates the result is new.  If not set indicates the result
77		/// was removed.
78		///
79		/// See [`kDNSServiceFlagsAdd`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsadd).
80		const ADD = ffi::FLAGS_ADD;
81	}
82}
83
84/// Optional data when querying for a record; either use its default
85/// value or customize it like:
86///
87/// ```
88/// # use async_dnssd::ResolveHostData;
89/// # use async_dnssd::QueryRecordFlags;
90/// ResolveHostData {
91///     flags: QueryRecordFlags::LONG_LIVED_QUERY,
92///     ..Default::default()
93/// };
94/// ```
95#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
96pub struct ResolveHostData {
97	/// flags for query
98	pub flags: QueryRecordFlags,
99	/// interface to query records on
100	pub interface: Interface,
101	#[doc(hidden)]
102	pub _non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
103}
104
105/// Pending resolve
106#[must_use = "streams do nothing unless polled"]
107pub struct ResolveHost {
108	inner: Pin<
109		Box<dyn futures_core::Stream<Item = io::Result<ResolveHostResult>> + 'static + Send + Sync>,
110	>,
111}
112
113impl futures_core::Stream for ResolveHost {
114	type Item = io::Result<ResolveHostResult>;
115
116	fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
117		self.inner.poll_next_unpin(cx)
118	}
119}
120
121/// Resolve host result
122///
123/// See [`DNSServiceResolveReply`](https://developer.apple.com/documentation/dnssd/dnsserviceresolvereply).
124#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
125pub struct ResolveHostResult {
126	/// flags
127	pub flags: ResolvedHostFlags,
128	/// address
129	pub address: ScopedSocketAddr,
130}
131
132/// IP address with port and "scope id" (even for IPv4)
133///
134/// When converting to `SocketAddr` the "scope id" is lost for IPv4; when converting to
135/// `SocketAddrV6` it uses `to_ipv6_mapped()` for IPv4 addresses.
136#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137pub enum ScopedSocketAddr {
138	/// IPv4 target
139	V4 {
140		/// IP address
141		address: Ipv4Addr,
142		/// Port
143		port: u16,
144		/// Scope id (interface index; 0 for any)
145		scope_id: u32,
146	},
147	/// IPv6 target
148	V6 {
149		/// IP address
150		address: Ipv6Addr,
151		/// Port
152		port: u16,
153		/// Scope id (interface index; 0 for any)
154		scope_id: u32,
155	},
156}
157
158impl ScopedSocketAddr {
159	/// Create new `ScopedSocketAddr`
160	pub fn new(address: IpAddr, port: u16, scope_id: u32) -> Self {
161		match address {
162			IpAddr::V4(address) => Self::V4 {
163				address,
164				port,
165				scope_id,
166			},
167			IpAddr::V6(address) => Self::V6 {
168				address,
169				port,
170				scope_id,
171			},
172		}
173	}
174}
175
176impl From<ScopedSocketAddr> for SocketAddr {
177	fn from(scoped_addr: ScopedSocketAddr) -> Self {
178		match scoped_addr {
179			ScopedSocketAddr::V4 { address, port, .. } => {
180				// doesn't use scope_id
181				Self::V4(SocketAddrV4::new(address, port))
182			},
183			ScopedSocketAddr::V6 {
184				address,
185				port,
186				scope_id,
187			} => Self::V6(SocketAddrV6::new(address, port, 0, scope_id)),
188		}
189	}
190}
191
192impl From<ScopedSocketAddr> for SocketAddrV6 {
193	fn from(scoped_addr: ScopedSocketAddr) -> Self {
194		match scoped_addr {
195			ScopedSocketAddr::V4 {
196				address,
197				port,
198				scope_id,
199			} => Self::new(address.to_ipv6_mapped(), port, 0, scope_id),
200			ScopedSocketAddr::V6 {
201				address,
202				port,
203				scope_id,
204			} => Self::new(address, port, 0, scope_id),
205		}
206	}
207}
208
209impl fmt::Display for ScopedSocketAddr {
210	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211		match self {
212			Self::V4 {
213				address,
214				port,
215				scope_id: 0,
216			} => write!(f, "{}:{}", address, port),
217			Self::V4 {
218				address,
219				port,
220				scope_id,
221			} => write!(f, "[{}%{}]:{}", address, scope_id, port),
222			Self::V6 {
223				address,
224				port,
225				scope_id: 0,
226			} => write!(f, "[{}]:{}", address, port),
227			Self::V6 {
228				address,
229				port,
230				scope_id,
231			} => write!(f, "[{}%{}]:{}", address, scope_id, port),
232		}
233	}
234}
235
236impl fmt::Debug for ScopedSocketAddr {
237	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238		fmt::Display::fmt(self, f)
239	}
240}
241
242/// Resolves hostname (with passed port) to stream of `ScopedSocketAddr`.
243///
244/// Uses
245/// [`DNSServiceQueryRecord`](https://developer.apple.com/documentation/dnssd/1804747-dnsservicequeryrecord)
246/// to query for `A` and `AAAA` records (in the `IN` class).
247#[doc(alias = "DNSServiceQueryRecord")]
248pub fn resolve_host_extended(host: &str, port: u16, data: ResolveHostData) -> ResolveHost {
249	let qrdata = QueryRecordData {
250		flags: data.flags,
251		interface: data.interface,
252		rr_class: Class::IN,
253		..Default::default()
254	};
255
256	let inner_v6 = query_record_extended(host, Type::AAAA, qrdata)
257		.try_filter_map(move |addr| async move { Ok(decode_aaaa(addr, port)) });
258	let inner_v4 = query_record_extended(host, Type::A, qrdata)
259		.try_filter_map(move |addr| async move { Ok(decode_a(addr, port)) });
260	let inner = Box::pin(futures_util::stream::select(inner_v6, inner_v4));
261
262	ResolveHost { inner }
263}