UPnP API for querying of services' external IP address

This commit is contained in:
Caleb Callaway 2014-10-17 20:30:47 -07:00
parent 75d5e74059
commit 85677eaf1a
2 changed files with 101 additions and 17 deletions

View File

@ -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 := ` <?xml version="1.0" ?>
<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
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,
</u:AddPortMapping>`
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)
</u:DeletePortMapping>`
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 := `<u:GetExternalIPAddress xmlns:u="%s" />`
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
package upnp
import (
"encoding/xml"
"testing"
)
func TestExternalIPParsing(t *testing.T) {
soap_response :=
[]byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewExternalIPAddress>1.2.3.4</NewExternalIPAddress>
</u:GetExternalIPAddressResponse>
</s:Body>
</s:Envelope>`)
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.")
}
}