github.com/btcsuite/btcd@v0.24.0/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  	"os"
    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 ourIP string
   130  		ourIP, err = getOurIP()
   131  		if err != nil {
   132  			return
   133  		}
   134  		nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
   135  		return
   136  	}
   137  	err = errors.New("UPnP port discovery failed")
   138  	return
   139  }
   140  
   141  // service represents the Service type in an UPnP xml description.
   142  // Only the parts we care about are present and thus the xml may have more
   143  // fields than present in the structure.
   144  type service struct {
   145  	ServiceType string `xml:"serviceType"`
   146  	ControlURL  string `xml:"controlURL"`
   147  }
   148  
   149  // deviceList represents the deviceList type in an UPnP xml description.
   150  // Only the parts we care about are present and thus the xml may have more
   151  // fields than present in the structure.
   152  type deviceList struct {
   153  	XMLName xml.Name `xml:"deviceList"`
   154  	Device  []device `xml:"device"`
   155  }
   156  
   157  // serviceList represents the serviceList type in an UPnP xml description.
   158  // Only the parts we care about are present and thus the xml may have more
   159  // fields than present in the structure.
   160  type serviceList struct {
   161  	XMLName xml.Name  `xml:"serviceList"`
   162  	Service []service `xml:"service"`
   163  }
   164  
   165  // device represents the device type in an UPnP xml description.
   166  // Only the parts we care about are present and thus the xml may have more
   167  // 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.
   176  // Only the parts we care about are present and thus the xml may have more
   177  // fields than present in the structure.
   178  type specVersion struct {
   179  	XMLName xml.Name `xml:"specVersion"`
   180  	Major   int      `xml:"major"`
   181  	Minor   int      `xml:"minor"`
   182  }
   183  
   184  // root represents the Root document for a UPnP xml description.
   185  // Only the parts we care about are present and thus the xml may have more
   186  // fields than present in the structure.
   187  type root struct {
   188  	XMLName     xml.Name `xml:"root"`
   189  	SpecVersion specVersion
   190  	Device      device
   191  }
   192  
   193  // getChildDevice searches the children of device for a device with the given
   194  // type.
   195  func getChildDevice(d *device, deviceType string) *device {
   196  	for i := range d.DeviceList.Device {
   197  		if d.DeviceList.Device[i].DeviceType == deviceType {
   198  			return &d.DeviceList.Device[i]
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  // getChildDevice searches the service list of device for a service with the
   205  // given type.
   206  func getChildService(d *device, serviceType string) *service {
   207  	for i := range d.ServiceList.Service {
   208  		if d.ServiceList.Service[i].ServiceType == serviceType {
   209  			return &d.ServiceList.Service[i]
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  // getOurIP returns a best guess at what the local IP is.
   216  func getOurIP() (ip string, err error) {
   217  	hostname, err := os.Hostname()
   218  	if err != nil {
   219  		return
   220  	}
   221  	return net.LookupCNAME(hostname)
   222  }
   223  
   224  // getServiceURL parses the xml description at the given root url to find the
   225  // url for the WANIPConnection service to be used for port forwarding.
   226  func getServiceURL(rootURL string) (url string, err error) {
   227  	r, err := http.Get(rootURL)
   228  	if err != nil {
   229  		return
   230  	}
   231  	defer r.Body.Close()
   232  	if r.StatusCode >= 400 {
   233  		err = errors.New(fmt.Sprint(r.StatusCode))
   234  		return
   235  	}
   236  	var root root
   237  	err = xml.NewDecoder(r.Body).Decode(&root)
   238  	if err != nil {
   239  		return
   240  	}
   241  	a := &root.Device
   242  	if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
   243  		err = errors.New("no InternetGatewayDevice")
   244  		return
   245  	}
   246  	b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
   247  	if b == nil {
   248  		err = errors.New("no WANDevice")
   249  		return
   250  	}
   251  	c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
   252  	if c == nil {
   253  		err = errors.New("no WANConnectionDevice")
   254  		return
   255  	}
   256  	d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
   257  	if d == nil {
   258  		err = errors.New("no WANIPConnection")
   259  		return
   260  	}
   261  	url = combineURL(rootURL, d.ControlURL)
   262  	return
   263  }
   264  
   265  // combineURL appends subURL onto rootURL.
   266  func combineURL(rootURL, subURL string) string {
   267  	protocolEnd := "://"
   268  	protoEndIndex := strings.Index(rootURL, protocolEnd)
   269  	a := rootURL[protoEndIndex+len(protocolEnd):]
   270  	rootIndex := strings.Index(a, "/")
   271  	return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
   272  }
   273  
   274  // soapBody represents the <s:Body> element in a SOAP reply.
   275  // fields we don't care about are elided.
   276  type soapBody struct {
   277  	XMLName xml.Name `xml:"Body"`
   278  	Data    []byte   `xml:",innerxml"`
   279  }
   280  
   281  // soapEnvelope represents the <s:Envelope> element in a SOAP reply.
   282  // fields we don't care about are elided.
   283  type soapEnvelope struct {
   284  	XMLName xml.Name `xml:"Envelope"`
   285  	Body    soapBody `xml:"Body"`
   286  }
   287  
   288  // soapRequests performs a soap request with the given parameters and returns
   289  // the xml replied stripped of the soap headers. in the case that the request is
   290  // unsuccessful the an error is returned.
   291  func soapRequest(url, function, message string) (replyXML []byte, err error) {
   292  	fullMessage := "<?xml version=\"1.0\" ?>" +
   293  		"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
   294  		"<s:Body>" + message + "</s:Body></s:Envelope>"
   295  
   296  	req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  	req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
   301  	req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
   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  		err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
   317  		r = nil
   318  		return
   319  	}
   320  	var reply soapEnvelope
   321  	err = xml.NewDecoder(r.Body).Decode(&reply)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	return reply.Body.Data, nil
   326  }
   327  
   328  // getExternalIPAddressResponse represents the XML response to a
   329  // GetExternalIPAddress SOAP request.
   330  type getExternalIPAddressResponse struct {
   331  	XMLName           xml.Name `xml:"GetExternalIPAddressResponse"`
   332  	ExternalIPAddress string   `xml:"NewExternalIPAddress"`
   333  }
   334  
   335  // GetExternalAddress implements the NAT interface by fetching the external IP
   336  // from the UPnP router.
   337  func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
   338  	message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n"
   339  	response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	var reply getExternalIPAddressResponse
   345  	err = xml.Unmarshal(response, &reply)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	addr = net.ParseIP(reply.ExternalIPAddress)
   351  	if addr == nil {
   352  		return nil, errors.New("unable to parse ip address")
   353  	}
   354  	return addr, nil
   355  }
   356  
   357  // AddPortMapping implements the NAT interface by setting up a port forwarding
   358  // from the UPnP router to the local machine with the given ports and protocol.
   359  func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
   360  	// A single concatenation would break ARM compilation.
   361  	message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
   362  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
   363  	message += "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>"
   364  	message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
   365  		"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
   366  		"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
   367  	message += description +
   368  		"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
   369  		"</NewLeaseDuration></u:AddPortMapping>"
   370  
   371  	response, err := soapRequest(n.serviceURL, "AddPortMapping", message)
   372  	if err != nil {
   373  		return
   374  	}
   375  
   376  	// TODO: check response to see if the port was forwarded
   377  	// If the port was not wildcard we don't get an reply with the port in
   378  	// it. Not sure about wildcard yet. miniupnpc just checks for error
   379  	// codes here.
   380  	mappedExternalPort = externalPort
   381  	_ = response
   382  	return
   383  }
   384  
   385  // DeletePortMapping implements the NAT interface by removing up a port forwarding
   386  // from the UPnP router to the local machine with the given ports and.
   387  func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
   388  
   389  	message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
   390  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
   391  		"</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>" +
   392  		"</u:DeletePortMapping>"
   393  
   394  	response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
   395  	if err != nil {
   396  		return
   397  	}
   398  
   399  	// TODO: check response to see if the port was deleted
   400  	// log.Println(message, response)
   401  	_ = response
   402  	return
   403  }