mirror of https://github.com/ethereum/go-ethereum
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.pull/960/head
parent
983f5a717a
commit
663d4e0aff
@ -0,0 +1,223 @@ |
||||
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() |
||||
} |
Loading…
Reference in new issue