SSDP (Simple Service Discovery Protocol, part of UPnP) uses UDP multicast on 239.255.255.250:1900.
| Message | Direction | Meaning |
|---|---|---|
| M-SEARCH | Client → multicast | "Who is there?" — devices respond with HTTP 200 |
| NOTIFY alive | Device → multicast | Unsolicited announcement |
| NOTIFY byebye | Device → multicast | Device going offline |
The announced LOCATION is an HTTP URL pointing to a device description XML file. NetNeighbor fetches that document to get friendly name, device type, icons, presentation URL, etc.
NetNeighbor listens and periodically sends M-SEARCH — it does not scan the LAN.
| Area | File / symbol | Role |
|---|---|---|
| Provider | discovery/ssdp.py |
SSDPDiscovery — socket, listen thread, refresh thread, GC, XML fetch |
| Contract | discovery/base.py |
BaseDiscovery._emit("device", payload) → normalized dict to manager |
| Orchestration | discovery/manager.py |
add_or_update_device — overrides, SSDP merge, notify listeners |
| Model | model/device.py |
Device — key prefers UDN, then USN base, then MAC, then source:ip:port |
| Rules | config/ssdp_rules.json |
Naming / information / type rules |
| UI payload | utils/details_payload.py |
build_ssdp_payload — rows for the details dialog |
- Parse headers (
LOCATION,ST/NT,USN,SERVER,CACHE-CONTROL, …) - Derive
ip/portfromLOCATIONURL (default 80) - Fetch and parse XML from
LOCATION(cached briefly) - Infer
nameandtypefrom headers + XML; applyssdp_rules.json - Emit payload with
metadataholding headers,xml_fields, raw XML
- Online: every valid response or NOTIFY alive refreshes the seen timestamp
- NOTIFY byebye: immediate
online: falsepayload - Timeout: GC emits
online: falsewhen expiry passes.
Expiry ≈2 × CACHE-CONTROL max-age, clamped to a maximum; default minimum when not specified.
DiscoveryManager may merge SSDP updates sharing the same ip:port (MAC-assisted) and merges XML metadata so the UI shows one logical row per stable Device.key.
Lets you tune naming, information text, and type classification without Python changes.
User overlay: ~/.config/netneighbor/ssdp_rules.json — merged with bundled defaults.
See COMMUNITY_OVERRIDES.md.
| Field | Type | Meaning |
|---|---|---|
fallback_fields |
list | Ordered xml_fields keys to use as name when earlier keys are empty |
prefer_display_name_when_no_friendly_name |
bool | Use displayName if friendlyName is missing |
| Field | Type | Meaning |
|---|---|---|
concat_fields |
list | XML field names to join for the "Information" detail row |
separator |
string | Placed between non-empty values |
Evaluated in order; first match wins.
| Field | Type | Meaning |
|---|---|---|
contains_any |
list | Lowercase substrings searched in SSDP headers + selected XML fields |
type |
string | Internal type id (e.g. router, nas, smarttv) |
- Put specific
contains_anygroups before generic ones. - Use tokens that appear in your network's actual SSDP/XML (check SSDP details dialog).
- Keep
typevalues consistent withconfig/device_types.json. - Validate:
python -m json.tool config/ssdp_rules.json - Restart the app.
- Enable
ssdpin~/.config/netneighbor/logging.json - SSDP details dialog: inspect LOCATION, USN, XML presence
- Watch for "XML fetched" vs fetch failures (timeout, parse error)
MDNS.md— parallel mDNS pipelineWSD.md— WS-Discovery pipelineNETBIOS.md— NetBIOS pipelineCOMMUNITY_OVERRIDES.md— user overlay rulesBACKEND_ARCHITECTURE.md— discovery manager overview