1 package upnp
2 3 // UPNP code taken from Taipei Torrent license is below:
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // in the documentation and/or other materials provided with the
11 // distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived from
14 // this software without specific prior written permission.
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 // Just enough UPnP to be able to forward ports
27 import (
28 "bytes"
29 "encoding/xml"
30 "errors"
31 "net"
32 "net/http"
33 "os"
34 "strconv"
35 "strings"
36 "time"
37 )
38 39 // NAT is an interface representing a NAT traversal options for example UPNP or NAT-PMP. It provides methods to query
40 // and manipulate this traversal to allow access to services.
41 type NAT interface {
42 // GetExternalAddress - Get the external address from outside the NAT.
43 GetExternalAddress() (addr net.IP, e error)
44 // AddPortMapping - Add a port mapping for protocol (
45 // "udp" or "tcp") from external port to internal port with description lasting
46 // for timeout.
47 AddPortMapping(
48 protocol string, externalPort, internalPort int,
49 description string, timeout int,
50 ) (mappedExternalPort int, e error)
51 // DeletePortMapping - Remove a previously added port mapping from external
52 // port to internal port.
53 DeletePortMapping(
54 protocol string, externalPort,
55 internalPort int,
56 ) (e error)
57 }
58 type upnpNAT struct {
59 serviceURL string
60 ourIP string
61 }
62 63 // Discover searches the local network for a UPnP router returning a NAT for the network if so, nil if not.
64 func Discover() (nat NAT, e error) {
65 var ssdp *net.UDPAddr
66 ssdp, e = net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
67 if e != nil {
68 E.Ln(e)
69 return
70 }
71 var conn net.PacketConn
72 conn, e = net.ListenPacket("udp4", ":0")
73 if e != nil {
74 E.Ln(e)
75 return
76 }
77 socket := conn.(*net.UDPConn)
78 defer func() {
79 if e = socket.Close(); E.Chk(e) {
80 }
81 }()
82 e = socket.SetDeadline(time.Now().Add(3 * time.Second))
83 if E.Chk(e) {
84 return
85 }
86 st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
87 buf := bytes.NewBufferString(
88 "M-SEARCH * HTTP/1.1\r\n" +
89 "HOST: 239.255.255.250:1900\r\n" +
90 st +
91 "MAN: \"ssdp:discover\"\r\n" +
92 "MX: 2\r\n\r\n",
93 )
94 message := buf.Bytes()
95 answerBytes := make([]byte, 1024)
96 for i := 0; i < 3; i++ {
97 _, e = socket.WriteToUDP(message, ssdp)
98 if e != nil {
99 E.Ln(e)
100 return
101 }
102 var n int
103 n, _, e = socket.ReadFromUDP(answerBytes)
104 if e != nil {
105 E.Ln(e)
106 continue
107 // socket.Close()
108 // return
109 }
110 answer := string(answerBytes[0:n])
111 if !strings.Contains(answer, "\r\n"+st) {
112 continue
113 }
114 // HTTP header field names are case-insensitive. http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
115 locString := "\r\nlocation: "
116 locIndex := strings.Index(strings.ToLower(answer), locString)
117 if locIndex < 0 {
118 continue
119 }
120 loc := answer[locIndex+len(locString):]
121 endIndex := strings.Index(loc, "\r\n")
122 if endIndex < 0 {
123 continue
124 }
125 locURL := loc[0:endIndex]
126 var serviceURL string
127 serviceURL, e = getServiceURL(locURL)
128 if e != nil {
129 E.Ln(e)
130 return
131 }
132 var ourIP string
133 ourIP, e = getOurIP()
134 if e != nil {
135 E.Ln(e)
136 return
137 }
138 nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
139 return
140 }
141 e = errors.New("UPnP port routeable failed")
142 return
143 }
144 145 // service represents the Service type in an UPnP xml description. Only the parts we care about are present and thus the
146 // xml may have more fields than present in the structure.
147 type service struct {
148 ServiceType string `xml:"serviceType"`
149 ControlURL string `xml:"controlURL"`
150 }
151 152 // deviceList represents the deviceList type in an UPnP xml description. Only the parts we care about are present and
153 // thus the xml may have more fields than present in the structure.
154 type deviceList struct {
155 XMLName xml.Name `xml:"deviceList"`
156 Device []device `xml:"device"`
157 }
158 159 // serviceList represents the serviceList type in an UPnP xml description. Only the parts we care about are present and
160 // thus the xml may have more fields than present in the structure.
161 type serviceList struct {
162 XMLName xml.Name `xml:"serviceList"`
163 Service []service `xml:"service"`
164 }
165 166 // device represents the device type in an UPnP xml description. Only the parts we care about are present and thus the
167 // xml may have more fields than present in the structure.
168 type device struct {
169 XMLName xml.Name `xml:"device"`
170 DeviceType string `xml:"deviceType"`
171 DeviceList deviceList `xml:"deviceList"`
172 ServiceList serviceList `xml:"serviceList"`
173 }
174 175 // specVersion represents the specVersion in a UPnP xml description. Only the parts we care about are present and thus
176 // the xml may have more fields than present in the structure.
177 type specVersion struct {
178 XMLName xml.Name `xml:"specVersion"`
179 Major int `xml:"major"`
180 Minor int `xml:"minor"`
181 }
182 183 // root represents the Root document for a UPnP xml description. Only the parts we care about are present and thus the
184 // xml may have more fields than present in the structure.
185 type root struct {
186 XMLName xml.Name `xml:"root"`
187 SpecVersion specVersion
188 Device device
189 }
190 191 // getChildDevice searches the children of device for a device with the given type.
192 func getChildDevice(d *device, deviceType string) *device {
193 for i := range d.DeviceList.Device {
194 if d.DeviceList.Device[i].DeviceType == deviceType {
195 return &d.DeviceList.Device[i]
196 }
197 }
198 return nil
199 }
200 201 // getChildDevice searches the service list of device for a service with the given type.
202 func getChildService(d *device, serviceType string) *service {
203 for i := range d.ServiceList.Service {
204 if d.ServiceList.Service[i].ServiceType == serviceType {
205 return &d.ServiceList.Service[i]
206 }
207 }
208 return nil
209 }
210 211 // getOurIP returns a best guess at what the local IP is.
212 func getOurIP() (ip string, e error) {
213 var hostname string
214 hostname, e = os.Hostname()
215 if e != nil {
216 E.Ln(e)
217 return
218 }
219 return net.LookupCNAME(hostname)
220 }
221 222 // getServiceURL parses the xml description at the given root url to find the url for the WANIPConnection service to be
223 // used for port forwarding.
224 func getServiceURL(rootURL string) (url string, e error) {
225 var re *http.Response
226 re, e = http.Get(rootURL)
227 if e != nil {
228 E.Ln(e)
229 return
230 }
231 defer func() {
232 if e = re.Body.Close(); E.Chk(e) {
233 }
234 }()
235 if re.StatusCode >= 400 {
236 e = errors.New(string(rune(re.StatusCode)))
237 return
238 }
239 var r root
240 e = xml.NewDecoder(re.Body).Decode(&r)
241 if e != nil {
242 E.Ln(e)
243 return
244 }
245 a := &r.Device
246 if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
247 e = errors.New("no InternetGatewayDevice")
248 return
249 }
250 b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
251 if b == nil {
252 e = errors.New("no WANDevice")
253 return
254 }
255 c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
256 if c == nil {
257 e = errors.New("no WANConnectionDevice")
258 return
259 }
260 d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
261 if d == nil {
262 e = errors.New("no WANIPConnection")
263 return
264 }
265 url = combineURL(rootURL, d.ControlURL)
266 return
267 }
268 269 // combineURL appends subURL onto rootURL.
270 func combineURL(rootURL, subURL string) string {
271 protocolEnd := "://"
272 protoEndIndex := strings.Index(rootURL, protocolEnd)
273 a := rootURL[protoEndIndex+len(protocolEnd):]
274 rootIndex := strings.Index(a, "/")
275 return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
276 }
277 278 // soapBody represents the <s:Body> element in a SOAP reply. fields we don't care about are elided.
279 type soapBody struct {
280 XMLName xml.Name `xml:"Body"`
281 Data []byte `xml:",innerxml"`
282 }
283 284 // soapEnvelope represents the <s:Envelope> element in a SOAP reply. fields we don't care about are elided.
285 type soapEnvelope struct {
286 XMLName xml.Name `xml:"Envelope"`
287 Body soapBody `xml:"Body"`
288 }
289 290 // soapRequests performs a soap request with the given parameters and returns the xml replied stripped of the soap
291 // headers. in the case that the request is unsuccessful the an error is returned.
292 func soapRequest(url, function, message string) (replyXML []byte, e error) {
293 fullMessage := "<?xml version=\"1.0\" ?>" +
294 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap." +
295 "org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap." +
296 "org/soap/encoding/\">\r\n" +
297 "<s:Body>" + message + "</s:Body></s:Envelope>"
298 var req *http.Request
299 req, e = http.NewRequest("POST", url, strings.NewReader(fullMessage))
300 if e != nil {
301 E.Ln(e)
302 return nil, e
303 }
304 req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
305 req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
306 // req.Header.Set("Transfer-Encoding", "chunked")
307 req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"")
308 req.Header.Set("Connection", "Close")
309 req.Header.Set("Cache-Control", "no-cache")
310 req.Header.Set("Pragma", "no-cache")
311 var r *http.Response
312 r, e = http.DefaultClient.Do(req)
313 if e != nil {
314 E.Ln(e)
315 return nil, e
316 }
317 if r.Body != nil {
318 defer func() {
319 if e = r.Body.Close(); E.Chk(e) {
320 }
321 }()
322 }
323 if r.StatusCode >= 400 {
324 // L.Stderr(function, r.StatusCode)
325 e = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
326 r = nil
327 return
328 }
329 var reply soapEnvelope
330 e = xml.NewDecoder(r.Body).Decode(&reply)
331 if e != nil {
332 E.Ln(e)
333 return nil, e
334 }
335 return reply.Body.Data, nil
336 }
337 338 // getExternalIPAddressResponse represents the XML response to a GetExternalIPAddress SOAP request.
339 type getExternalIPAddressResponse struct {
340 XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
341 ExternalIPAddress string `xml:"NewExternalIPAddress"`
342 }
343 344 // GetExternalAddress implements the NAT interface by fetching the external IP from the UPnP router.
345 func (n *upnpNAT) GetExternalAddress() (addr net.IP, e error) {
346 message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n"
347 var response []byte
348 response, e = soapRequest(n.serviceURL, "GetExternalIPAddress", message)
349 if e != nil {
350 E.Ln(e)
351 return nil, e
352 }
353 var reply getExternalIPAddressResponse
354 e = xml.Unmarshal(response, &reply)
355 if e != nil {
356 E.Ln(e)
357 return nil, e
358 }
359 addr = net.ParseIP(reply.ExternalIPAddress)
360 if addr == nil {
361 return nil, errors.New("unable to parse ip address")
362 }
363 return addr, nil
364 }
365 366 // AddPortMapping implements the NAT interface by setting up a port forwarding from the UPnP router to the local machine
367 // with the given ports and protocol.
368 func (n *upnpNAT) AddPortMapping(
369 protocol string,
370 externalPort, internalPort int,
371 description string,
372 timeout int,
373 ) (mappedExternalPort int, e error) {
374 // A single concatenation would break ARM compilation.
375 message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
376 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
377 message += "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>"
378 message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
379 "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
380 "<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
381 message += description +
382 "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
383 "</NewLeaseDuration></u:AddPortMapping>"
384 var response []byte
385 response, e = soapRequest(n.serviceURL, "AddPortMapping", message)
386 if e != nil {
387 E.Ln(e)
388 return
389 }
390 // TODO: check response to see if the port was forwarded
391 //
392 // If the port was not wildcard we don't get an reply with the port in it. Not sure about wildcard yet. miniupnpc
393 // just checks for error codes here.
394 mappedExternalPort = externalPort
395 _ = response
396 return
397 }
398 399 // DeletePortMapping implements the NAT interface by removing up a port forwarding from the UPnP router to the local
400 // machine with the given ports and.
401 func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (e error) {
402 message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
403 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
404 "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>" +
405 "</u:DeletePortMapping>"
406 var response []byte
407 response, e = soapRequest(n.serviceURL, "DeletePortMapping", message)
408 if e != nil {
409 E.Ln(e)
410 return
411 }
412 // TODO: check response to see if the port was deleted
413 _ = response
414 return
415 }
416