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