Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/portmapper: also send UPnP SSDP query to the SSDP multicast address. #3199

Merged
merged 1 commit into from Oct 27, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions net/portmapper/portmapper.go
Expand Up @@ -677,6 +677,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {

pxpAddr := netaddr.IPPortFrom(gw, c.pxpPort()).UDPAddr()
upnpAddr := netaddr.IPPortFrom(gw, c.upnpPort()).UDPAddr()
upnpMulticastAddr := netaddr.IPPortFrom(netaddr.IPv4(239, 255, 255, 250), c.upnpPort()).UDPAddr()

// Don't send probes to services that we recently learned (for
// the same gw/myIP) are available. See
Expand All @@ -694,7 +695,47 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
if c.sawUPnPRecently() {
res.UPnP = true
} else if !DisableUPnP {
// Strictly speaking, you discover UPnP services by sending an
// SSDP query (which uPnPPacket is) to udp/1900 on the SSDP
// multicast address, and then get a flood of responses back
// from everything on your network.
//
// Empirically, many home routers also respond to SSDP queries
// directed at udp/1900 on their LAN unicast IP
// (e.g. 192.168.1.1). This is handy because it means we can
// probe the router directly and likely get a reply. However,
// the specs do not _require_ UPnP devices to respond to
// unicast SSDP queries, so some conformant UPnP
// implementations only respond to multicast queries.
//
// In theory, we could send just the multicast query and get
// all compliant devices to respond. However, we instead send
// to both a unicast and a multicast addresses, for a couple
// of reasons:
//
// First, some LANs and OSes have broken multicast in one way
// or another, so it's possible for the multicast query to be
// lost while the unicast query gets through. But we still
// have to send the multicast query to also get a response
// from strict-UPnP devices on multicast-working networks.
//
// Second, SSDP's packet dynamics are a bit weird: you send
// the SSDP query from your unicast IP to the SSDP multicast
// IP, but responses are from the UPnP devices's _unicast_ IP
// to your unicast IP. This can confuse some less-intelligent
// stateful host firewalls, who might block the responses. To
// work around this, we send the unicast query first, to teach
// the firewall to expect a unicast response from the router,
// and then send our multicast query. That way, even if the
// device doesn't respond to the unicast query, we've set the
// stage for the host firewall to accept the response to the
// multicast query.
//
// See https://github.com/tailscale/tailscale/issues/3197 for
// an example of a device that strictly implements UPnP, and
// only responds to multicast queries.
uc.WriteTo(uPnPPacket, upnpAddr)
uc.WriteTo(uPnPPacket, upnpMulticastAddr)
}

buf := make([]byte, 1500)
Expand Down