github.com/Finschia/ostracon@v1.1.5/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/Finschia/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  	message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
   307  		"</u:GetExternalIPAddress>"
   308  
   309  	var response *http.Response
   310  	response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
   311  	if response != nil {
   312  		defer response.Body.Close()
   313  	}
   314  	if err != nil {
   315  		return
   316  	}
   317  	var envelope Envelope
   318  	data, err := io.ReadAll(response.Body)
   319  	if err != nil {
   320  		return
   321  	}
   322  	reader := bytes.NewReader(data)
   323  	err = xml.NewDecoder(reader).Decode(&envelope)
   324  	if err != nil {
   325  		return
   326  	}
   327  
   328  	info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
   329  
   330  	if err != nil {
   331  		return
   332  	}
   333  
   334  	return info, err
   335  }
   336  
   337  // GetExternalAddress returns an external IP. If GetExternalIPAddress action
   338  // fails or IP returned is invalid, GetExternalAddress returns an error.
   339  func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
   340  	info, err := n.getExternalIPAddress()
   341  	if err != nil {
   342  		return
   343  	}
   344  	addr = net.ParseIP(info.externalIPAddress)
   345  	if addr == nil {
   346  		err = fmt.Errorf("failed to parse IP: %v", info.externalIPAddress)
   347  	}
   348  	return
   349  }
   350  
   351  func (n *upnpNAT) AddPortMapping(
   352  	protocol string,
   353  	externalPort,
   354  	internalPort int,
   355  	description string,
   356  	timeout int,
   357  ) (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 := io.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  	message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
   390  		"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
   391  		"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
   392  		"</u:DeletePortMapping>"
   393  
   394  	var response *http.Response
   395  	response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
   396  	if response != nil {
   397  		defer response.Body.Close()
   398  	}
   399  	if err != nil {
   400  		return
   401  	}
   402  
   403  	// TODO: check response to see if the port was deleted
   404  	// log.Println(message, response)
   405  	_ = response
   406  	return
   407  }