upnp.go raw

   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