diff --git a/cmd/discoverigd/discoverigd.go b/cmd/discoverigd/discoverigd.go index da44bc2..4f2274f 100644 --- a/cmd/discoverigd/discoverigd.go +++ b/cmd/discoverigd/discoverigd.go @@ -19,7 +19,16 @@ func (i indentLevel) String() string { func displayDevice(indent indentLevel, device *goupnp.Device) { fmt.Println(indent.String(), device) for _, srv := range device.Services { - fmt.Println((indent + 1).String(), srv) + fmt.Println((indent + 1).String(), srv, srv.SCPDURL.URL.String(), srv.ControlURL.URL.String()) + fmt.Println(goupnp.ServiceTypeWANPPPConnection, srv.ServiceType) + if srv.ServiceType == goupnp.ServiceTypeWANPPPConnection { + results, err := goupnp.PerformSoapAction(goupnp.ServiceTypeWANPPPConnection, "GetExternalIPAddress", &srv.ControlURL.URL, nil) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(results) + } + } } for _, subdev := range device.Devices { displayDevice(indent+1, subdev) diff --git a/goupnp.go b/goupnp.go index a0d14a5..57fa359 100644 --- a/goupnp.go +++ b/goupnp.go @@ -11,11 +11,11 @@ import ( // Non-exhaustive set of UPnP service types. const ( ServiceTypeLayer3Forwarding = "urn:schemas-upnp-org:service:Layer3Forwarding:1" - ServiceTypeWANCommonInterfaceConfig = "urn:schemas-upnp-org:WANCommonInterfaceConfig:1" + ServiceTypeWANCommonInterfaceConfig = "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" // WANPPPConnection is typically useful with regard to the external IP and // port forwarding. // http://upnp.org/specs/gw/UPnP-gw-WANPPPConnection-v1-Service.pdf - ServiceTypeWANPPPConnection = "urn:schemas-upnp-org:WANPPPConnection:1" + ServiceTypeWANPPPConnection = "urn:schemas-upnp-org:service:WANPPPConnection:1" ) // Non-exhaustive set of UPnP device types. diff --git a/soap.go b/soap.go new file mode 100644 index 0000000..9326832 --- /dev/null +++ b/soap.go @@ -0,0 +1,107 @@ +// Definition for the SOAP structure required for UPnP's SOAP usage. + +package goupnp + +import ( + "bytes" + "encoding/xml" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + SoapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" +) + +type NameValue struct { + Name string + Value string +} + +// NewSoapAction creates a SoapEnvelope with the given action and arguments. +func newSoapAction(actionNamespace, actionName string, arguments []NameValue) *SoapEnvelope { + env := &SoapEnvelope{ + EncodingStyle: SoapEncodingStyle, + Body: SoapBody{ + Action: SoapAction{ + XMLName: xml.Name{actionNamespace, actionName}, + Arguments: make([]SoapArgument, len(arguments)), + }, + }, + } + + for i := range arguments { + env.Body.Action.Arguments[i].XMLName.Local = arguments[i].Name + env.Body.Action.Arguments[i].Value = arguments[i].Value + } + + return env +} + +// PerformSoapAction makes a SOAP request, with the given action. +func PerformSoapAction(actionNamespace, actionName string, url *url.URL, arguments []NameValue) ([]NameValue, error) { + requestEnv := newSoapAction(actionNamespace, actionName, arguments) + requestBytes, err := xml.Marshal(requestEnv) + if err != nil { + return nil, err + } + + client := http.Client{} + response, err := client.Do(&http.Request{ + Method: "POST", + URL: url, + Header: http.Header{ + "SOAPACTION": []string{actionNamespace + "#" + actionName}, + "CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""}, + }, + Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)), + // Set ContentLength to avoid chunked encoding - some servers might not support it. + ContentLength: int64(len(requestBytes)), + }) + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != 200 { + return nil, fmt.Errorf("goupnp: SOAP request got %s response from %s", response.Status, url.String()) + } + + decoder := xml.NewDecoder(response.Body) + fmt.Println(response.Header) + var responseEnv SoapEnvelope + if err := decoder.Decode(&responseEnv); err != nil { + return nil, err + } + + results := make([]NameValue, len(responseEnv.Body.Action.Arguments)) + for i, soapArg := range responseEnv.Body.Action.Arguments { + results[i] = NameValue{ + Name: soapArg.XMLName.Local, + Value: soapArg.Value, + } + } + + return results, nil +} + +type SoapEnvelope struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"` + Body SoapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` +} + +type SoapBody struct { + Action SoapAction `xml:",any"` +} + +type SoapAction struct { + XMLName xml.Name + Arguments []SoapArgument `xml:",any"` +} + +type SoapArgument struct { + XMLName xml.Name + Value string `xml:",chardata"` +}