github.com/lbryio/lbcd@v0.22.119/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  	"fmt"
    40  	"net"
    41  	"net/http"
    42  	"net/url"
    43  	"strconv"
    44  	"strings"
    45  	"time"
    46  )
    47  
    48  // NAT is an interface representing a NAT traversal options for example UPNP or
    49  // NAT-PMP. It provides methods to query and manipulate this traversal to allow
    50  // access to services.
    51  type NAT interface {
    52  	// Get the external address from outside the NAT.
    53  	GetExternalAddress() (addr net.IP, err error)
    54  	// Add a port mapping for protocol ("udp" or "tcp") from external port to
    55  	// internal port with description lasting for timeout.
    56  	AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
    57  	// Remove a previously added port mapping from external port to
    58  	// internal port.
    59  	DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
    60  }
    61  
    62  type upnpNAT struct {
    63  	serviceURL string
    64  	ourIP      string
    65  }
    66  
    67  // Discover searches the local network for a UPnP router returning a NAT
    68  // for the network if so, nil if not.
    69  func Discover() (nat NAT, err error) {
    70  	ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
    71  	if err != nil {
    72  		return
    73  	}
    74  	conn, err := net.ListenPacket("udp4", ":0")
    75  	if err != nil {
    76  		return
    77  	}
    78  	socket := conn.(*net.UDPConn)
    79  	defer socket.Close()
    80  
    81  	err = socket.SetDeadline(time.Now().Add(3 * time.Second))
    82  	if err != nil {
    83  		return
    84  	}
    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  	message := buf.Bytes()
    94  	answerBytes := make([]byte, 1024)
    95  	for i := 0; i < 3; i++ {
    96  		_, err = socket.WriteToUDP(message, ssdp)
    97  		if err != nil {
    98  			return
    99  		}
   100  		var n int
   101  		n, _, err = socket.ReadFromUDP(answerBytes)
   102  		if err != nil {
   103  			continue
   104  			// socket.Close()
   105  			// return
   106  		}
   107  		answer := string(answerBytes[0:n])
   108  		if !strings.Contains(answer, "\r\n"+st) {
   109  			continue
   110  		}
   111  		// HTTP header field names are case-insensitive.
   112  		// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
   113  		locString := "\r\nlocation: "
   114  		locIndex := strings.Index(strings.ToLower(answer), locString)
   115  		if locIndex < 0 {
   116  			continue
   117  		}
   118  		loc := answer[locIndex+len(locString):]
   119  		endIndex := strings.Index(loc, "\r\n")
   120  		if endIndex < 0 {
   121  			continue
   122  		}
   123  		locURL := loc[0:endIndex]
   124  		var serviceURL string
   125  		serviceURL, err = getServiceURL(locURL)
   126  		if err != nil {
   127  			return
   128  		}
   129  		var serviceIP string = getServiceIP(serviceURL)
   130  		var ourIP string
   131  		ourIP, err = getOurIP(serviceIP)
   132  		if err != nil {
   133  			return
   134  		}
   135  		nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
   136  		return
   137  	}
   138  	err = errors.New("UPnP port discovery failed")
   139  	return nat, err
   140  }
   141  
   142  // service represents the Service type in an UPnP xml description.
   143  // Only the parts we care about are present and thus the xml may have more
   144  // fields than present in the structure.
   145  type service struct {
   146  	ServiceType string `xml:"serviceType"`
   147  	ControlURL  string `xml:"controlURL"`
   148  }
   149  
   150  // deviceList represents the deviceList type in an UPnP xml description.
   151  // Only the parts we care about are present and thus the xml may have more
   152  // fields than present in the structure.
   153  type deviceList struct {
   154  	XMLName xml.Name `xml:"deviceList"`
   155  	Device  []device `xml:"device"`
   156  }
   157  
   158  // serviceList represents the serviceList type in an UPnP xml description.
   159  // Only the parts we care about are present and thus the xml may have more
   160  // 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.
   167  // Only the parts we care about are present and thus the xml may have more
   168  // fields than present in the structure.
   169  type device struct {
   170  	XMLName     xml.Name    `xml:"device"`
   171  	DeviceType  string      `xml:"deviceType"`
   172  	DeviceList  deviceList  `xml:"deviceList"`
   173  	ServiceList serviceList `xml:"serviceList"`
   174  }
   175  
   176  // specVersion represents the specVersion in a UPnP xml description.
   177  // Only the parts we care about are present and thus the xml may have more
   178  // fields than present in the structure.
   179  type specVersion struct {
   180  	XMLName xml.Name `xml:"specVersion"`
   181  	Major   int      `xml:"major"`
   182  	Minor   int      `xml:"minor"`
   183  }
   184  
   185  // root represents the Root document for a UPnP xml description.
   186  // Only the parts we care about are present and thus the xml may have more
   187  // fields than present in the structure.
   188  type root struct {
   189  	XMLName     xml.Name `xml:"root"`
   190  	SpecVersion specVersion
   191  	Device      device
   192  }
   193  
   194  // getChildDevice searches the children of device for a device with the given
   195  // type.
   196  func getChildDevice(d *device, deviceType string) *device {
   197  	for i := range d.DeviceList.Device {
   198  		if d.DeviceList.Device[i].DeviceType == deviceType {
   199  			return &d.DeviceList.Device[i]
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  // getChildDevice searches the service list of device for a service with the
   206  // given type.
   207  func getChildService(d *device, serviceType string) *service {
   208  	for i := range d.ServiceList.Service {
   209  		if d.ServiceList.Service[i].ServiceType == serviceType {
   210  			return &d.ServiceList.Service[i]
   211  		}
   212  	}
   213  	return nil
   214  }
   215  
   216  func getServiceIP(serviceURL string) (routerIP string) {
   217  	url, _ := url.Parse(serviceURL)
   218  	return url.Hostname()
   219  }
   220  
   221  // getOurIP returns the local IP that is on the same subnet as the serviceIP.
   222  func getOurIP(serviceIP string) (ip string, err error) {
   223  	_, serviceNet, _ := net.ParseCIDR(serviceIP + "/24")
   224  	addrs, err := net.InterfaceAddrs()
   225  	for _, addr := range addrs {
   226  		ip, _, _ := net.ParseCIDR(addr.String())
   227  		if serviceNet.Contains(ip) {
   228  			return ip.String(), nil
   229  		}
   230  	}
   231  	return
   232  }
   233  
   234  // getServiceURL parses the xml description at the given root url to find the
   235  // url for the WANIPConnection service to be used for port forwarding.
   236  func getServiceURL(rootURL string) (url string, err error) {
   237  	r, err := http.Get(rootURL)
   238  	if err != nil {
   239  		return
   240  	}
   241  	defer r.Body.Close()
   242  	if r.StatusCode >= 400 {
   243  		err = errors.New(fmt.Sprint(r.StatusCode))
   244  		return
   245  	}
   246  	var root root
   247  	err = xml.NewDecoder(r.Body).Decode(&root)
   248  	if err != nil {
   249  		return
   250  	}
   251  	a := &root.Device
   252  	if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
   253  		err = errors.New("no InternetGatewayDevice")
   254  		return
   255  	}
   256  	b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
   257  	if b == nil {
   258  		err = errors.New("no WANDevice")
   259  		return
   260  	}
   261  	c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
   262  	if c == nil {
   263  		err = errors.New("no WANConnectionDevice")
   264  		return
   265  	}
   266  	d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
   267  	if d == nil {
   268  		err = errors.New("no WANIPConnection")
   269  		return
   270  	}
   271  	url = combineURL(rootURL, d.ControlURL)
   272  	return url, err
   273  }
   274  
   275  // combineURL appends subURL onto rootURL.
   276  func combineURL(rootURL, subURL string) string {
   277  	protocolEnd := "://"
   278  	protoEndIndex := strings.Index(rootURL, protocolEnd)
   279  	a := rootURL[protoEndIndex+len(protocolEnd):]
   280  	rootIndex := strings.Index(a, "/")
   281  	return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
   282  }
   283  
   284  // soapBody represents the <s:Body> element in a SOAP reply.
   285  // fields we don't care about are elided.
   286  type soapBody struct {
   287  	XMLName xml.Name `xml:"Body"`
   288  	Data    []byte   `xml:",innerxml"`
   289  }
   290  
   291  // soapEnvelope represents the <s:Envelope> element in a SOAP reply.
   292  // fields we don't care about are elided.
   293  type soapEnvelope struct {
   294  	XMLName xml.Name `xml:"Envelope"`
   295  	Body    soapBody `xml:"Body"`
   296  }
   297  
   298  // soapRequests performs a soap request with the given parameters and returns
   299  // the xml replied stripped of the soap headers. in the case that the request is
   300  // unsuccessful the an error is returned.
   301  func soapRequest(url, function, message string) (replyXML []byte, err error) {
   302  	fullMessage := "<?xml version=\"1.0\" ?>" +
   303  		"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
   304  		"<s:Body>" + message + "</s:Body></s:Envelope>"
   305  
   306  	req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
   311  	req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
   312  	req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"")
   313  	req.Header.Set("Connection", "Close")
   314  	req.Header.Set("Cache-Control", "no-cache")
   315  	req.Header.Set("Pragma", "no-cache")
   316  
   317  	r, err := http.DefaultClient.Do(req)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	if r.Body != nil {
   322  		defer r.Body.Close()
   323  	}
   324  
   325  	if r.StatusCode >= 400 {
   326  		err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
   327  		r = nil
   328  		return
   329  	}
   330  	var reply soapEnvelope
   331  	err = xml.NewDecoder(r.Body).Decode(&reply)
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	return reply.Body.Data, nil
   336  }
   337  
   338  // getExternalIPAddressResponse represents the XML response to a
   339  // GetExternalIPAddress SOAP request.
   340  type getExternalIPAddressResponse struct {
   341  	XMLName           xml.Name `xml:"GetExternalIPAddressResponse"`
   342  	ExternalIPAddress string   `xml:"NewExternalIPAddress"`
   343  }
   344  
   345  // GetExternalAddress implements the NAT interface by fetching the external IP
   346  // from the UPnP router.
   347  func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
   348  	message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n"
   349  	response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	var reply getExternalIPAddressResponse
   355  	err = xml.Unmarshal(response, &reply)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	addr = net.ParseIP(reply.ExternalIPAddress)
   361  	if addr == nil {
   362  		return nil, errors.New("unable to parse ip address")
   363  	}
   364  	return addr, nil
   365  }
   366  
   367  // AddPortMapping implements the NAT interface by setting up a port forwarding
   368  // from the UPnP router to the local machine with the given ports and protocol.
   369  func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
   370  	// A single concatenation would break ARM compilation.
   371  	message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
   372  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
   373  	message += "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>"
   374  	message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
   375  		"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
   376  		"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
   377  	message += description +
   378  		"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
   379  		"</NewLeaseDuration></u:AddPortMapping>"
   380  
   381  	response, err := soapRequest(n.serviceURL, "AddPortMapping", message)
   382  	if err != nil {
   383  		return
   384  	}
   385  
   386  	// TODO: check response to see if the port was forwarded
   387  	// If the port was not wildcard we don't get an reply with the port in
   388  	// it. Not sure about wildcard yet. miniupnpc just checks for error
   389  	// codes here.
   390  	mappedExternalPort = externalPort
   391  	_ = response
   392  	return
   393  }
   394  
   395  // DeletePortMapping implements the NAT interface by removing up a port forwarding
   396  // from the UPnP router to the local machine with the given ports and.
   397  func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
   398  
   399  	message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
   400  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
   401  		"</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>" +
   402  		"</u:DeletePortMapping>"
   403  
   404  	response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
   405  	if err != nil {
   406  		return
   407  	}
   408  
   409  	// TODO: check response to see if the port was deleted
   410  	// log.Println(message, response)
   411  	_ = response
   412  	return
   413  }