diff --git a/internal/upnp/upnp.go b/internal/upnp/upnp.go
index 48ccefba1..a6780bce2 100644
--- a/internal/upnp/upnp.go
+++ b/internal/upnp/upnp.go
@@ -39,13 +39,13 @@ import (
type IGD struct {
uuid string
friendlyName string
- services []IGDServiceDescription
+ services []IGDService
url *url.URL
localIPAddress string
}
// A container for relevant properties of a UPnP service of an IGD.
-type IGDServiceDescription struct {
+type IGDService struct {
serviceURL string
serviceURN string
}
@@ -335,18 +335,18 @@ func getChildServices(d upnpDevice, serviceType string) []upnpService {
return result
}
-func getServiceDescriptions(rootURL string, device upnpDevice) ([]IGDServiceDescription, error) {
- result := make([]IGDServiceDescription, 0)
+func getServiceDescriptions(rootURL string, device upnpDevice) ([]IGDService, error) {
+ result := make([]IGDService, 0)
if device.DeviceType == "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
- descriptions := getIGDServiceDescriptions(rootURL, device,
+ 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 := getIGDServiceDescriptions(rootURL, device,
+ 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:1"})
@@ -363,8 +363,8 @@ func getServiceDescriptions(rootURL string, device upnpDevice) ([]IGDServiceDesc
}
}
-func getIGDServiceDescriptions(rootURL string, device upnpDevice, wanDeviceURN string, wanConnectionURN string, serviceURNs []string) []IGDServiceDescription {
- result := make([]IGDServiceDescription, 0)
+func getIGDServices(rootURL string, device upnpDevice, wanDeviceURN string, wanConnectionURN string, serviceURNs []string) []IGDService {
+ result := make([]IGDService, 0)
devices := getChildDevices(device, wanDeviceURN)
@@ -399,7 +399,9 @@ func getIGDServiceDescriptions(rootURL string, device upnpDevice, wanDeviceURN s
l.Debugln("[" + rootURL + "] Found " + service.ServiceType + " with URL " + u.String())
}
- result = append(result, IGDServiceDescription{serviceURL: u.String(), serviceURN: service.ServiceType})
+ service := IGDService{serviceURL: u.String(), serviceURN: service.ServiceType}
+
+ result = append(result, service)
}
}
}
@@ -425,17 +427,19 @@ func replaceRawPath(u *url.URL, rp string) {
u.RawQuery = q
}
-func soapRequest(url, device, function, message string) error {
+func soapRequest(url, device, function, message string) ([]byte, error) {
tpl := `
%s
`
+ var resp []byte
+
body := fmt.Sprintf(tpl, message)
req, err := http.NewRequest("POST", url, strings.NewReader(body))
if err != nil {
- return err
+ return resp, err
}
req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
req.Header.Set("User-Agent", "syncthing/1.0")
@@ -451,21 +455,21 @@ func soapRequest(url, device, function, message string) error {
r, err := http.DefaultClient.Do(req)
if err != nil {
- return err
+ return resp, err
}
+ resp, _ = ioutil.ReadAll(r.Body)
if debug {
- resp, _ := ioutil.ReadAll(r.Body)
l.Debugln("SOAP Response:\n\n" + string(resp) + "\n")
}
r.Body.Close()
if r.StatusCode >= 400 {
- return errors.New(function + ": " + r.Status)
+ return resp, errors.New(function + ": " + r.Status)
}
- return nil
+ return resp, nil
}
// Add a port mapping to the specified InternetGatewayDevice.
@@ -483,7 +487,7 @@ func (n *IGD) AddPortMapping(protocol Protocol, externalPort, internalPort int,
`
body := fmt.Sprintf(tpl, service.serviceURN, externalPort, protocol, internalPort, n.localIPAddress, description, timeout)
- err := soapRequest(service.serviceURL, service.serviceURN, "AddPortMapping", body)
+ _, err := soapRequest(service.serviceURL, service.serviceURN, "AddPortMapping", body)
if err != nil {
return err
}
@@ -501,7 +505,7 @@ func (n *IGD) DeletePortMapping(protocol Protocol, externalPort int) (err error)
`
body := fmt.Sprintf(tpl, service.serviceURN, externalPort, protocol)
- err := soapRequest(service.serviceURL, service.serviceURN, "DeletePortMapping", body)
+ _, err := soapRequest(service.serviceURL, service.serviceURN, "DeletePortMapping", body)
if err != nil {
return err
}
@@ -528,3 +532,41 @@ func (n *IGD) FriendlyIdentifier() string {
func (n *IGD) URL() *url.URL {
return n.url
}
+
+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"`
+}
+
+// Query 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 := ``
+
+ body := fmt.Sprintf(tpl, s.serviceURN)
+
+ response, err := soapRequest(s.serviceURL, s.serviceURN, "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
+}
diff --git a/internal/upnp/upnp_test.go b/internal/upnp/upnp_test.go
new file mode 100644
index 000000000..e3bf6becd
--- /dev/null
+++ b/internal/upnp/upnp_test.go
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see .
+
+package upnp
+
+import (
+ "encoding/xml"
+ "testing"
+)
+
+func TestExternalIPParsing(t *testing.T) {
+ soap_response :=
+ []byte(`
+
+
+ 1.2.3.4
+
+
+ `)
+
+ envelope := &soapGetExternalIPAddressResponseEnvelope{}
+ err := xml.Unmarshal(soap_response, envelope)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if envelope.Body.GetExternalIPAddressResponse.NewExternalIPAddress != "1.2.3.4" {
+ t.Error("Parse of SOAP request failed.")
+ }
+}