go-wsd

module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: AGPL-3.0

README

Web Service Discovery

Golang Web Service Discovery utilities.

The standard is described in https://www.oasis-open.org/standard/ws-discovery/

The current package offers two flavors, the draft of 2005 and the stabilized v1.1 of 2009. They are not interoperable: the wsa:To and the action URI differ, so a device listening for one ignores the other outright.

Initially written to serve use-go/onvif, a golang Onvif client library.

Two halves

Discovery is not just polling. A Probe is multicast and collects whatever answers inside a time window; Hello and Bye are what a device sends unprompted when it joins or leaves the network. A Probe alone can never observe a departure, so both are implemented.

import "github.com/jfsmig/go-wsd/wsd"

// Poll the link. The zero ProbeOptions speaks the dialect ONVIF mandates.
devices, err := wsd.Discover(ctx, "eth0", wsd.ProbeOptions{})
for _, d := range devices {
    fmt.Println(d.UUID, d.DeviceServiceURL)
}
// Watch the link until ctx is done; the channel is closed when it is.
announcements, err := wsd.Listen(ctx, "eth0")
for a := range announcements {
    fmt.Println(a.Kind, a.Device.From, a.Device.UUID) // Hello or Bye
}

Discover returns a wsd.Device: the Xaddr as host:port, the UUID endpoint reference, and the DeviceServiceURL exactly as advertised, scheme and path intact — ONVIF fixes neither, and §7.3.2.3 asks for one URI per protocol, https included.

Flavors

ProbeOptions.Flavor Version Namespace Answered by
FlavorDraft2005 (zero value) April 2005 XMLSOAP draft schemas.xmlsoap.org/ws/2005/04/discovery ONVIF cameras
FlavorOASIS11 OASIS WS-Discovery 1.1, 2009 docs.oasis-open.org/ws-dd/ns/discovery/2009/01 printers, Windows hosts (WSD)

ONVIF Core references the 2005 draft normatively, which is why it is the default: a camera ignores a v1.1 Probe. Listen needs no flavor — replies are matched on local element names, so one parser serves both.

Probe options

The zero value works. Everything below is a refinement.

Field Default Purpose
Timeout 3s Collection window of one exchange, bounded at both ends. Raised to MatchTimeout (1.1s) if lower: a device may wait up to 1s before it even starts answering. Lowered to MaxProbeTimeout (90s) if higher. One exchange runs per IP family, so a dual-stack interface pays it once per family.
Attempts 3 Multicast transmissions, per MULTICAST_UDP_REPEAT + 1 of SOAP-over-UDP §4, spaced by a randomised backoff.
HopLimit 1 Multicast TTL. Discovery is a link-local concern.
PortTypes, Scopes empty Narrow the Probe. Matching is conjunctive, so listing several selects fewer devices, not more.
IncludeNonOnvif false Keep Target Services advertising no ONVIF port type.
Flavor FlavorDraft2005 See above.

Port types

PortTypes takes TypeName values, which carry the namespace with the local name so a caller never has to keep a prefix map in step with a QName list. The well-known ones are exported — TypeONVIFNetworkVideoTransmitter, TypeONVIFDevice, TypeWindowsPrinter and their neighbours — and ParseTypeName accepts either a short name or the explicit {namespace}LocalName form, which is how a discovered type is printed:

devices, err := wsd.Discover(ctx, "eth0", wsd.ProbeOptions{
    PortTypes: []wsd.TypeName{wsd.TypeONVIFNetworkVideoTransmitter},
})

wsdc exposes the same thing as --types, comma-separated, on the discover subcommand:

wsdc discover                             # every probeable interface
wsdc discover eth0                        # every Target Service on that link
wsdc discover --types onvif-nvt eth0      # only ONVIF video transmitters
wsdc discover --types '{urn:x}Thing' eth0
wsdc -h                                   # lists the well-known names

An untyped Probe still carries an empty d:Types element. WS-Discovery says an absent d:Types matches every Target Service, and this package used to omit it on that reading. Equipment disagrees. Measured on one link with three ONVIF cameras and one non-ONVIF device:

Probe body devices that answered
<d:Probe/> — no d:Types 1 of 4
<d:Probe><d:Types/></d:Probe> 4 of 4
<d:Probe><d:Types> </d:Types></d:Probe> 3 of 4

The cameras answer whenever the element is present, whatever it contains. The element has to be genuinely empty: the blank variant is parsed by the non-ONVIF device as a type list matching nothing.

Untrusted input

Every datagram arrives unauthenticated over UDP multicast from any host on the link. Replies that do not parse, that do not correlate with the Probe that was sent, or that carry no usable address are discarded rather than reported — ONVIF Core §7.3.6 asks that malformed multicast be dropped silently rather than answered, to avoid packet storms.

An advertised address survives only if it is http or https and carries no credentials, because the caller is the one who will dial it. It still names wherever its sender chose, so treat DeviceServiceURL as an address you were given, not one you trust.

Device.From carries the sender, on both halves alike, because the endpoint reference inside a datagram is only a claim: any host on the link can assert any identity. It is the one field that was observed rather than told.

Volume is bounded as well as content: a probe retains a limited number of datagrams and a limited number of bytes, per exchange. The timeout bounds the window in which replies are collected, and is itself capped at MaxProbeTimeout (90s): without a ceiling one mistyped duration kept a socket, a multicast membership and a read loop busy for years. None of these bounds the whole call — one exchange runs per IP family, Attempts transmissions finish before the window opens, and replies are parsed after it closes — so give ctx a deadline if you want a bound on the call itself.

Code organization

  • wsd is a golang library doing the very basic job of web-service discovery: building and multicasting Probes, collecting and correlating the replies, and reporting the Hello and Bye announcements. ProbeableInterfaceNames answers the question that comes before all of that — which of a host's interfaces are worth the call — and is the one thing in the package that is policy rather than protocol.

  • wsd/transport is the multicast plumbing, IPv4 and IPv6, behind one connection interface.

  • gosoap builds the SOAP envelopes. Vendored from jfsmig/onvif.

  • bin/wsdc a CLI tool to wrap wsd, built on cobra:

    wsdc discover             # probe every probeable interface, in parallel
    wsdc discover eth0 wlan0  # probe these two, in parallel
    wsdc listen               # print Hello and Bye until interrupted
    wsdc completion bash      # a shell completion script, on stdout
    

    Both verbs take zero or more interface names. Naming none selects every interface a device can plausibly answer on — up, not the loopback, multicast-capable, and not a container, VM or overlay device by name, which is wsd.ProbeableInterfaceNames and the same policy onvif-cli applies. A name given explicitly is used whatever the filter would have said, so wsdc discover lo works. --all-interfaces widens the automatic set; it does nothing when a name is given, and it is not --all, which means something else entirely. Interfaces are polled in parallel, so a run costs one collection window per IP family rather than one per interface — a dual-stack interface still pays it twice, sequentially, inside Discover.

    The fan-out is unbounded on purpose, and the default filter is what keeps it small: one interface on a laptop, eighteen under --all-interfaces on a host running containers. The retention caps are per exchange, so that flag multiplies the memory a flooded link can make a run hold by the number of interfaces polled.

    Flags: --timeout (collection window per IP family, at most 90s), --oasis11 (use the v1.1 flavor), --all (keep devices advertising no ONVIF port type), --types (comma-separated port types to probe for, see above). All four describe a Probe, so they belong to discover and follow it on the command line; listen sends no Probe and rejects them. --all-interfaces is about interface selection rather than the Probe, so both verbs carry it.

    Exit status: 0 on success, including no device answered, nothing to poll, and one interface failing while another answers; 1 when no interface could be polled at all; 2 for a wrong command line. An unknown flag, verb or argument count also prints the usage block on stderr. A rejected value prints only the diagnostic, since the block would bury the line naming what is accepted: a negative --timeout, an unknown --types name, an unknown help topic, completion without a shell, and a wrong argument count to __complete, whose usage block describes machinery nobody types.

    On a discover or a listen, stdout carries data rows and nothing else: no device answered, and the line naming which interfaces were polled, are results and go to stderr. --help and wsdc completion <shell> are successes and print on stdout too, being what was asked for.

    Two tab-separated row shapes, both led by the interface the device was heard on:

    discover  INTERFACE  UUID  DEVICE_SERVICE_URL
    listen    INTERFACE  TIME  KIND  FROM  UUID  DEVICE_SERVICE_URL
    

    A field the device did not advertise is a literal -. The interface column was prepended, so cut -f2 now yields the UUID where it used to yield the device service URL — the field order matches onvif-cli's deliberately. A device answering on two interfaces prints one row per interface; nothing is de-duplicated across them.

License

AGPL-3.0-or-later, see LICENSE. gosoap/ derives from jfsmig/onvif, originally MIT; the file headers carry both notices.

Directories

Path Synopsis
bin
wsdc command
Command wsdc discovers WS-Discovery devices on the interfaces named, or on every interface a device can plausibly answer on when none is named.
Command wsdc discovers WS-Discovery devices on the interfaces named, or on every interface a device can plausibly answer on when none is named.
wsd
Package wsd implements the Client role of WS-Discovery, the protocol ONVIF cameras use to announce themselves on a local link.
Package wsd implements the Client role of WS-Discovery, the protocol ONVIF cameras use to announce themselves on a local link.
transport
Package transport carries the WS-Discovery multicast plumbing: the discovery groups of both IP families, joining them on an interface, and one connection interface over the two families, whose PacketConn types differ only in an argument unused here.
Package transport carries the WS-Discovery multicast plumbing: the discovery groups of both IP families, joining them on an interface, and one connection interface over the two families, whose PacketConn types differ only in an argument unused here.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL