natupnp_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Copyright 2015 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package nat
  17. import (
  18. "fmt"
  19. "io"
  20. "net"
  21. "net/http"
  22. "os"
  23. "runtime"
  24. "strings"
  25. "testing"
  26. "github.com/huin/goupnp/httpu"
  27. )
  28. func TestUPNP_DDWRT(t *testing.T) {
  29. if runtime.GOOS == "windows" {
  30. t.Skipf("disabled to avoid firewall prompt")
  31. }
  32. dev := &fakeIGD{
  33. t: t,
  34. ssdpResp: "HTTP/1.1 200 OK\r\n" +
  35. "Cache-Control: max-age=300\r\n" +
  36. "Date: Sun, 10 May 2015 10:05:33 GMT\r\n" +
  37. "Ext: \r\n" +
  38. "Location: http://{{listenAddr}}/InternetGatewayDevice.xml\r\n" +
  39. "Server: POSIX UPnP/1.0 DD-WRT Linux/V24\r\n" +
  40. "ST: urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
  41. "USN: uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800::urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
  42. "\r\n",
  43. httpResps: map[string]string{
  44. "GET /InternetGatewayDevice.xml": `
  45. <?xml version="1.0"?>
  46. <root xmlns="urn:schemas-upnp-org:device-1-0">
  47. <specVersion>
  48. <major>1</major>
  49. <minor>0</minor>
  50. </specVersion>
  51. <device>
  52. <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
  53. <manufacturer>DD-WRT</manufacturer>
  54. <manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
  55. <modelDescription>Gateway</modelDescription>
  56. <friendlyName>Asus RT-N16:DD-WRT</friendlyName>
  57. <modelName>Asus RT-N16</modelName>
  58. <modelNumber>V24</modelNumber>
  59. <serialNumber>0000001</serialNumber>
  60. <modelURL>http://www.dd-wrt.com</modelURL>
  61. <UDN>uuid:A13AB4C3-3A14-E386-DE6A-EFEA923A06FE</UDN>
  62. <serviceList>
  63. <service>
  64. <serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
  65. <serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>
  66. <SCPDURL>/x_layer3forwarding.xml</SCPDURL>
  67. <controlURL>/control?Layer3Forwarding</controlURL>
  68. <eventSubURL>/event?Layer3Forwarding</eventSubURL>
  69. </service>
  70. </serviceList>
  71. <deviceList>
  72. <device>
  73. <deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
  74. <friendlyName>WANDevice</friendlyName>
  75. <manufacturer>DD-WRT</manufacturer>
  76. <manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
  77. <modelDescription>Gateway</modelDescription>
  78. <modelName>router</modelName>
  79. <modelURL>http://www.dd-wrt.com</modelURL>
  80. <UDN>uuid:48FD569B-F9A9-96AE-4EE6-EB403D3DB91A</UDN>
  81. <serviceList>
  82. <service>
  83. <serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
  84. <serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
  85. <SCPDURL>/x_wancommoninterfaceconfig.xml</SCPDURL>
  86. <controlURL>/control?WANCommonInterfaceConfig</controlURL>
  87. <eventSubURL>/event?WANCommonInterfaceConfig</eventSubURL>
  88. </service>
  89. </serviceList>
  90. <deviceList>
  91. <device>
  92. <deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
  93. <friendlyName>WAN Connection Device</friendlyName>
  94. <manufacturer>DD-WRT</manufacturer>
  95. <manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
  96. <modelDescription>Gateway</modelDescription>
  97. <modelName>router</modelName>
  98. <modelURL>http://www.dd-wrt.com</modelURL>
  99. <UDN>uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800</UDN>
  100. <serviceList>
  101. <service>
  102. <serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
  103. <serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
  104. <SCPDURL>/x_wanipconnection.xml</SCPDURL>
  105. <controlURL>/control?WANIPConnection</controlURL>
  106. <eventSubURL>/event?WANIPConnection</eventSubURL>
  107. </service>
  108. </serviceList>
  109. </device>
  110. </deviceList>
  111. </device>
  112. <device>
  113. <deviceType>urn:schemas-upnp-org:device:LANDevice:1</deviceType>
  114. <friendlyName>LANDevice</friendlyName>
  115. <manufacturer>DD-WRT</manufacturer>
  116. <manufacturerURL>http://www.dd-wrt.com</manufacturerURL>
  117. <modelDescription>Gateway</modelDescription>
  118. <modelName>router</modelName>
  119. <modelURL>http://www.dd-wrt.com</modelURL>
  120. <UDN>uuid:04021998-3B35-2BDB-7B3C-99DA4435DA09</UDN>
  121. <serviceList>
  122. <service>
  123. <serviceType>urn:schemas-upnp-org:service:LANHostConfigManagement:1</serviceType>
  124. <serviceId>urn:upnp-org:serviceId:LANHostCfg1</serviceId>
  125. <SCPDURL>/x_lanhostconfigmanagement.xml</SCPDURL>
  126. <controlURL>/control?LANHostConfigManagement</controlURL>
  127. <eventSubURL>/event?LANHostConfigManagement</eventSubURL>
  128. </service>
  129. </serviceList>
  130. </device>
  131. </deviceList>
  132. <presentationURL>http://{{listenAddr}}</presentationURL>
  133. </device>
  134. </root>
  135. `,
  136. // The response to our GetNATRSIPStatus call. This
  137. // particular implementation has a bug where the elements
  138. // inside u:GetNATRSIPStatusResponse are not properly
  139. // namespaced.
  140. "POST /control?WANIPConnection": `
  141. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  142. <s:Body>
  143. <u:GetNATRSIPStatusResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
  144. <NewRSIPAvailable>0</NewRSIPAvailable>
  145. <NewNATEnabled>1</NewNATEnabled>
  146. </u:GetNATRSIPStatusResponse>
  147. </s:Body>
  148. </s:Envelope>
  149. `,
  150. },
  151. }
  152. if err := dev.listen(); err != nil {
  153. t.Skipf("cannot listen: %v", err)
  154. }
  155. dev.serve()
  156. defer dev.close()
  157. // Attempt to discover the fake device.
  158. discovered := discoverUPnP()
  159. if discovered == nil {
  160. if os.Getenv("CI") != "" {
  161. t.Fatalf("not discovered")
  162. } else {
  163. t.Skipf("UPnP not discovered (known issue, see https://github.com/ethereum/go-ethereum/issues/21476)")
  164. }
  165. }
  166. upnp, _ := discovered.(*upnp)
  167. if upnp.service != "IGDv1-IP1" {
  168. t.Errorf("upnp.service mismatch: got %q, want %q", upnp.service, "IGDv1-IP1")
  169. }
  170. wantURL := "http://" + dev.listener.Addr().String() + "/InternetGatewayDevice.xml"
  171. if upnp.dev.URLBaseStr != wantURL {
  172. t.Errorf("upnp.dev.URLBaseStr mismatch: got %q, want %q", upnp.dev.URLBaseStr, wantURL)
  173. }
  174. }
  175. // fakeIGD presents itself as a discoverable UPnP device which sends
  176. // canned responses to HTTPU and HTTP requests.
  177. type fakeIGD struct {
  178. t *testing.T // for logging
  179. listener net.Listener
  180. mcastListener *net.UDPConn
  181. // This should be a complete HTTP response (including headers).
  182. // It is sent as the response to any sspd packet. Any occurrence
  183. // of "{{listenAddr}}" is replaced with the actual TCP listen
  184. // address of the HTTP server.
  185. ssdpResp string
  186. // This one should contain XML payloads for all requests
  187. // performed. The keys contain method and path, e.g. "GET /foo/bar".
  188. // As with ssdpResp, "{{listenAddr}}" is replaced with the TCP
  189. // listen address.
  190. httpResps map[string]string
  191. }
  192. // httpu.Handler
  193. func (dev *fakeIGD) ServeMessage(r *http.Request) {
  194. dev.t.Logf(`HTTPU request %s %s`, r.Method, r.RequestURI)
  195. conn, err := net.Dial("udp4", r.RemoteAddr)
  196. if err != nil {
  197. fmt.Printf("reply Dial error: %v", err)
  198. return
  199. }
  200. defer conn.Close()
  201. io.WriteString(conn, dev.replaceListenAddr(dev.ssdpResp))
  202. }
  203. // http.Handler
  204. func (dev *fakeIGD) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  205. if resp, ok := dev.httpResps[r.Method+" "+r.RequestURI]; ok {
  206. dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 200)
  207. io.WriteString(w, dev.replaceListenAddr(resp))
  208. } else {
  209. dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 404)
  210. w.WriteHeader(http.StatusNotFound)
  211. }
  212. }
  213. func (dev *fakeIGD) replaceListenAddr(resp string) string {
  214. return strings.Replace(resp, "{{listenAddr}}", dev.listener.Addr().String(), -1)
  215. }
  216. func (dev *fakeIGD) listen() (err error) {
  217. if dev.listener, err = net.Listen("tcp", "127.0.0.1:0"); err != nil {
  218. return err
  219. }
  220. laddr := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 1900}
  221. if dev.mcastListener, err = net.ListenMulticastUDP("udp", nil, laddr); err != nil {
  222. dev.listener.Close()
  223. return err
  224. }
  225. return nil
  226. }
  227. func (dev *fakeIGD) serve() {
  228. go httpu.Serve(dev.mcastListener, dev)
  229. go http.Serve(dev.listener, dev)
  230. }
  231. func (dev *fakeIGD) close() {
  232. dev.mcastListener.Close()
  233. dev.listener.Close()
  234. }