1use std::future::Future;
4
5use agnostic_net::Net;
6use async_channel::Sender;
7use hick_udp::{
8 MulticastOptionsV4, MulticastOptionsV6, try_bind_v4, try_bind_v6, try_join_v4, try_join_v6,
9};
10use mdns_proto::{QuerySpec, ServiceSpec};
11
12use hick_trace::*;
13
14use crate::{
15 command::{Command, QueryStarted, ServiceRegistered},
16 driver::{self, BoundSockets},
17 error::{RegisterError, ServerError, StartQueryError},
18 options::ServerOptions,
19 query::Query,
20 service::Service,
21};
22
23#[derive(Clone)]
29pub struct Endpoint {
30 cmd: Sender<Command>,
31 #[cfg(feature = "stats")]
34 stats: std::sync::Arc<stats::Stats>,
35}
36
37impl Endpoint {
38 pub async fn server<N: Net>(opts: ServerOptions) -> Result<Self, ServerError> {
41 if !opts.ipv4() && !opts.ipv6() {
42 return Err(ServerError::NoFamilyEnabled);
43 }
44
45 let interface_index = match opts.interface_index() {
46 Some(i) => i,
47 None => pick_default_interface_index(opts.ipv4(), opts.ipv6()).ok_or_else(|| {
48 ServerError::Io(std::io::Error::new(
49 std::io::ErrorKind::NotFound,
50 "no multicast-capable interface found",
51 ))
52 })?,
53 };
54
55 let iface_has_v4 = match getifs::interface_by_index(interface_index) {
62 Ok(Some(i)) => matches!(i.ipv4_addrs(), Ok(ref a) if !a.is_empty()),
63 _ => false,
64 };
65 let iface_has_v6 = match getifs::interface_by_index(interface_index) {
66 Ok(Some(i)) => matches!(i.ipv6_addrs(), Ok(ref a) if !a.is_empty()),
67 _ => false,
68 };
69 let bind_v4 = opts.ipv4() && iface_has_v4;
70 let bind_v6 = opts.ipv6() && iface_has_v6;
71 if !bind_v4 && !bind_v6 {
72 return Err(ServerError::Io(std::io::Error::new(
73 std::io::ErrorKind::AddrNotAvailable,
74 "interface has no address in any requested family",
75 )));
76 }
77
78 let v4 = if bind_v4 {
79 match try_bind_v4(MulticastOptionsV4::new(interface_index)) {
80 Ok(std_sock) => {
81 debug!(interface_index, "bound v4 mDNS socket");
82 match try_join_v4(&std_sock, interface_index) {
83 Ok(()) => {
84 debug!(interface_index, "joined v4 mDNS multicast group");
85 }
86 Err(e) => {
87 warn!(error = %e, interface_index, "failed to join v4 mDNS multicast group");
88 return Err(map_join_to_bind_v4(e));
89 }
90 }
91 std_sock.set_nonblocking(true)?;
92 let async_sock = N::UdpSocket::try_from(std_sock).map_err(ServerError::WrapSocket)?;
93 Some(async_sock)
94 }
95 Err(e) => {
96 warn!(error = %e, interface_index, "failed to bind v4 mDNS socket");
97 return Err(ServerError::BindV4(e));
98 }
99 }
100 } else {
101 None
102 };
103
104 let v6 = if bind_v6 {
105 match try_bind_v6(MulticastOptionsV6::new(interface_index)) {
106 Ok(std_sock) => {
107 debug!(interface_index, "bound v6 mDNS socket");
108 match try_join_v6(&std_sock, interface_index) {
109 Ok(()) => {
110 debug!(interface_index, "joined v6 mDNS multicast group");
111 }
112 Err(e) => {
113 warn!(error = %e, interface_index, "failed to join v6 mDNS multicast group");
114 return Err(map_join_to_bind_v6(e));
115 }
116 }
117 std_sock.set_nonblocking(true)?;
118 let async_sock = N::UdpSocket::try_from(std_sock).map_err(ServerError::WrapSocket)?;
119 Some(async_sock)
120 }
121 Err(e) => {
122 warn!(error = %e, interface_index, "failed to bind v6 mDNS socket");
123 return Err(ServerError::BindV6(e));
124 }
125 }
126 } else {
127 None
128 };
129
130 let (cmd_tx, cmd_rx) = async_channel::unbounded::<Command>();
136 let sockets = BoundSockets {
137 v4,
138 v6,
139 interface_index,
140 };
141 #[cfg(feature = "stats")]
142 let mut stats_slot: Option<std::sync::Arc<stats::Stats>> = None;
143 driver::spawn::<N>(
144 opts,
145 sockets,
146 cmd_rx,
147 #[cfg(feature = "stats")]
148 &mut stats_slot,
149 );
150
151 Ok(Self {
152 cmd: cmd_tx,
153 #[cfg(feature = "stats")]
154 stats: stats_slot.expect("spawn always populates stats_slot when stats feature is enabled"),
155 })
156 }
157
158 #[cfg(feature = "stats")]
167 #[cfg_attr(docsrs, doc(cfg(feature = "stats")))]
168 pub fn stats(&self) -> stats::StatsSnapshot {
169 self.stats.snapshot()
170 }
171
172 pub(crate) fn spawn_lookup<F>(&self, fut: F) -> Result<(), StartQueryError>
181 where
182 F: Future<Output = ()> + Send + 'static,
183 {
184 self
185 .cmd
186 .try_send(Command::SpawnLookup {
187 task: Box::pin(fut),
188 })
189 .map_err(|_| StartQueryError::DriverGone)
190 }
191
192 pub async fn register_service(&self, spec: ServiceSpec) -> Result<Service, RegisterError> {
196 let (reply_tx, reply_rx) = futures::channel::oneshot::channel();
197 self
198 .cmd
199 .send(Command::RegisterService {
200 spec,
201 reply: reply_tx,
202 })
203 .await
204 .map_err(|_| RegisterError::DriverGone)?;
205 let ServiceRegistered {
206 handle,
207 mailbox,
208 doorbell,
209 } = reply_rx.await.map_err(|_| RegisterError::DriverGone)??;
210 Ok(Service::new(handle, mailbox, doorbell, self.cmd.clone()))
211 }
212
213 pub async fn start_query(&self, spec: QuerySpec) -> Result<Query, StartQueryError> {
216 let (reply_tx, reply_rx) = futures::channel::oneshot::channel();
217 self
218 .cmd
219 .send(Command::StartQuery {
220 spec,
221 reply: reply_tx,
222 })
223 .await
224 .map_err(|_| StartQueryError::DriverGone)?;
225 let QueryStarted {
226 handle,
227 mailbox,
228 doorbell,
229 } = reply_rx.await.map_err(|_| StartQueryError::DriverGone)??;
230 Ok(Query::new(handle, mailbox, doorbell, self.cmd.clone()))
231 }
232}
233
234fn map_join_to_bind_v4(e: hick_udp::JoinError) -> ServerError {
235 match e {
236 hick_udp::JoinError::Io(io) => ServerError::BindV4(hick_udp::BindError::Io(io)),
237 hick_udp::JoinError::InterfaceNotFound(d) => {
238 ServerError::BindV4(hick_udp::BindError::InterfaceNotFound(d))
239 }
240 _ => ServerError::Io(std::io::Error::other("unknown JoinError variant")),
241 }
242}
243
244fn map_join_to_bind_v6(e: hick_udp::JoinError) -> ServerError {
245 match e {
246 hick_udp::JoinError::Io(io) => ServerError::BindV6(hick_udp::BindError::Io(io)),
247 hick_udp::JoinError::InterfaceNotFound(d) => {
248 ServerError::BindV6(hick_udp::BindError::InterfaceNotFound(d))
249 }
250 _ => ServerError::Io(std::io::Error::other("unknown JoinError variant")),
251 }
252}
253
254fn pick_default_interface_index(want_v4: bool, want_v6: bool) -> Option<u32> {
255 let ifs = getifs::interfaces().ok()?;
256 let has_v4 = |i: &getifs::Interface| matches!(i.ipv4_addrs(), Ok(ref v) if !v.is_empty());
257 let has_v6 = |i: &getifs::Interface| matches!(i.ipv6_addrs(), Ok(ref v) if !v.is_empty());
258 let multicast_up_non_loopback = |i: &getifs::Interface| -> bool {
259 let f = i.flags();
260 f.contains(getifs::Flags::UP)
261 && f.contains(getifs::Flags::MULTICAST)
262 && !f.contains(getifs::Flags::LOOPBACK)
263 && i.index() != 0
264 };
265 let loopback_up = |i: &getifs::Interface| -> bool {
266 i.flags().contains(getifs::Flags::LOOPBACK) && i.flags().contains(getifs::Flags::UP)
267 };
268 let strict =
274 |i: &&getifs::Interface| -> bool { (!want_v4 || has_v4(i)) && (!want_v6 || has_v6(i)) };
275 let loose = |i: &&getifs::Interface| -> bool { (want_v4 && has_v4(i)) || (want_v6 && has_v6(i)) };
276 let strict_non_loopback = ifs
277 .iter()
278 .find(|i| multicast_up_non_loopback(i) && strict(i));
279 let loose_non_loopback = ifs
280 .iter()
281 .find(|i| multicast_up_non_loopback(i) && loose(i));
282 let strict_loopback = ifs.iter().find(|i| loopback_up(i) && strict(i));
283 let loose_loopback = ifs.iter().find(|i| loopback_up(i) && loose(i));
284 strict_non_loopback
285 .or(loose_non_loopback)
286 .or(strict_loopback)
287 .or(loose_loopback)
288 .map(|i| i.index())
289}
290
291#[cfg(test)]
292mod tests;