Skip to content

UPnP: filter non-WAN device announcements before fetching description (#622) - #623

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:fix/upnp-filter-non-wan-discovery
May 15, 2026
Merged

UPnP: filter non-WAN device announcements before fetching description (#622)#623
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:fix/upnp-filter-non-wan-discovery

Conversation

@got3nks

@got3nks got3nks commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

The SSDP discovery callback in CUPnPControlPoint::Callback handles UPNP_DISCOVERY_ADVERTISEMENT_ALIVE by downloading the device's description.xml first and only then checking the parsed deviceType for IGW (line 1234). On a typical home LAN — smart speakers, media renderers, mesh APs, ESP32 IoT devices all multicasting their own NOTIFY ALIVE announcements — amule fetches XML from endpoints it has no use for. Two consequences reported in #622:

  • "Error retrieving device description" log lines fired at critical level for every device that returns a transient socket error or malformed URL. Stoatwblr's report shows both a SqueezeboxServer MediaRenderer and a Zyxel mesh WAP triggering it.
  • libupnp's internal ThreadPool queue fills under announcement bursts (ThreadPoolAdd too many jobs: 100), because amule's callback is the slow consumer.

Fix

Filter UPNP_DISCOVERY_ADVERTISEMENT_ALIVE by NT (DeviceType in the UpnpDiscovery struct) before going near the network. The whitelist is the IGW family plus upnp:rootdevice (which is opaque from SSDP alone — the XML still has to be fetched to classify it):

  • upnp:rootdevice
  • urn:schemas-upnp-org:device:InternetGatewayDevice:*
  • urn:schemas-upnp-org:device:WANDevice:*
  • urn:schemas-upnp-org:device:WANConnectionDevice:*
  • urn:schemas-upnp-org:device:LANDevice:*
  • urn:schemas-upnp-org:service:Layer3Forwarding:*
  • urn:schemas-upnp-org:service:WANCommonInterfaceConfig:*
  • urn:schemas-upnp-org:service:WANIPConnection:*
  • urn:schemas-upnp-org:service:WANPPPConnection:*

Prefix-matched (case-insensitive) so future :2/:3 UPnP revisions don't need code changes. The new helper UPnP::IsWANRelatedDeviceType lives next to the existing constants block.

case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: {
    /* fetch NT/DeviceType from the discovery struct */
    if (!UPnP::IsWANRelatedDeviceType(deviceType ? deviceType : "")) {
        break;
    }
    msg << "error(UPNP_DISCOVERY_ADVERTISEMENT_ALIVE): ";
    msg2<< "UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: ";
    goto upnpDiscovery;
}

UPNP_DISCOVERY_SEARCH_RESULT processing is untouched — we explicitly issued an M-SEARCH for upnp:rootdevice and need to handle whatever the matching devices respond with.

Why not just demote the log

Two reasons we don't simply demote AddDebugLogLineC to AddDebugLogLineN:

  1. Stoatwblr's primary ask is that amule "doesn't poke at anything which doesn't advertise as a WAN" — i.e. functional filtering, not just log suppression. Random LAN devices shouldn't see HTTP GETs from amule at all.
  2. The libupnp ThreadPool too many jobs saturation is fixed only by returning from the callback faster, which requires not doing the download.

Validation

  • macOS arm64, monolithic amule rebuilds clean.
  • The empty-string and unrecognised-NT cases both return early without touching the network.
  • IGW devices and ambiguous upnp:rootdevice announcements still fall through to the existing path so amule continues to detect and port-map against real routers.

Follow-up worth flagging

src/UPnPBase.cpp has ~30 #if UPNP_VERSION >= 10800 blocks carrying compatibility with libupnp 1.6.x. libupnp 1.8 shipped in April 2017 and every non-ESM distro has been on 1.8+ for years (Debian Buster shipped 1.8.4; Ubuntu 20.04+ on 1.14; Homebrew on 1.18). A separate cleanup PR collapsing those branches would simplify the file substantially — kept out of this PR to stay focused.

Closes #622.

…amule-project#622)

The SSDP discovery callback handles UPNP_DISCOVERY_ADVERTISEMENT_ALIVE
by downloading description.xml first and only then checking the parsed
deviceType. On a typical home LAN -- with smart speakers, media
renderers, mesh APs, ESP32 IoT devices, etc. all multicasting their
own NOTIFY ALIVE announcements -- amule ends up fetching XML from
endpoints it has no use for. Two consequences in the wild (amule-project#622):

  * "Error retrieving device description" log lines for every device
    that returns a transient socket error, malformed URL, or weird
    Content-Type. Stoatwblr's report shows squeezeboxserver and a
    Zyxel mesh WAP both triggering it.

  * libupnp's internal ThreadPool queue fills under bursts of
    announcements ("ThreadPoolAdd too many jobs: 100"), because the
    callback is the slow consumer.

Filter NOTIFY ALIVE by NT (DeviceType in the UpnpDiscovery struct)
before going near the network. The whitelist is the IGW family plus
upnp:rootdevice (which is opaque from SSDP -- still needs the XML to
classify):

  - upnp:rootdevice
  - urn:schemas-upnp-org:device:InternetGatewayDevice:*
  - urn:schemas-upnp-org:device:WANDevice:*
  - urn:schemas-upnp-org:device:WANConnectionDevice:*
  - urn:schemas-upnp-org:device:LANDevice:*
  - urn:schemas-upnp-org:service:Layer3Forwarding:*
  - urn:schemas-upnp-org:service:WANCommonInterfaceConfig:*
  - urn:schemas-upnp-org:service:WANIPConnection:*
  - urn:schemas-upnp-org:service:WANPPPConnection:*

Prefix-matched so future :2/:3 UPnP revisions don't need code
changes. UPNP_DISCOVERY_SEARCH_RESULT processing is untouched -- we
explicitly asked, we still handle whatever comes back.

Verified locally on macOS arm64: monolithic amule rebuilds clean.

Reported by Stoatwblr.
@got3nks got3nks mentioned this pull request May 15, 2026
@mrjimenez
mrjimenez merged commit b4083e7 into amule-project:master May 15, 2026
12 checks passed
@got3nks
got3nks deleted the fix/upnp-filter-non-wan-discovery branch May 15, 2026 19:16
mrjimenez pushed a commit that referenced this pull request May 15, 2026
The SSDP discovery + service-walk paths log a handful of lines at
AddDebugLogLineC (critical -- always visible to the user), which on
busy home LANs fills stdout with messages the user can't act on:

  - "Error retrieving device description from <url>: ..." -- transient
    HTTP fetch failures from random LAN devices that announce via
    SSDP but block HTTP from amule's subnet (mesh APs, multi-vlan
    setups, IoT devices going offline)
  - "Uninteresting service detected: '...'" -- emitted as amule walks
    a downloaded description.xml; one line per service it doesn't
    consume. Useful for debugging, noise for users
  - "error(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE): ..." -- BYEBYE event
    parse error, expected during LAN churn
  - "Error getting SCPD Document from ..." / subscribe-error path --
    routine, amule already retries

Demote those to AddDebugLogLineN. They stay accessible with
DebugLogTypes=UPnP enabled, but no longer surface to stdout for
users running the default log level. Keep user-actionable errors
(AddPortMapping/DeletePortMapping failures, "WAN Service not
detected" at port-map time) and success notifications (IGW
detected, SCPD retrieved, subscribed) at critical level so the
"is UPnP working?" UX is unchanged.

Reported by Stoatwblr in #622 -- a Zyxel mesh WAP that announces
SSDP rootdevice from a subnet whose HTTP is unreachable to amule
generated a steady stream of "Error retrieving device description"
lines. PR #623 already drops the per-leaf-service-NT noise; this
finishes the cleanup for the rootdevice case that #623 deliberately
lets through (rootdevice can't be classified without the XML).

Verified locally on macOS arm64: monolithic amule rebuilds clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libupnp issues

2 participants