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