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		QueryRecordData,
32		QueryRecordFlags,
33		QueryRecordResult,
34		query_record_extended,
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`]
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(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
117		let this = self.get_mut();
118		this.inner.poll_next_unpin(cx)
119	}
120}
121
122/// Resolve host result
123///
124/// See [`DNSServiceResolveReply`](https://developer.apple.com/documentation/dnssd/dnsserviceresolvereply).
125#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
126pub struct ResolveHostResult {
127	/// flags
128	pub flags: ResolvedHostFlags,
129	/// address
130	pub address: ScopedSocketAddr,
131}
132
133/// IP address with port and "scope id" (even for IPv4)
134///
135/// When converting to `SocketAddr` the "scope id" is lost for IPv4; when converting to
136/// `SocketAddrV6` it uses `to_ipv6_mapped()` for IPv4 addresses.
137#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138pub enum ScopedSocketAddr {
139	/// IPv4 target
140	V4 {
141		/// IP address
142		address: Ipv4Addr,
143		/// Port
144		port: u16,
145		/// Scope id (interface index; 0 for any)
146		scope_id: u32,
147	},
148	/// IPv6 target
149	V6 {
150		/// IP address
151		address: Ipv6Addr,
152		/// Port
153		port: u16,
154		/// Scope id (interface index; 0 for any)
155		scope_id: u32,
156	},
157}
158
159impl ScopedSocketAddr {
160	/// Create new `ScopedSocketAddr`
161	pub fn new(address: IpAddr, port: u16, scope_id: u32) -> Self {
162		match address {
163			IpAddr::V4(address) => Self::V4 {
164				address,
165				port,
166				scope_id,
167			},
168			IpAddr::V6(address) => Self::V6 {
169				address,
170				port,
171				scope_id,
172			},
173		}
174	}
175}
176
177impl From<ScopedSocketAddr> for SocketAddr {
178	fn from(scoped_addr: ScopedSocketAddr) -> Self {
179		match scoped_addr {
180			ScopedSocketAddr::V4 { address, port, .. } => {
181				// doesn't use scope_id
182				Self::V4(SocketAddrV4::new(address, port))
183			},
184			ScopedSocketAddr::V6 {
185				address,
186				port,
187				scope_id,
188			} => Self::V6(SocketAddrV6::new(address, port, 0, scope_id)),
189		}
190	}
191}
192
193impl From<ScopedSocketAddr> for SocketAddrV6 {
194	fn from(scoped_addr: ScopedSocketAddr) -> Self {
195		match scoped_addr {
196			ScopedSocketAddr::V4 {
197				address,
198				port,
199				scope_id,
200			} => Self::new(address.to_ipv6_mapped(), port, 0, scope_id),
201			ScopedSocketAddr::V6 {
202				address,
203				port,
204				scope_id,
205			} => Self::new(address, port, 0, scope_id),
206		}
207	}
208}
209
210impl fmt::Display for ScopedSocketAddr {
211	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212		match self {
213			Self::V4 {
214				address,
215				port,
216				scope_id: 0,
217			} => write!(f, "{}:{}", address, port),
218			Self::V4 {
219				address,
220				port,
221				scope_id,
222			} => write!(f, "[{}%{}]:{}", address, scope_id, port),
223			Self::V6 {
224				address,
225				port,
226				scope_id: 0,
227			} => write!(f, "[{}]:{}", address, port),
228			Self::V6 {
229				address,
230				port,
231				scope_id,
232			} => write!(f, "[{}%{}]:{}", address, scope_id, port),
233		}
234	}
235}
236
237impl fmt::Debug for ScopedSocketAddr {
238	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239		fmt::Display::fmt(self, f)
240	}
241}
242
243/// Resolves hostname (with passed port) to stream of `ScopedSocketAddr`.
244///
245/// Uses
246/// [`DNSServiceQueryRecord`](https://developer.apple.com/documentation/dnssd/1804747-dnsservicequeryrecord)
247/// to query for `A` and `AAAA` records (in the `IN` class).
248#[doc(alias = "DNSServiceQueryRecord")]
249pub fn resolve_host_extended(host: &str, port: u16, data: ResolveHostData) -> ResolveHost {
250	let qrdata = QueryRecordData {
251		flags: data.flags,
252		interface: data.interface,
253		rr_class: Class::IN,
254		..Default::default()
255	};
256
257	let inner_v6 = query_record_extended(host, Type::AAAA, qrdata)
258		.try_filter_map(move |addr| async move { Ok(decode_aaaa(addr, port)) });
259	let inner_v4 = query_record_extended(host, Type::A, qrdata)
260		.try_filter_map(move |addr| async move { Ok(decode_a(addr, port)) });
261	let inner = Box::pin(futures_util::stream::select(inner_v6, inner_v4));
262
263	ResolveHost { inner }
264}