Skip to content

fix(dlna): answer browsemetadata on containers and advertise a reachable address - #907

Merged
InstaZDLL merged 2 commits into
mainfrom
fix/dlna-browse-interfaces
Oct 18, 2026
Merged

InstaZDLL merged 2 commits into
mainfrom
fix/dlna-browse-interfaces

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Two defects in the opt-in DLNA server: folders answered nothing when a controller asked for their metadata, and the server could advertise an address the receiver cannot reach.

BrowseMetadata on a container returned nothing

browse_metadata only handled the root and 0/track/<id>. A BrowseMetadata on 0/artists, 0/albums, 0/albums/<id> or 0/artists/<id> answered an empty DIDL. No container carried childCount, and a track reported parentID="0". Controllers that read a folder's metadata before listing it, or before queueing a whole folder, stalled or showed it empty.

  • Ids: an ObjectId enum parses ids once for both browse flags.
  • Metadata: every container the server lists now answers BrowseMetadata with one <container> carrying its id, parentID, title, class and childCount. The counts come from index-backed COUNT subqueries (idx_album_artist, idx_track_album_available) and only include available tracks. Listings carry childCount too.
  • Parents: an album names 0/albums, its canonical place, even when it was reached through an artist. A track names its album container, or 0 when it has no album.
  • Root titles: they stay as they are; localizing them is a separate card.

The servers advertised the first IPv4 they found

pick_lan_ip took .next(), SSDP joined only that interface, and MPD showed the same address. With a VPN, Hyper-V, WSL or Docker adapter listed first, the description, stream and cover URLs pointed at an address the receiver could not reach.

  • Ranking: lan_addrs() puts private addresses on running interfaces first, then public ones, then point-to-point, down or adapters named like a VPN/VM/container, then link-local. A name only lowers an address's rank, it never drops it.
  • SSDP: it joins the multicast group on every IPv4 interface. Each NOTIFY goes out on each interface with that interface's own LOCATION. An M-SEARCH is answered with the local address on the requester's subnet.
  • HTTP: stream and cover URLs and the description's URLBase are built from the address each request arrived on (ConnectInfo). Without this, a correct LOCATION would still have led to unreachable stream URLs.
  • MPD: clients connect to us, so only the address shown in Settings changes.
  • Dependencies: none added (if-addrs, socket2 and axum were already there), so the Flatpak sources are untouched.
  • Known limits, documented: the interface list is read when the server starts, so after a network change the server must be toggled off and on. Choosing an interface by hand is left as a follow-up.

Checks

  • New pure tests: id parsing, parents, childCount, interface ranking, subnet matching (including IPv4-mapped peers) and the LOCATION format.
  • The 13 SQL queries in cds.rs were run against an in-memory database built from every profile migration.
  • cargo fmt --check passes, cargo clippy -p waveflow --all-targets -D warnings is clean, and the app tests compile (they run on the CI's Ubuntu job).
  • Local CodeRabbit review: no findings.
  • Documented in docs/features/dlna.md and docs/features/mpd.md.

To test: on a PC with WSL, Hyper-V or a VPN, enable the server. In BubbleUPnP or VLC, open Artists, then an artist, then an album. The folders should show counts, "play all" should queue the album, and the stream URLs should use the LAN IP.

@coderabbitai review

Summary by CodeRabbit

  • Améliorations
    • Les connexions DLNA utilisent l’adresse réseau réellement empruntée, ce qui permet aux URL de flux et d’illustration de correspondre à l’interface utilisée.
    • Les annonces DLNA sont désormais diffusées sur les interfaces IPv4 disponibles, avec une adresse adaptée à chaque appareil connecté.
    • Les réponses DLNA fournissent les métadonnées et le nombre d’éléments des conteneurs. Un identifiant inconnu ou une piste indisponible renvoie un résultat vide plutôt qu’une erreur.
    • L’adresse affichée dans le statut MPD privilégie une interface LAN réelle plutôt qu’une interface virtuelle.

A BrowseMetadata on 0/artists, 0/albums or one artist or album answered
an empty DIDL, so a controller that reads a folder's metadata before
opening or queueing it showed the folder empty. Containers carried no
childCount, and a track's own metadata named the root as its parent.

Object ids are now parsed once into an ObjectId that both browse flags
dispatch on. Every container id answers BrowseMetadata with a single
container carrying its childCount, from index-backed COUNT subqueries
that the listings now carry too. An album names 0/albums as its parent
(the container its id lives under) and a track names its album.
The servers took the first non-loopback IPv4 the OS listed, joined the
SSDP group on that interface only and put it in every LOCATION, URLBase,
stream and cover URL. With a VPN, WSL, Hyper-V or Docker adapter listed
first, a receiver was pointed at an address it cannot reach.

lan_addrs now ranks every IPv4 address: a private one on a running,
physical interface first, then a non-private one, then point-to-point,
down or VPN / VM / container-named adapters, link-local last. The name
match only demotes. SSDP joins the group on every interface, sends each
NOTIFY batch out of each interface with its own address, and answers an
M-SEARCH with the address on the requester's subnet. The HTTP layer
builds its URLs from the local address each request arrived on, read
through ConnectInfo. With no IPv4 address at all, SSDP now reports an
error instead of announcing 0.0.0.0.

MPD only displays the address (clients connect to us), so it keeps
calling pick_lan_ip and simply shows the better-ranked one.
@InstaZDLL InstaZDLL added scope: backend Rust/Tauri backend (src-tauri/) scope: docs Docs, README, assets type: fix Bug fix size: xl > 500 lines labels Oct 18, 2026
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: InstaZDLL/WaveFlow/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 57e61fd1-fbce-422d-9531-f58723874a53
📥 Commits

Reviewing files that changed from the base of the PR and between 0dd8d15 and f0ad7ea.

📒 Files selected for processing: 6
  • docs/features/dlna.md
  • docs/features/mpd.md
  • src-tauri/crates/app/src/dlna/cds.rs
  • src-tauri/crates/app/src/dlna/http.rs
  • src-tauri/crates/app/src/dlna/mod.rs
  • src-tauri/crates/app/src/dlna/ssdp.rs

Included review availability: This review used your included allowance. 6 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 8 reviews per hour.

📝 Walkthrough

Le serveur DLNA choisit des adresses IPv4 selon les interfaces et les sous-réseaux. HTTP construit ses URL selon l’adresse locale de la connexion, et SSDP utilise plusieurs interfaces. Le Content Directory fournit désormais les métadonnées, les parents et les décomptes des conteneurs et des pistes.

Changes: Adresses et navigation DLNA

Layer / File(s) Summary
Sélection des adresses LAN
src-tauri/crates/app/src/dlna/mod.rs, docs/features/dlna.md, docs/features/mpd.md
Le serveur énumère et classe les adresses IPv4 LAN. Il utilise une adresse préférée pour l’URL du serveur et sélectionne, si possible, l’adresse du sous-réseau du pair.
URL HTTP selon la connexion
src-tauri/crates/app/src/dlna/http.rs, src-tauri/crates/app/src/dlna/mod.rs, src-tauri/crates/app/src/dlna/cds.rs
Axum transmet l’adresse locale de chaque connexion. Le descripteur DLNA et les URL de média utilisent cette adresse, avec l’URL configurée comme repli.
SSDP sur plusieurs interfaces
src-tauri/crates/app/src/dlna/ssdp.rs, src-tauri/crates/app/src/dlna/mod.rs, docs/features/dlna.md
SSDP rejoint le groupe multicast et envoie des annonces par interface. Les réponses M-SEARCH utilisent l’adresse correspondant au sous-réseau du demandeur. Une liste d’adresses vide provoque une erreur NotFound.
Métadonnées du Content Directory
src-tauri/crates/app/src/dlna/cds.rs, docs/features/dlna.md
Browse traite les identifiants de conteneurs et de pistes. Les métadonnées indiquent les parents et les décomptes d’enfants disponibles. Les identifiants inconnus ou les objets absents renvoient un DIDL vide.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant ServeurDLNA
  participant InterfacesLAN
  participant SSDP
  participant ClientDLNA
  ServeurDLNA->>InterfacesLAN: Énumère et classe les adresses IPv4
  InterfacesLAN-->>ServeurDLNA: Retourne les adresses LAN
  ServeurDLNA->>SSDP: Démarre avec toutes les adresses
  SSDP-->>ClientDLNA: Envoie NOTIFY par interface
  ClientDLNA->>SSDP: Envoie M-SEARCH
  SSDP->>InterfacesLAN: local_ip_for choisit l’adresse du sous-réseau
  SSDP-->>ClientDLNA: Répond avec la LOCATION correspondante
Loading
Merge Risk: ⚪ Minimal · up to f0ad7

No identified issue currently prevents merging the DLNA changes after normal checks.

Security architecture risk: 🟡 Moderate · up to f0ad7

When network sharing is enabled, the server now attempts discovery announcements on additional networks, including virtual, VPN and public-address adapters. This can make the music library easier to discover outside the intended LAN. Sharing remains opt-in, and this PR does not newly open the HTTP listener on those interfaces; actual access still depends on routing and firewall rules.

Retained concerns

  • Medium · security · observed: Discovery is broadened to every enumerated non-loopback IPv4 interface without a trust-zone selection control. VPN, VM, container and public-address interfaces are only ranked lower, not excluded. Where multicast and HTTP traffic are permitted, peers on additional networks can discover the server and receive usable library URLs without pairing. The HTTP access surface already existed on those interfaces; the introduced concern is broader automatic advertisement and discovery, not newly granted arbitrary-file access or proven Internet exposure.
Security review details

Security Blast Radius

  • inferred — The affected assets are the enabled server's library metadata, available-track streams, artwork and discovery identity. Additional multicast-capable networks can receive announcements. Reading media still requires HTTP connectivity through the host's existing routing and firewall policy; broader discovery does not establish unrestricted Internet access.

Security Findings and Attack Paths

  • inferred — A peer on an additional advertised interface can learn the description URL through NOTIFY or M-SEARCH, browse the library and request available tracks if HTTP is reachable. No pairing is required. This makes a pre-existing wildcard media service easier to discover and consume; it is not evidence of newly introduced arbitrary-file access.

Trust Boundaries and Controls

  • observed — Interface ranking and peer-subnet matching are address-selection mechanisms, not authorization. Per-interface source binding and multicast TTL 4 constrain announcement egress, while the documented opt-in switch controls sharing globally. None selects which networks are trusted.

Resilience and Maintainability Implications

  • observed — Normal stop aborts both SSDP tasks through their ownership guard. The broader interface list remains within the same cleanup owner rather than creating independently managed discovery services.

Hardening Proposals

  • proposed — Offer explicit trusted-interface selection and make discovery scope visible when enabling sharing. If this is intended as an access boundary, apply the same policy to HTTP admission or binding; filtering announcements alone would leave the pre-existing wildcard media listener reachable.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 63.93% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 4 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed Le titre est concis, suit le format Conventional Commits et décrit les deux changements principaux : BrowseMetadata pour les conteneurs et l’annonce d’une adresse DLNA joignable.
Description check Passed La description explique les deux défauts, les corrections, les limites connues, les tests réalisés et la documentation mise à jour. Les sections formelles Checklist et Linked issues ne sont pas repris…
Full details: Description check

Explanation

La description explique les deux défauts, les corrections, les limites connues, les tests réalisés et la documentation mise à jour. Les sections formelles Checklist et Linked issues ne sont pas reprises, mais les informations essentielles sont présentes.

Full details: Docstring Coverage

Explanation

Docstring coverage is 63.93% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 4 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@InstaZDLL InstaZDLL added this to the v2.0.0 milestone Oct 18, 2026
@InstaZDLL InstaZDLL self-assigned this Oct 18, 2026
@InstaZDLL
InstaZDLL merged commit 7c6b3d7 into main Oct 18, 2026
17 checks passed
@InstaZDLL
InstaZDLL deleted the fix/dlna-browse-interfaces branch October 18, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: backend Rust/Tauri backend (src-tauri/) scope: docs Docs, README, assets size: xl > 500 lines type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant