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