1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-04 10:19:27 +02:00

Discovery: Refactor and add Avahi DBus backend (#1347)

* discovery: use opaque error type for DnsSdError

This helps to decouple discovery and core by not leaking implementation
details of the zeroconf backend into Error conversion impls in core.

* discovery: map all MDNS/DNS-SD errors to DiscoveryError::DnsSdError

previously, libmdns errors would use a generic conversion
from std::io::Error to core::Error

* discovery: use an opaque type for the handle to the DNS-SD service

* discovery: make features additive

i.e. add with-libmdns instead of using not(with-dns-sd).

The logic is such that enabling with-dns-sd in addition to the default
with-libmdns will still end up using dns-sd, as before.
If only with-libmdns is enabled, that will be the default.
If none of the features is enabled, attempting to build a `Discovery`
will yield an error.

* discovery: add --zeroconf-backend CLI flag

* discovery: Add minimal Avahi zeroconf backend

* bump MSRV to 1.75

required by zbus >= 4

* discovery: ensure that server and dns-sd backend shutdown gracefully

Previously, on drop the the shutdown_tx/close_tx, it wasn't guaranteed
the corresponding tasks would continue to be polled until they actually
completed their shutdown.

Since dns_sd::Service is not Send and non-async, and because libmdns is
non-async, put them on their own threads.

* discovery: use a shared channel for server and zeroconf status messages

* discovery: add Avahi reconnection logic

This deals gracefully with the case where the Avahi daemon is restarted
or not running initially.

* discovery: allow running when compiled without zeroconf backend...

...but exit with an error if there's no way to authenticate

* better error messages for invalid options with no short flag
This commit is contained in:
Benedikt 2024-10-26 16:45:02 +02:00 committed by GitHub
parent d2324ddd1b
commit 94d174c33d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1156 additions and 129 deletions

View file

@ -1,18 +1,14 @@
use std::{
borrow::Cow,
collections::BTreeMap,
convert::Infallible,
net::{Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener},
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll},
};
use aes::cipher::{KeyIvInit, StreamCipher};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::engine::Engine as _;
use bytes::Bytes;
use futures_core::Stream;
use futures_util::{FutureExt, TryFutureExt};
use hmac::{Hmac, Mac};
use http_body_util::{BodyExt, Full};
@ -24,7 +20,7 @@ use serde_json::json;
use sha1::{Digest, Sha1};
use tokio::sync::{mpsc, oneshot};
use super::DiscoveryError;
use super::{DiscoveryError, DiscoveryEvent};
use crate::{
core::config::DeviceType,
@ -47,21 +43,17 @@ struct RequestHandler {
config: Config,
username: Mutex<Option<String>>,
keys: DhLocalKeys,
tx: mpsc::UnboundedSender<Credentials>,
event_tx: mpsc::UnboundedSender<DiscoveryEvent>,
}
impl RequestHandler {
fn new(config: Config) -> (Self, mpsc::UnboundedReceiver<Credentials>) {
let (tx, rx) = mpsc::unbounded_channel();
let discovery = Self {
fn new(config: Config, event_tx: mpsc::UnboundedSender<DiscoveryEvent>) -> Self {
Self {
config,
username: Mutex::new(None),
keys: DhLocalKeys::random(&mut rand::thread_rng()),
tx,
};
(discovery, rx)
event_tx,
}
}
fn active_user(&self) -> String {
@ -202,7 +194,8 @@ impl RequestHandler {
{
let maybe_username = self.username.lock();
self.tx.send(credentials)?;
self.event_tx
.send(DiscoveryEvent::Credentials(credentials))?;
if let Ok(mut username_field) = maybe_username {
*username_field = Some(String::from(username));
} else {
@ -258,14 +251,22 @@ impl RequestHandler {
}
}
pub(crate) enum DiscoveryServerCmd {
Shutdown,
}
pub struct DiscoveryServer {
cred_rx: mpsc::UnboundedReceiver<Credentials>,
_close_tx: oneshot::Sender<Infallible>,
close_tx: oneshot::Sender<DiscoveryServerCmd>,
task_handle: tokio::task::JoinHandle<()>,
}
impl DiscoveryServer {
pub fn new(config: Config, port: &mut u16) -> Result<Self, Error> {
let (discovery, cred_rx) = RequestHandler::new(config);
pub fn new(
config: Config,
port: &mut u16,
event_tx: mpsc::UnboundedSender<DiscoveryEvent>,
) -> Result<Self, Error> {
let discovery = RequestHandler::new(config, event_tx);
let address = if cfg!(windows) {
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), *port)
} else {
@ -297,7 +298,7 @@ impl DiscoveryServer {
}
}
tokio::spawn(async move {
let task_handle = tokio::spawn(async move {
let discovery = Arc::new(discovery);
let server = hyper::server::conn::http1::Builder::new();
@ -326,27 +327,32 @@ impl DiscoveryServer {
});
}
_ = &mut close_rx => {
debug!("Shutting down discovery server");
break;
}
}
}
graceful.shutdown().await;
debug!("Discovery server stopped");
});
Ok(Self {
cred_rx,
_close_tx: close_tx,
close_tx,
task_handle,
})
}
}
impl Stream for DiscoveryServer {
type Item = Credentials;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Credentials>> {
self.cred_rx.poll_recv(cx)
pub async fn shutdown(self) {
let Self {
close_tx,
task_handle,
..
} = self;
log::debug!("Shutting down discovery server");
if close_tx.send(DiscoveryServerCmd::Shutdown).is_err() {
log::warn!("Discovery server unexpectedly disappeared");
} else {
let _ = task_handle.await;
log::debug!("Discovery server stopped");
}
}
}