github.com/evdatsion/aphelion-dpos-bft@v0.32.1/p2p/upnp/upnp.go (about)

     1  // Taken from taipei-torrent.
     2  // Just enough UPnP to be able to forward ports
     3  // For more information, see: http://www.upnp-hacks.org/upnp.html
     4  package upnp
     5  
     6  // TODO: use syscalls to get actual ourIP, see issue #712
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/xml"
    11  	"errors"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"net"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  type upnpNAT struct {
    22  	serviceURL string
    23  	ourIP      string
    24  	urnDomain  string
    25  }
    26  
    27  // protocol is either "udp" or "tcp"
    28  type NAT interface {
    29  	GetExternalAddress() (addr net.IP, err error)
    30  	AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
    31  	DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
    32  }
    33  
    34  func Discover() (nat NAT, err error) {
    35  	ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
    36  	if err != nil {
    37  		return
    38  	}
    39  	conn, err := net.ListenPacket("udp4", ":0")
    40  	if err != nil {
    41  		return
    42  	}
    43  	socket := conn.(*net.UDPConn)
    44  	defer socket.Close() // nolint: errcheck
    45  
    46  	if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	st := "InternetGatewayDevice:1"
    51  
    52  	buf := bytes.NewBufferString(
    53  		"M-SEARCH * HTTP/1.1\r\n" +
    54  			"HOST: 239.255.255.250:1900\r\n" +
    55  			"ST: ssdp:all\r\n" +
    56  			"MAN: \"ssdp:discover\"\r\n" +
    57  			"MX: 2\r\n\r\n")
    58  	message := buf.Bytes()
    59  	answerBytes := make([]byte, 1024)
    60  	for i := 0; i < 3; i++ {
    61  		_, err = socket.WriteToUDP(message, ssdp)
    62  		if err != nil {
    63  			return
    64  		}
    65  		var n int
    66  		_, _, err = socket.ReadFromUDP(answerBytes)
    67  		if err != nil {
    68  			return
    69  		}
    70  		for {
    71  			n, _, err = socket.ReadFromUDP(answerBytes)
    72  			if err != nil {
    73  				break
    74  			}
    75  			answer := string(answerBytes[0:n])
    76  			if !strings.Contains(answer, st) {
    77  				continue
    78  			}
    79  			// HTTP header field names are case-insensitive.
    80  			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
    81  			locString := "\r\nlocation:"
    82  			answer = strings.ToLower(answer)
    83  			locIndex := strings.Index(answer, locString)
    84  			if locIndex < 0 {
    85  				continue
    86  			}
    87  			loc := answer[locIndex+len(locString):]
    88  			endIndex := strings.Index(loc, "\r\n")
    89  			if endIndex < 0 {
    90  				continue
    91  			}
    92  			locURL := strings.TrimSpace(loc[0:endIndex])
    93  			var serviceURL, urnDomain string
    94  			serviceURL, urnDomain, err = getServiceURL(locURL)
    95  			if err != nil {
    96  				return
    97  			}
    98  			var ourIP net.IP
    99  			ourIP, err = localIPv4()
   100  			if err != nil {
   101  				return
   102  			}
   103  			nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}
   104  			return
   105  		}
   106  	}
   107  	err = errors.New("UPnP port discovery failed")
   108  	return
   109  }
   110  
   111  type Envelope struct {
   112  	XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
   113  	Soap    *SoapBody
   114  }
   115  type SoapBody struct {
   116  	XMLName    xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
   117  	ExternalIP *ExternalIPAddressResponse
   118  }
   119  
   120  type ExternalIPAddressResponse struct {
   121  	XMLName   xml.Name `xml:"GetExternalIPAddressResponse"`
   122  	IPAddress string   `xml:"NewExternalIPAddress"`
   123  }
   124  
   125  type ExternalIPAddress struct {
   126  	XMLName xml.Name `xml:"NewExternalIPAddress"`
   127  	IP      string
   128  }
   129  
   130  type UPNPService struct {
   131  	ServiceType string `xml:"serviceType"`
   132  	ControlURL  string `xml:"controlURL"`
   133  }
   134  
   135  type DeviceList struct {
   136  	Device []Device `xml:"device"`
   137  }
   138  
   139  type ServiceList struct {
   140  	Service []UPNPService `xml:"service"`
   141  }
   142  
   143  type Device struct {
   144  	XMLName     xml.Name    `xml:"device"`
   145  	DeviceType  string      `xml:"deviceType"`
   146  	DeviceList  DeviceList  `xml:"deviceList"`
   147  	ServiceList ServiceList `xml:"serviceList"`
   148  }
   149  
   150  type Root struct {
   151  	Device Device
   152  }
   153  
   154  func getChildDevice(d *Device, deviceType string) *Device {
   155  	dl := d.DeviceList.Device
   156  	for i := 0; i < len(dl); i++ {
   157  		if strings.Contains(dl[i].DeviceType, deviceType) {
   158  			return &dl[i]
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  func getChildService(d *Device, serviceType string) *UPNPService {
   165  	sl := d.ServiceList.Service
   166  	for i := 0; i < len(sl); i++ {
   167  		if strings.Contains(sl[i].ServiceType, serviceType) {
   168  			return &sl[i]
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  func localIPv4() (net.IP, error) {
   175  	tt, err := net.Interfaces()
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	for _, t := range tt {
   180  		aa, err := t.Addrs()
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  		for _, a := range aa {
   185  			ipnet, ok := a.(*net.IPNet)
   186  			if !ok {
   187  				continue
   188  			}
   189  			v4 := ipnet.IP.To4()
   190  			if v4 == nil || v4[0] == 127 { // loopback address
   191  				continue
   192  			}
   193  			return v4, nil
   194  		}
   195  	}
   196  	return nil, errors.New("cannot find local IP address")
   197  }
   198  
   199  func getServiceURL(rootURL string) (url, urnDomain string, err error) {
   200  	r, err := http.Get(rootURL) // nolint: gosec
   201  	if err != nil {
   202  		return
   203  	}
   204  	defer r.Body.Close() // nolint: errcheck
   205  
   206  	if r.StatusCode >= 400 {
   207  		err = errors.New(string(r.StatusCode))
   208  		return
   209  	}
   210  	var root Root
   211  	err = xml.NewDecoder(r.Body).Decode(&root)
   212  	if err != nil {
   213  		return
   214  	}
   215  	a := &root.Device
   216  	if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") {
   217  		err = errors.New("No InternetGatewayDevice")
   218  		return
   219  	}
   220  	b := getChildDevice(a, "WANDevice:1")
   221  	if b == nil {
   222  		err = errors.New("No WANDevice")
   223  		return
   224  	}
   225  	c := getChildDevice(b, "WANConnectionDevice:1")
   226  	if c == nil {
   227  		err = errors.New("No WANConnectionDevice")
   228  		return
   229  	}
   230  	d := getChildService(c, "WANIPConnection:1")
   231  	if d == nil {
   232  		// Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
   233  		// instead of under WanConnectionDevice
   234  		d = getChildService(b, "WANIPConnection:1")
   235  
   236  		if d == nil {
   237  			err = errors.New("No WANIPConnection")
   238  			return
   239  		}
   240  	}
   241  	// Extract the domain name, which isn't always 'schemas-upnp-org'
   242  	urnDomain = strings.Split(d.ServiceType, ":")[1]
   243  	url = combineURL(rootURL, d.ControlURL)
   244  	return
   245  }
   246  
   247  func combineURL(rootURL, subURL string) string {
   248  	protocolEnd := "://"
   249  	protoEndIndex := strings.Index(rootURL, protocolEnd)
   250  	a := rootURL[protoEndIndex+len(protocolEnd):]
   251  	rootIndex := strings.Index(a, "/")
   252  	return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
   253  }
   254  
   255  func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
   256  	fullMessage := "<?xml version=\"1.0\" ?>" +
   257  		"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
   258  		"<s:Body>" + message + "</s:Body></s:Envelope>"
   259  
   260  	req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
   265  	req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
   266  	//req.Header.Set("Transfer-Encoding", "chunked")
   267  	req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
   268  	req.Header.Set("Connection", "Close")
   269  	req.Header.Set("Cache-Control", "no-cache")
   270  	req.Header.Set("Pragma", "no-cache")
   271  
   272  	// log.Stderr("soapRequest ", req)
   273  
   274  	r, err = http.DefaultClient.Do(req)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  	/*if r.Body != nil {
   279  	    defer r.Body.Close()
   280  	}*/
   281  
   282  	if r.StatusCode >= 400 {
   283  		// log.Stderr(function, r.StatusCode)
   284  		err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
   285  		r = nil
   286  		return
   287  	}
   288  	return
   289  }
   290  
   291  type statusInfo struct {
   292  	externalIpAddress string
   293  }
   294  
   295  func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
   296  
   297  	message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
   298  		"</u:GetExternalIPAddress>"
   299  
   300  	var response *http.Response
   301  	response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
   302  	if response != nil {
   303  		defer response.Body.Close() // nolint: errcheck
   304  	}
   305  	if err != nil {
   306  		return
   307  	}
   308  	var envelope Envelope
   309  	data, err := ioutil.ReadAll(response.Body)
   310  	if err != nil {
   311  		return
   312  	}
   313  	reader := bytes.NewReader(data)
   314  	err = xml.NewDecoder(reader).Decode(&envelope)
   315  	if err != nil {
   316  		return
   317  	}
   318  
   319  	info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
   320  
   321  	if err != nil {
   322  		return
   323  	}
   324  
   325  	return
   326  }
   327  
   328  // GetExternalAddress returns an external IP. If GetExternalIPAddress action
   329  // fails or IP returned is invalid, GetExternalAddress returns an error.
   330  func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
   331  	info, err := n.getExternalIPAddress()
   332  	if err != nil {
   333  		return
   334  	}
   335  	addr = net.ParseIP(info.externalIpAddress)
   336  	if addr == nil {
   337  		err = fmt.Errorf("Failed to parse IP: %v", info.externalIpAddress)
   338  	}
   339  	return
   340  }
   341  
   342  func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
   343  	// A single concatenation would break ARM compilation.
   344  	message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
   345  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
   346  	message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
   347  	message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
   348  		"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
   349  		"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
   350  	message += description +
   351  		"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
   352  		"</NewLeaseDuration></u:AddPortMapping>"
   353  
   354  	var response *http.Response
   355  	response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
   356  	if response != nil {
   357  		defer response.Body.Close() // nolint: errcheck
   358  	}
   359  	if err != nil {
   360  		return
   361  	}
   362  
   363  	// TODO: check response to see if the port was forwarded
   364  	// log.Println(message, response)
   365  	// JAE:
   366  	// body, err := ioutil.ReadAll(response.Body)
   367  	// fmt.Println(string(body), err)
   368  	mappedExternalPort = externalPort
   369  	_ = response
   370  	return
   371  }
   372  
   373  func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
   374  
   375  	message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
   376  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
   377  		"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
   378  		"</u:DeletePortMapping>"
   379  
   380  	var response *http.Response
   381  	response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
   382  	if response != nil {
   383  		defer response.Body.Close() // nolint: errcheck
   384  	}
   385  	if err != nil {
   386  		return
   387  	}
   388  
   389  	// TODO: check response to see if the port was deleted
   390  	// log.Println(message, response)
   391  	_ = response
   392  	return
   393  }