syncthing/lib/upnp/upnp.go

583 lines
16 KiB
Go
Raw Normal View History

2014-11-16 20:13:20 +00:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 19:43:32 +00:00
//
2015-03-07 20:36:35 +00:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
2014-04-18 11:20:42 +00:00
// Adapted from https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/IGD.go
// Copyright (c) 2010 Jack Palevich (https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/LICENSE)
2014-06-01 20:50:14 +00:00
2014-10-18 02:01:25 +00:00
// Package upnp implements UPnP InternetGatewayDevice discovery, querying, and port mapping.
2014-06-01 20:50:14 +00:00
package upnp
2014-04-18 11:20:42 +00:00
import (
"bufio"
"bytes"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"regexp"
2015-05-17 15:42:26 +00:00
"runtime"
2014-04-18 11:20:42 +00:00
"strings"
"time"
2015-04-22 22:54:31 +00:00
2015-10-12 18:30:14 +00:00
"github.com/syncthing/syncthing/lib/dialer"
2015-08-06 09:29:25 +00:00
"github.com/syncthing/syncthing/lib/sync"
2014-04-18 11:20:42 +00:00
)
2015-04-28 20:32:10 +00:00
// An IGD is a UPnP InternetGatewayDevice.
2014-04-18 11:20:42 +00:00
type IGD struct {
uuid string
friendlyName string
services []IGDService
url *url.URL
localIPAddress string
}
func (n *IGD) UUID() string {
return n.uuid
}
func (n *IGD) FriendlyName() string {
return n.friendlyName
}
2015-04-28 20:32:10 +00:00
// FriendlyIdentifier returns a friendly identifier (friendly name + IP
// address) for the IGD.
func (n *IGD) FriendlyIdentifier() string {
return "'" + n.FriendlyName() + "' (" + strings.Split(n.URL().Host, ":")[0] + ")"
}
func (n *IGD) URL() *url.URL {
return n.url
}
2015-04-28 20:32:10 +00:00
// An IGDService is a specific service provided by an IGD.
type IGDService struct {
2015-12-23 15:31:12 +00:00
ID string
URL string
URN string
}
2014-04-18 11:20:42 +00:00
type Protocol string
const (
TCP Protocol = "TCP"
UDP = "UDP"
)
type upnpService struct {
2015-12-23 15:31:12 +00:00
ID string `xml:"serviceId"`
Type string `xml:"serviceType"`
ControlURL string `xml:"controlURL"`
2014-04-18 11:20:42 +00:00
}
type upnpDevice struct {
DeviceType string `xml:"deviceType"`
FriendlyName string `xml:"friendlyName"`
Devices []upnpDevice `xml:"deviceList>device"`
Services []upnpService `xml:"serviceList>service"`
2014-04-18 11:20:42 +00:00
}
type upnpRoot struct {
Device upnpDevice `xml:"device"`
}
2014-10-18 02:01:25 +00:00
// Discover discovers UPnP InternetGatewayDevices.
// The order in which the devices appear in the results list is not deterministic.
2015-04-15 23:34:27 +00:00
func Discover(timeout time.Duration) []IGD {
var results []IGD
2014-04-18 11:20:42 +00:00
interfaces, err := net.Interfaces()
if err != nil {
l.Infoln("Listing network interfaces:", err)
return results
}
resultChan := make(chan IGD)
2015-04-22 22:54:31 +00:00
wg := sync.NewWaitGroup()
for _, intf := range interfaces {
2015-05-17 15:42:26 +00:00
// Interface flags seem to always be 0 on Windows
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
continue
}
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)
wg.Done()
}(intf, deviceType)
}
2014-04-18 11:20:42 +00:00
}
go func() {
wg.Wait()
close(resultChan)
}()
nextResult:
for result := range resultChan {
for _, existingResult := range results {
if existingResult.uuid == result.uuid {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
if shouldDebug() {
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
2015-12-23 15:31:12 +00:00
for _, service := range result.services {
l.Debugf("* [%s] %s", service.ID, service.URL)
}
}
continue nextResult
}
}
results = append(results, result)
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
if shouldDebug() {
l.Debugf("UPnP discovery result %s with services:", result.uuid)
2015-12-23 15:31:12 +00:00
for _, service := range result.services {
l.Debugf("* [%s] %s", service.ID, service.URL)
}
}
}
return results
}
2014-10-18 02:01:25 +00:00
// Search for UPnP InternetGatewayDevices for <timeout> seconds, ignoring responses from any devices listed in knownDevices.
// 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<- IGD) {
ssdp := &net.UDPAddr{IP: []byte{239, 255, 255, 250}, Port: 1900}
tpl := `M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
ST: %s
MAN: "ssdp:discover"
MX: %d
USER-AGENT: syncthing/1.0
2014-04-18 11:20:42 +00:00
2014-05-28 14:04:20 +00:00
`
2015-04-15 23:34:27 +00:00
searchStr := fmt.Sprintf(tpl, deviceType, timeout/time.Second)
2014-05-28 14:04:20 +00:00
search := []byte(strings.Replace(searchStr, "\n", "\r\n", -1))
2014-04-18 11:20:42 +00:00
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("Starting discovery of device type", deviceType, "on", intf.Name)
socket, err := net.ListenMulticastUDP("udp4", intf, &net.UDPAddr{IP: ssdp.IP})
2014-04-18 11:20:42 +00:00
if err != nil {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln(err)
return
2014-04-18 11:20:42 +00:00
}
defer socket.Close() // Make sure our socket gets closed
2014-04-18 11:20:42 +00:00
2015-04-15 23:34:27 +00:00
err = socket.SetDeadline(time.Now().Add(timeout))
2014-04-18 11:20:42 +00:00
if err != nil {
l.Infoln(err)
return
2014-04-18 11:20:42 +00:00
}
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("Sending search request for device type", deviceType, "on", intf.Name)
2014-04-18 11:20:42 +00:00
_, err = socket.WriteTo(search, ssdp)
if err != nil {
l.Infoln(err)
return
}
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("Listening for UPnP response for device type", deviceType, "on", intf.Name)
// Listen for responses until a timeout is reached
for {
resp := make([]byte, 65536)
n, _, err := socket.ReadFrom(resp)
if err != nil {
if e, ok := err.(net.Error); !ok || !e.Timeout() {
2015-04-16 14:40:01 +00:00
l.Infoln("UPnP read:", err) //legitimate error, not a timeout.
}
break
}
igd, err := parseResponse(deviceType, resp[:n])
if err != nil {
2015-04-16 14:40:01 +00:00
l.Infoln("UPnP parse:", err)
continue
}
results <- igd
}
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("Discovery for device type", deviceType, "on", intf.Name, "finished.")
}
func parseResponse(deviceType string, resp []byte) (IGD, error) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("Handling UPnP response:\n\n" + string(resp))
reader := bufio.NewReader(bytes.NewBuffer(resp))
2014-04-18 11:20:42 +00:00
request := &http.Request{}
response, err := http.ReadResponse(reader, request)
if err != nil {
return IGD{}, err
}
2014-04-18 11:20:42 +00:00
respondingDeviceType := response.Header.Get("St")
if respondingDeviceType != deviceType {
return IGD{}, errors.New("unrecognized UPnP device of type " + respondingDeviceType)
2014-04-18 11:20:42 +00:00
}
deviceDescriptionLocation := response.Header.Get("Location")
if deviceDescriptionLocation == "" {
2015-04-28 20:32:10 +00:00
return IGD{}, errors.New("invalid IGD response: no location specified")
2014-04-18 11:20:42 +00:00
}
deviceDescriptionURL, err := url.Parse(deviceDescriptionLocation)
2014-04-18 11:20:42 +00:00
if err != nil {
l.Infoln("Invalid IGD location: " + err.Error())
2014-04-18 11:20:42 +00:00
}
deviceUSN := response.Header.Get("USN")
if deviceUSN == "" {
2015-04-28 20:32:10 +00:00
return IGD{}, errors.New("invalid IGD response: USN not specified")
}
deviceUUID := strings.TrimPrefix(strings.Split(deviceUSN, "::")[0], "uuid:")
matched, err := regexp.MatchString("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}", deviceUUID)
if !matched {
l.Infoln("Invalid IGD response: invalid device UUID", deviceUUID, "(continuing anyway)")
}
response, err = http.Get(deviceDescriptionLocation)
if err != nil {
return IGD{}, err
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return IGD{}, errors.New("bad status code:" + response.Status)
}
var upnpRoot upnpRoot
err = xml.NewDecoder(response.Body).Decode(&upnpRoot)
if err != nil {
return IGD{}, err
}
services, err := getServiceDescriptions(deviceDescriptionLocation, upnpRoot.Device)
if err != nil {
return IGD{}, err
}
// Figure out our IP number, on the network used to reach the IGD.
// We do this in a fairly roundabout way by connecting to the IGD and
2014-04-18 11:20:42 +00:00
// 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)
2014-04-18 11:20:42 +00:00
if err != nil {
return IGD{}, err
2014-04-18 11:20:42 +00:00
}
return IGD{
uuid: deviceUUID,
friendlyName: upnpRoot.Device.FriendlyName,
url: deviceDescriptionURL,
services: services,
localIPAddress: localIPAddress,
}, nil
}
2014-04-18 11:20:42 +00:00
func localIP(url *url.URL) (string, error) {
2015-10-12 18:30:14 +00:00
conn, err := dialer.Dial("tcp", url.Host)
2014-04-18 11:20:42 +00:00
if err != nil {
return "", err
}
defer conn.Close()
localIPAddress, _, err := net.SplitHostPort(conn.LocalAddr().String())
2014-04-18 11:20:42 +00:00
if err != nil {
return "", err
}
return localIPAddress, nil
2014-04-18 11:20:42 +00:00
}
func getChildDevices(d upnpDevice, deviceType string) []upnpDevice {
var result []upnpDevice
2014-04-18 11:20:42 +00:00
for _, dev := range d.Devices {
if dev.DeviceType == deviceType {
result = append(result, dev)
2014-04-18 11:20:42 +00:00
}
}
return result
2014-04-18 11:20:42 +00:00
}
func getChildServices(d upnpDevice, serviceType string) []upnpService {
var result []upnpService
2015-12-23 15:31:12 +00:00
for _, service := range d.Services {
if service.Type == serviceType {
result = append(result, service)
2014-04-18 11:20:42 +00:00
}
}
return result
2014-04-18 11:20:42 +00:00
}
func getServiceDescriptions(rootURL string, device upnpDevice) ([]IGDService, error) {
var result []IGDService
if device.DeviceType == "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
descriptions := getIGDServices(rootURL, device,
"urn:schemas-upnp-org:device:WANDevice:1",
"urn:schemas-upnp-org:device:WANConnectionDevice:1",
[]string{"urn:schemas-upnp-org:service:WANIPConnection:1", "urn:schemas-upnp-org:service:WANPPPConnection:1"})
result = append(result, descriptions...)
} else if device.DeviceType == "urn:schemas-upnp-org:device:InternetGatewayDevice:2" {
descriptions := getIGDServices(rootURL, device,
"urn:schemas-upnp-org:device:WANDevice:2",
"urn:schemas-upnp-org:device:WANConnectionDevice:2",
[]string{"urn:schemas-upnp-org:service:WANIPConnection:2", "urn:schemas-upnp-org:service:WANPPPConnection:2"})
result = append(result, descriptions...)
} else {
return result, errors.New("[" + rootURL + "] Malformed root device description: not an InternetGatewayDevice.")
2014-04-18 11:20:42 +00:00
}
if len(result) < 1 {
return result, errors.New("[" + rootURL + "] Malformed device description: no compatible service descriptions found.")
2014-04-18 11:20:42 +00:00
}
2015-04-28 20:32:10 +00:00
return result, nil
}
2014-04-18 11:20:42 +00:00
2015-12-23 15:31:12 +00:00
func getIGDServices(rootURL string, device upnpDevice, wanDeviceURN string, wanConnectionURN string, URNs []string) []IGDService {
var result []IGDService
2014-04-18 11:20:42 +00:00
devices := getChildDevices(device, wanDeviceURN)
2014-04-18 11:20:42 +00:00
if len(devices) < 1 {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Infoln(rootURL, "- malformed InternetGatewayDevice description: no WANDevices specified.")
return result
2014-04-18 11:20:42 +00:00
}
for _, device := range devices {
connections := getChildDevices(device, wanConnectionURN)
2014-04-18 11:20:42 +00:00
if len(connections) < 1 {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Infoln(rootURL, "- malformed ", wanDeviceURN, "description: no WANConnectionDevices specified.")
}
2014-04-18 11:20:42 +00:00
for _, connection := range connections {
2015-12-23 15:31:12 +00:00
for _, URN := range URNs {
services := getChildServices(connection, URN)
2015-12-23 15:31:12 +00:00
l.Debugln(rootURL, "- no services of type", URN, " found on connection.")
for _, service := range services {
if len(service.ControlURL) == 0 {
2015-12-23 15:31:12 +00:00
l.Infoln(rootURL+"- malformed", service.Type, "description: no control URL.")
} else {
u, _ := url.Parse(rootURL)
replaceRawPath(u, service.ControlURL)
2015-12-23 15:31:12 +00:00
l.Debugln(rootURL, "- found", service.Type, "with URL", u)
2015-12-23 15:31:12 +00:00
service := IGDService{ID: service.ID, URL: u.String(), URN: service.Type}
result = append(result, service)
}
}
}
}
2014-04-18 11:20:42 +00:00
}
return result
}
func replaceRawPath(u *url.URL, rp string) {
asURL, err := url.Parse(rp)
if err != nil {
return
} else if asURL.IsAbs() {
u.Path = asURL.Path
u.RawQuery = asURL.RawQuery
2014-04-18 11:20:42 +00:00
} else {
var p, q string
fs := strings.Split(rp, "?")
p = fs[0]
if len(fs) > 1 {
q = fs[1]
}
if p[0] == '/' {
u.Path = p
} else {
u.Path += p
}
u.RawQuery = q
2014-04-18 11:20:42 +00:00
}
}
2014-10-19 17:38:12 +00:00
func soapRequest(url, service, function, message string) ([]byte, error) {
tpl := `<?xml version="1.0" ?>
2014-04-18 11:20:42 +00:00
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>%s</s:Body>
</s:Envelope>
`
var resp []byte
2014-04-18 11:20:42 +00:00
body := fmt.Sprintf(tpl, message)
req, err := http.NewRequest("POST", url, strings.NewReader(body))
if err != nil {
return resp, err
2014-04-18 11:20:42 +00:00
}
req.Close = true
2014-04-18 11:20:42 +00:00
req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
req.Header.Set("User-Agent", "syncthing/1.0")
2015-04-26 10:37:37 +00:00
req.Header["SOAPAction"] = []string{fmt.Sprintf(`"%s#%s"`, service, function)} // Enforce capitalization in header-entry for sensitive routers. See issue #1696
2014-04-18 11:20:42 +00:00
req.Header.Set("Connection", "Close")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln("SOAP Request URL: " + url)
l.Debugln("SOAP Action: " + req.Header.Get("SOAPAction"))
l.Debugln("SOAP Request:\n\n" + body)
2014-04-18 11:20:42 +00:00
r, err := http.DefaultClient.Do(req)
if err != nil {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugln(err)
return resp, err
2014-04-18 11:20:42 +00:00
}
resp, _ = ioutil.ReadAll(r.Body)
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 15:25:21 +00:00
l.Debugf("SOAP Response: %s\n\n%s\n\n", r.Status, resp)
2014-04-18 11:20:42 +00:00
r.Body.Close()
if r.StatusCode >= 400 {
return resp, errors.New(function + ": " + r.Status)
2014-04-18 11:20:42 +00:00
}
return resp, nil
2014-04-18 11:20:42 +00:00
}
2015-04-28 20:32:10 +00:00
// AddPortMapping adds a port mapping to all relevant services on the
// specified InternetGatewayDevice. Port mapping will fail and return an error
// if action is fails for _any_ of the relevant services. For this reason, it
// is generally better to configure port mapping for each individual service
// instead.
2014-04-18 11:20:42 +00:00
func (n *IGD) AddPortMapping(protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
for _, service := range n.services {
err := service.AddPortMapping(n.localIPAddress, protocol, externalPort, internalPort, description, timeout)
if err != nil {
return err
}
}
return nil
2014-04-18 11:20:42 +00:00
}
2015-04-28 20:32:10 +00:00
// DeletePortMapping deletes a port mapping from all relevant services on the
// specified InternetGatewayDevice. Port mapping will fail and return an error
// if action is fails for _any_ of the relevant services. For this reason, it
// is generally better to configure port mapping for each individual service
// instead.
func (n *IGD) DeletePortMapping(protocol Protocol, externalPort int) error {
for _, service := range n.services {
err := service.DeletePortMapping(protocol, externalPort)
if err != nil {
return err
}
}
return nil
}
type soapGetExternalIPAddressResponseEnvelope struct {
XMLName xml.Name
Body soapGetExternalIPAddressResponseBody `xml:"Body"`
}
type soapGetExternalIPAddressResponseBody struct {
XMLName xml.Name
GetExternalIPAddressResponse getExternalIPAddressResponse `xml:"GetExternalIPAddressResponse"`
}
type getExternalIPAddressResponse struct {
NewExternalIPAddress string `xml:"NewExternalIPAddress"`
}
type soapErrorResponse struct {
ErrorCode int `xml:"Body>Fault>detail>UPnPError>errorCode"`
ErrorDescription string `xml:"Body>Fault>detail>UPnPError>errorDescription"`
}
2015-04-28 20:32:10 +00:00
// AddPortMapping adds a port mapping to the specified IGD service.
func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
tpl := `<u:AddPortMapping xmlns:u="%s">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>%d</NewExternalPort>
<NewProtocol>%s</NewProtocol>
<NewInternalPort>%d</NewInternalPort>
<NewInternalClient>%s</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>%s</NewPortMappingDescription>
<NewLeaseDuration>%d</NewLeaseDuration>
</u:AddPortMapping>`
2015-12-23 15:31:12 +00:00
body := fmt.Sprintf(tpl, s.URN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
2015-12-23 15:31:12 +00:00
response, err := soapRequest(s.URL, s.URN, "AddPortMapping", body)
if err != nil && timeout > 0 {
// Try to repair error code 725 - OnlyPermanentLeasesSupported
envelope := &soapErrorResponse{}
2015-12-13 09:38:21 +00:00
if unmarshalErr := xml.Unmarshal(response, envelope); unmarshalErr != nil {
return unmarshalErr
}
if envelope.ErrorCode == 725 {
return s.AddPortMapping(localIPAddress, protocol, externalPort, internalPort, description, 0)
}
}
return err
}
2015-04-28 20:32:10 +00:00
// DeletePortMapping deletes a port mapping from the specified IGD service.
func (s *IGDService) DeletePortMapping(protocol Protocol, externalPort int) error {
tpl := `<u:DeletePortMapping xmlns:u="%s">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>%d</NewExternalPort>
<NewProtocol>%s</NewProtocol>
</u:DeletePortMapping>`
2015-12-23 15:31:12 +00:00
body := fmt.Sprintf(tpl, s.URN, externalPort, protocol)
2015-12-23 15:31:12 +00:00
_, err := soapRequest(s.URL, s.URN, "DeletePortMapping", body)
if err != nil {
return err
}
return nil
}
2015-04-28 20:32:10 +00:00
// GetExternalIPAddress queries the IGD service for its external IP address.
// Returns nil if the external IP address is invalid or undefined, along with
// any relevant errors
func (s *IGDService) GetExternalIPAddress() (net.IP, error) {
tpl := `<u:GetExternalIPAddress xmlns:u="%s" />`
2015-12-23 15:31:12 +00:00
body := fmt.Sprintf(tpl, s.URN)
2015-12-23 15:31:12 +00:00
response, err := soapRequest(s.URL, s.URN, "GetExternalIPAddress", body)
if err != nil {
return nil, err
}
envelope := &soapGetExternalIPAddressResponseEnvelope{}
err = xml.Unmarshal(response, envelope)
if err != nil {
return nil, err
}
result := net.ParseIP(envelope.Body.GetExternalIPAddressResponse.NewExternalIPAddress)
return result, nil
}