// Copyright 2015 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package nat import ( "fmt" "io" "net" "net/http" "runtime" "strings" "testing" "github.com/huin/goupnp/httpu" ) func TestUPNP_DDWRT(t *testing.T) { if runtime.GOOS == "windows" { t.Skipf("disabled to avoid firewall prompt") } 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() }