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 the bundled file.
See COMMUNITY_OVERRIDES.md.
Single source of truth. This bundled file is the rules — there is no Python copy. The startup integrity check (
utils/config_integrity.py) refuses to launch with a "corrupted installation, please reinstall" dialog if it is missing or not valid JSON.
| 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 |
SSDP type classification is fully data-driven — there is no hardcoded type chain in
ssdp.py anymore. Two ordered lists (both first-match-wins), evaluated by _match_type_rules:
type_rules— curated, specific rules. The override stage (_apply_type_rules), evaluated against a rich haystack (SSDP headers plusfriendlyName/displayName/roomName/modelName…). Tokens here must be specific because they see free-form names.base_type_rules— coarse fallbacks formerly hardcoded in_infer_type(mediaserver,router,printer,computer,modelType==nas, …). The base stage (_infer_type), evaluated against a narrow haystack (ST/USN/SERVER/modelName/manufacturer only — no friendly/room names) so loose tokens likewandon't false-positive on a device named e.g. "Rowan's iPhone". These fire only when notype_rulesentry matched.
| Field | Type | Meaning |
|---|---|---|
contains_any |
list | Lowercase substrings searched in the stage's haystack |
equals_field |
object | {fieldName: [values]} — exact, case-insensitive match against a field (e.g. {"modelType": ["nas"]}) |
type |
string | Internal type id (e.g. router, nas, smarttv) |
A rule matches if either contains_any or equals_field is satisfied. Use equals_field
when a substring match would be too loose (a NAS that also advertises DLNA must stay nas).
- Add device-specific rules to
type_rules(specific tokens only); leavebase_type_rulesfor coarse fallbacks. Put more specificcontains_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.
User overlay (
~/.config/netneighbor/ssdp_rules.json) prepends totype_rules— the right place for your own classifications.base_type_rulesstays as shipped.
- 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