cast: don't drop the io loop on a single bad message, and don't let a scopeless link-local IPv6 clobber a working host
The receiver-status io loop treated any read error as a dead connection, so one malformed/unexpected message (e.g. a MEDIA_STATUS missing a field the struct requires) tore down the whole control channel for the rest of the session even though the RTP stream was fine. Only end the loop on Io/Tls/Dns errors now; log and keep going on Serialization/Parsing/etc. mDNS resolves a single physical Cast device once per local address it has, so the same device id can show up with a private IPv4 host and again with a link-local IPv6 host. A bare fe80:: address has no zone id attached, so connecting to it fails outright -- don't let it replace an already-usable host in the device map just because it resolved more recently.
This commit is contained in:
parent
696e3f540f
commit
7fb1934d52
2 changed files with 32 additions and 3 deletions
|
|
@ -326,10 +326,20 @@ fn run_io_loop(
|
||||||
// doc comment for why that's fine for this project's usage.
|
// doc comment for why that's fine for this project's usage.
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
// Only end the loop on an error that means the connection
|
||||||
tracing::debug!(error = %e, "cast session io loop ending: receive error");
|
// itself is gone -- a single malformed/unexpected message (e.g.
|
||||||
|
// a MEDIA_STATUS missing a field this crate's struct treats as
|
||||||
|
// required) is a `Serialization`/`Parsing` error, not a dead
|
||||||
|
// socket, and used to take the whole receiver-status/control
|
||||||
|
// channel down with it for the rest of the session even though
|
||||||
|
// the RTP stream itself was unaffected.
|
||||||
|
Err(e @ (rust_cast::errors::Error::Io(_) | rust_cast::errors::Error::Tls(_) | rust_cast::errors::Error::Dns(_))) => {
|
||||||
|
tracing::debug!(error = %e, "cast session io loop ending: connection error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(error = %e, "cast session: ignoring unparseable/unexpected message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,12 @@ impl ActiveSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `host` strings come straight from mDNS resolution, so this just checks
|
||||||
|
/// the address, not whether a zone id is attached (mDNS never gives us one).
|
||||||
|
fn is_link_local_v6(host: &str) -> bool {
|
||||||
|
matches!(host.parse::<std::net::IpAddr>(), Ok(std::net::IpAddr::V6(v6)) if (v6.segments()[0] & 0xffc0) == 0xfe80)
|
||||||
|
}
|
||||||
|
|
||||||
struct Daemon {
|
struct Daemon {
|
||||||
cast_devices: HashMap<String, CastDevice>,
|
cast_devices: HashMap<String, CastDevice>,
|
||||||
/// Keyed by `DlnaDevice::url`, the closest thing DLNA has to a stable
|
/// Keyed by `DlnaDevice::url`, the closest thing DLNA has to a stable
|
||||||
|
|
@ -128,7 +134,20 @@ impl Daemon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DaemonCommand::CastDeviceFound(device) => {
|
DaemonCommand::CastDeviceFound(device) => {
|
||||||
|
// mDNS resolves one physical device on every local address
|
||||||
|
// it has -- typically a private IPv4 and a link-local IPv6
|
||||||
|
// -- as separate events carrying the same id. A bare
|
||||||
|
// `fe80::` address has no interface scope attached, so
|
||||||
|
// `TcpStream::connect`ing to it fails outright; don't let
|
||||||
|
// one clobber an already-usable host just because it
|
||||||
|
// happened to resolve more recently.
|
||||||
|
let should_replace = match self.cast_devices.get(&device.id) {
|
||||||
|
Some(existing) if is_link_local_v6(&device.host) && !is_link_local_v6(&existing.host) => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
if should_replace {
|
||||||
self.cast_devices.insert(device.id.clone(), device);
|
self.cast_devices.insert(device.id.clone(), device);
|
||||||
|
}
|
||||||
self.broadcast_devices();
|
self.broadcast_devices();
|
||||||
}
|
}
|
||||||
DaemonCommand::CastDeviceLost(id) => {
|
DaemonCommand::CastDeviceLost(id) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue