663d4e0aff
The test listens for multicast UDP packets on the default interface because I couldn't get it to work reliably on loopback without massive changes to goupnp. This means that the test might fail when there is a UPnP-enabled router attached on that interface. I checked that locally by looping the test and it passes reliably because the local SSDP server always responds faster.
224 lines
7.9 KiB
Go
224 lines
7.9 KiB
Go
package nat
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/huin/goupnp/httpu"
|
|
)
|
|
|
|
func TestUPNP_DDWRT(t *testing.T) {
|
|
dev := &fakeIGD{
|
|
t: t,
|
|
ssdpResp: "HTTP/1.1 200 OK\r\n" +
|
|
"Cache-Control: max-age=300\r\n" +
|
|
"Date: Sun, 10 May 2015 10:05:33 GMT\r\n" +
|
|
"Ext: \r\n" +
|
|
"Location: http://{{listenAddr}}/InternetGatewayDevice.xml\r\n" +
|
|
"Server: POSIX UPnP/1.0 DD-WRT Linux/V24\r\n" +
|
|
"ST: urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
|
|
"USN: uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800::urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
|
|
"\r\n",
|
|
httpResps: map[string]string{
|
|
"GET /InternetGatewayDevice.xml": `
|
|
<?xml version="1.0"?>
|
|
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
|
<specVersion>
|
|
<major>1</major>
|
|
<minor>0</minor>
|
|
</specVersion>
|
|
<device>
|
|
<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
|
|
<manufacturer>DD-WRT</manufacturer>
|
|
<manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
|
|
<modelDescription>Gateway</modelDescription>
|
|
<friendlyName>Asus RT-N16:DD-WRT</friendlyName>
|
|
<modelName>Asus RT-N16</modelName>
|
|
<modelNumber>V24</modelNumber>
|
|
<serialNumber>0000001</serialNumber>
|
|
<modelURL>http://www.dd-wrt.com</modelURL>
|
|
<UDN>uuid:A13AB4C3-3A14-E386-DE6A-EFEA923A06FE</UDN>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>
|
|
<SCPDURL>/x_layer3forwarding.xml</SCPDURL>
|
|
<controlURL>/control?Layer3Forwarding</controlURL>
|
|
<eventSubURL>/event?Layer3Forwarding</eventSubURL>
|
|
</service>
|
|
</serviceList>
|
|
<deviceList>
|
|
<device>
|
|
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
|
|
<friendlyName>WANDevice</friendlyName>
|
|
<manufacturer>DD-WRT</manufacturer>
|
|
<manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
|
|
<modelDescription>Gateway</modelDescription>
|
|
<modelName>router</modelName>
|
|
<modelURL>http://www.dd-wrt.com</modelURL>
|
|
<UDN>uuid:48FD569B-F9A9-96AE-4EE6-EB403D3DB91A</UDN>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
|
|
<SCPDURL>/x_wancommoninterfaceconfig.xml</SCPDURL>
|
|
<controlURL>/control?WANCommonInterfaceConfig</controlURL>
|
|
<eventSubURL>/event?WANCommonInterfaceConfig</eventSubURL>
|
|
</service>
|
|
</serviceList>
|
|
<deviceList>
|
|
<device>
|
|
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
|
|
<friendlyName>WAN Connection Device</friendlyName>
|
|
<manufacturer>DD-WRT</manufacturer>
|
|
<manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
|
|
<modelDescription>Gateway</modelDescription>
|
|
<modelName>router</modelName>
|
|
<modelURL>http://www.dd-wrt.com</modelURL>
|
|
<UDN>uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800</UDN>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
|
|
<SCPDURL>/x_wanipconnection.xml</SCPDURL>
|
|
<controlURL>/control?WANIPConnection</controlURL>
|
|
<eventSubURL>/event?WANIPConnection</eventSubURL>
|
|
</service>
|
|
</serviceList>
|
|
</device>
|
|
</deviceList>
|
|
</device>
|
|
<device>
|
|
<deviceType>urn:schemas-upnp-org:device:LANDevice:1</deviceType>
|
|
<friendlyName>LANDevice</friendlyName>
|
|
<manufacturer>DD-WRT</manufacturer>
|
|
<manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
|
|
<modelDescription>Gateway</modelDescription>
|
|
<modelName>router</modelName>
|
|
<modelURL>http://www.dd-wrt.com</modelURL>
|
|
<UDN>uuid:04021998-3B35-2BDB-7B3C-99DA4435DA09</UDN>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:LANHostConfigManagement:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:LANHostCfg1</serviceId>
|
|
<SCPDURL>/x_lanhostconfigmanagement.xml</SCPDURL>
|
|
<controlURL>/control?LANHostConfigManagement</controlURL>
|
|
<eventSubURL>/event?LANHostConfigManagement</eventSubURL>
|
|
</service>
|
|
</serviceList>
|
|
</device>
|
|
</deviceList>
|
|
<presentationURL>http://{{listenAddr}}</presentationURL>
|
|
</device>
|
|
</root>
|
|
`,
|
|
// The response to our GetNATRSIPStatus call. This
|
|
// particular implementation has a bug where the elements
|
|
// inside u:GetNATRSIPStatusResponse are not properly
|
|
// namespaced.
|
|
"POST /control?WANIPConnection": `
|
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
<s:Body>
|
|
<u:GetNATRSIPStatusResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
|
|
<NewRSIPAvailable>0</NewRSIPAvailable>
|
|
<NewNATEnabled>1</NewNATEnabled>
|
|
</u:GetNATRSIPStatusResponse>
|
|
</s:Body>
|
|
</s:Envelope>
|
|
`,
|
|
},
|
|
}
|
|
if err := dev.listen(); err != nil {
|
|
t.Skipf("cannot listen: %v", err)
|
|
}
|
|
dev.serve()
|
|
defer dev.close()
|
|
|
|
// Attempt to discover the fake device.
|
|
discovered := discoverUPnP()
|
|
if discovered == nil {
|
|
t.Fatalf("not discovered")
|
|
}
|
|
upnp, _ := discovered.(*upnp)
|
|
if upnp.service != "IGDv1-IP1" {
|
|
t.Errorf("upnp.service mismatch: got %q, want %q", upnp.service, "IGDv1-IP1")
|
|
}
|
|
wantURL := "http://" + dev.listener.Addr().String() + "/InternetGatewayDevice.xml"
|
|
if upnp.dev.URLBaseStr != wantURL {
|
|
t.Errorf("upnp.dev.URLBaseStr mismatch: got %q, want %q", upnp.dev.URLBaseStr, wantURL)
|
|
}
|
|
}
|
|
|
|
// fakeIGD presents itself as a discoverable UPnP device which sends
|
|
// canned responses to HTTPU and HTTP requests.
|
|
type fakeIGD struct {
|
|
t *testing.T // for logging
|
|
|
|
listener net.Listener
|
|
mcastListener *net.UDPConn
|
|
|
|
// This should be a complete HTTP response (including headers).
|
|
// It is sent as the response to any sspd packet. Any occurrence
|
|
// of "{{listenAddr}}" is replaced with the actual TCP listen
|
|
// address of the HTTP server.
|
|
ssdpResp string
|
|
// This one should contain XML payloads for all requests
|
|
// performed. The keys contain method and path, e.g. "GET /foo/bar".
|
|
// As with ssdpResp, "{{listenAddr}}" is replaced with the TCP
|
|
// listen address.
|
|
httpResps map[string]string
|
|
}
|
|
|
|
// httpu.Handler
|
|
func (dev *fakeIGD) ServeMessage(r *http.Request) {
|
|
dev.t.Logf(`HTTPU request %s %s`, r.Method, r.RequestURI)
|
|
conn, err := net.Dial("udp4", r.RemoteAddr)
|
|
if err != nil {
|
|
fmt.Printf("reply Dial error: %v", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
io.WriteString(conn, dev.replaceListenAddr(dev.ssdpResp))
|
|
}
|
|
|
|
// http.Handler
|
|
func (dev *fakeIGD) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if resp, ok := dev.httpResps[r.Method+" "+r.RequestURI]; ok {
|
|
dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 200)
|
|
io.WriteString(w, dev.replaceListenAddr(resp))
|
|
} else {
|
|
dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 404)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}
|
|
|
|
func (dev *fakeIGD) replaceListenAddr(resp string) string {
|
|
return strings.Replace(resp, "{{listenAddr}}", dev.listener.Addr().String(), -1)
|
|
}
|
|
|
|
func (dev *fakeIGD) listen() (err error) {
|
|
if dev.listener, err = net.Listen("tcp", "127.0.0.1:0"); err != nil {
|
|
return err
|
|
}
|
|
laddr := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 1900}
|
|
if dev.mcastListener, err = net.ListenMulticastUDP("udp", nil, laddr); err != nil {
|
|
dev.listener.Close()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (dev *fakeIGD) serve() {
|
|
go httpu.Serve(dev.mcastListener, dev)
|
|
go http.Serve(dev.listener, dev)
|
|
}
|
|
|
|
func (dev *fakeIGD) close() {
|
|
dev.mcastListener.Close()
|
|
dev.listener.Close()
|
|
}
|