github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/p2p/upnp/upnp.go (about)

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