|
@@ -35,8 +35,8 @@ package upnp
|
|
|
import (
|
|
|
"bufio"
|
|
|
"bytes"
|
|
|
+ "context"
|
|
|
"encoding/xml"
|
|
|
- "errors"
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
|
"net"
|
|
@@ -47,6 +47,8 @@ import (
|
|
|
"sync"
|
|
|
"time"
|
|
|
|
|
|
+ "github.com/pkg/errors"
|
|
|
+
|
|
|
"github.com/syncthing/syncthing/lib/dialer"
|
|
|
"github.com/syncthing/syncthing/lib/nat"
|
|
|
)
|
|
@@ -83,7 +85,7 @@ func (e UnsupportedDeviceTypeError) Error() string {
|
|
|
|
|
|
// Discover discovers UPnP InternetGatewayDevices.
|
|
|
// The order in which the devices appear in the results list is not deterministic.
|
|
|
-func Discover(renewal, timeout time.Duration) []nat.Device {
|
|
|
+func Discover(ctx context.Context, renewal, timeout time.Duration) []nat.Device {
|
|
|
var results []nat.Device
|
|
|
|
|
|
interfaces, err := net.Interfaces()
|
|
@@ -105,7 +107,7 @@ func Discover(renewal, timeout time.Duration) []nat.Device {
|
|
|
for _, deviceType := range []string{"urn:schemas-upnp-org:device:InternetGatewayDevice:1", "urn:schemas-upnp-org:device:InternetGatewayDevice:2"} {
|
|
|
wg.Add(1)
|
|
|
go func(intf net.Interface, deviceType string) {
|
|
|
- discover(&intf, deviceType, timeout, resultChan)
|
|
|
+ discover(ctx, &intf, deviceType, timeout, resultChan)
|
|
|
wg.Done()
|
|
|
}(intf, deviceType)
|
|
|
}
|
|
@@ -135,7 +137,7 @@ nextResult:
|
|
|
|
|
|
// Search for UPnP InternetGatewayDevices for <timeout> seconds.
|
|
|
// The order in which the devices appear in the result list is not deterministic
|
|
|
-func discover(intf *net.Interface, deviceType string, timeout time.Duration, results chan<- nat.Device) {
|
|
|
+func discover(ctx context.Context, intf *net.Interface, deviceType string, timeout time.Duration, results chan<- nat.Device) {
|
|
|
ssdp := &net.UDPAddr{IP: []byte{239, 255, 255, 250}, Port: 1900}
|
|
|
|
|
|
tpl := `M-SEARCH * HTTP/1.1
|
|
@@ -187,13 +189,15 @@ USER-AGENT: syncthing/1.0
|
|
|
}
|
|
|
break
|
|
|
}
|
|
|
- igds, err := parseResponse(deviceType, resp[:n])
|
|
|
+ igds, err := parseResponse(ctx, deviceType, resp[:n])
|
|
|
if err != nil {
|
|
|
switch err.(type) {
|
|
|
case *UnsupportedDeviceTypeError:
|
|
|
l.Debugln(err.Error())
|
|
|
default:
|
|
|
- l.Infoln("UPnP parse:", err)
|
|
|
+ if errors.Cause(err) != context.Canceled {
|
|
|
+ l.Infoln("UPnP parse:", err)
|
|
|
+ }
|
|
|
}
|
|
|
continue
|
|
|
}
|
|
@@ -205,7 +209,7 @@ USER-AGENT: syncthing/1.0
|
|
|
l.Debugln("Discovery for device type", deviceType, "on", intf.Name, "finished.")
|
|
|
}
|
|
|
|
|
|
-func parseResponse(deviceType string, resp []byte) ([]IGDService, error) {
|
|
|
+func parseResponse(ctx context.Context, deviceType string, resp []byte) ([]IGDService, error) {
|
|
|
l.Debugln("Handling UPnP response:\n\n" + string(resp))
|
|
|
|
|
|
reader := bufio.NewReader(bytes.NewBuffer(resp))
|
|
@@ -257,7 +261,7 @@ func parseResponse(deviceType string, resp []byte) ([]IGDService, error) {
|
|
|
// We do this in a fairly roundabout way by connecting to the IGD and
|
|
|
// checking the address of the local end of the socket. I'm open to
|
|
|
// suggestions on a better way to do this...
|
|
|
- localIPAddress, err := localIP(deviceDescriptionURL)
|
|
|
+ localIPAddress, err := localIP(ctx, deviceDescriptionURL)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -270,8 +274,10 @@ func parseResponse(deviceType string, resp []byte) ([]IGDService, error) {
|
|
|
return services, nil
|
|
|
}
|
|
|
|
|
|
-func localIP(url *url.URL) (net.IP, error) {
|
|
|
- conn, err := dialer.DialTimeout("tcp", url.Host, time.Second)
|
|
|
+func localIP(ctx context.Context, url *url.URL) (net.IP, error) {
|
|
|
+ timeoutCtx, cancel := context.WithTimeout(ctx, time.Second)
|
|
|
+ defer cancel()
|
|
|
+ conn, err := dialer.DialContext(timeoutCtx, "tcp", url.Host)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|