114 lines
2.6 KiB
Go
114 lines
2.6 KiB
Go
|
package goupnp
|
||
|
|
||
|
import (
|
||
|
"encoding/xml"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"io"
|
||
|
"os"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
ssdpUDP4Addr = "239.255.255.250:1900"
|
||
|
|
||
|
methodSearch = "M-SEARCH"
|
||
|
// Search Target for InternetGatewayDevice.
|
||
|
stIgd = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
||
|
hdrMan = `"ssdp:discover"`
|
||
|
)
|
||
|
|
||
|
// DiscoverIGD attempts to find Internet Gateway Devices.
|
||
|
//
|
||
|
// TODO: Fix implementation to discover multiple. Currently it will find a
|
||
|
// maximum of one.
|
||
|
func DiscoverIGD() ([]IGD, error) {
|
||
|
hc := http.Client{
|
||
|
Transport: udpRoundTripper{},
|
||
|
CheckRedirect: func(r *http.Request, via []*http.Request) error {
|
||
|
return errors.New("goupnp: unexpected HTTP redirect")
|
||
|
},
|
||
|
Jar: nil,
|
||
|
}
|
||
|
|
||
|
request := http.Request{
|
||
|
Method: methodSearch,
|
||
|
// TODO: Support both IPv4 and IPv6.
|
||
|
Host: ssdpUDP4Addr,
|
||
|
URL: &url.URL{Opaque: "*"},
|
||
|
Header: http.Header{
|
||
|
// Putting headers in here avoids them being title-cased.
|
||
|
// (The UPnP discovery protocol uses case-sensitive headers)
|
||
|
"HOST": []string{ssdpUDP4Addr},
|
||
|
"MX": []string{"2"}, // TODO: Variable max wait time.
|
||
|
"MAN": []string{hdrMan},
|
||
|
"ST": []string{stIgd},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
response, err := hc.Do(&request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Any errors past this point are simply "no result found". We log the
|
||
|
// errors, but report no results. In a future version of this implementation,
|
||
|
// multiple *good* results can be returned.
|
||
|
|
||
|
if response.StatusCode != 200 {
|
||
|
log.Printf("goupnp: response code %d %q from UPnP discovery",
|
||
|
response.StatusCode, response.Status)
|
||
|
return nil, nil
|
||
|
}
|
||
|
if st := response.Header.Get("ST"); st != stIgd {
|
||
|
log.Printf("goupnp: got unexpected search target result %q", st)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
location, err := response.Location()
|
||
|
if err != nil {
|
||
|
log.Printf("goupnp: missing location in response")
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
igd, err := requestIgd(location.String())
|
||
|
if err != nil {
|
||
|
log.Printf("goupnp: error requesting IGD: %v", err)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
return []IGD{igd}, nil
|
||
|
}
|
||
|
|
||
|
// IGD defines the interface for an Internet Gateway Device.
|
||
|
type IGD interface {
|
||
|
}
|
||
|
|
||
|
type igd struct {
|
||
|
serviceUrl string
|
||
|
}
|
||
|
|
||
|
func requestIgd(serviceUrl string) (IGD, error) {
|
||
|
resp, err := http.Get(serviceUrl)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
decoder := xml.NewDecoder(io.TeeReader(resp.Body, os.Stdout))
|
||
|
decoder.DefaultSpace = deviceXmlNs
|
||
|
var root xmlRootDevice
|
||
|
if err = decoder.Decode(&root); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
log.Printf("%+v", root)
|
||
|
|
||
|
return igd{serviceUrl}, nil
|
||
|
}
|
||
|
|
||
|
func (device *igd) String() string {
|
||
|
return fmt.Sprintf("goupnp.IGD @ %s", device.serviceUrl)
|
||
|
}
|