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  }