github.com/dominant-strategies/go-quai@v0.28.2/p2p/nat/natupnp.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package nat
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/huin/goupnp"
    28  	"github.com/huin/goupnp/dcps/internetgateway1"
    29  	"github.com/huin/goupnp/dcps/internetgateway2"
    30  )
    31  
    32  const (
    33  	soapRequestTimeout = 3 * time.Second
    34  	rateLimit          = 200 * time.Millisecond
    35  )
    36  
    37  type upnp struct {
    38  	dev         *goupnp.RootDevice
    39  	service     string
    40  	client      upnpClient
    41  	mu          sync.Mutex
    42  	lastReqTime time.Time
    43  }
    44  
    45  type upnpClient interface {
    46  	GetExternalIPAddress() (string, error)
    47  	AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
    48  	DeletePortMapping(string, uint16, string) error
    49  	GetNATRSIPStatus() (sip bool, nat bool, err error)
    50  }
    51  
    52  func (n *upnp) natEnabled() bool {
    53  	var ok bool
    54  	var err error
    55  	n.withRateLimit(func() error {
    56  		_, ok, err = n.client.GetNATRSIPStatus()
    57  		return err
    58  	})
    59  	return err == nil && ok
    60  }
    61  
    62  func (n *upnp) ExternalIP() (addr net.IP, err error) {
    63  	var ipString string
    64  	n.withRateLimit(func() error {
    65  		ipString, err = n.client.GetExternalIPAddress()
    66  		return err
    67  	})
    68  
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	ip := net.ParseIP(ipString)
    73  	if ip == nil {
    74  		return nil, errors.New("bad IP in response")
    75  	}
    76  	return ip, nil
    77  }
    78  
    79  func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error {
    80  	ip, err := n.internalAddress()
    81  	if err != nil {
    82  		return nil
    83  	}
    84  	protocol = strings.ToUpper(protocol)
    85  	lifetimeS := uint32(lifetime / time.Second)
    86  	n.DeleteMapping(protocol, extport, intport)
    87  
    88  	return n.withRateLimit(func() error {
    89  		return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
    90  	})
    91  }
    92  
    93  func (n *upnp) internalAddress() (net.IP, error) {
    94  	devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	ifaces, err := net.Interfaces()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	for _, iface := range ifaces {
   103  		addrs, err := iface.Addrs()
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		for _, addr := range addrs {
   108  			if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
   109  				return x.IP, nil
   110  			}
   111  		}
   112  	}
   113  	return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
   114  }
   115  
   116  func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
   117  	return n.withRateLimit(func() error {
   118  		return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
   119  	})
   120  }
   121  
   122  func (n *upnp) String() string {
   123  	return "UPNP " + n.service
   124  }
   125  
   126  func (n *upnp) withRateLimit(fn func() error) error {
   127  	n.mu.Lock()
   128  	defer n.mu.Unlock()
   129  
   130  	lastreq := time.Since(n.lastReqTime)
   131  	if lastreq < rateLimit {
   132  		time.Sleep(rateLimit - lastreq)
   133  	}
   134  	err := fn()
   135  	n.lastReqTime = time.Now()
   136  	return err
   137  }
   138  
   139  // discoverUPnP searches for Internet Gateway Devices
   140  // and returns the first one it can find on the local network.
   141  func discoverUPnP() Interface {
   142  	found := make(chan *upnp, 2)
   143  	// IGDv1
   144  	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
   145  		switch sc.Service.ServiceType {
   146  		case internetgateway1.URN_WANIPConnection_1:
   147  			return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
   148  		case internetgateway1.URN_WANPPPConnection_1:
   149  			return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
   150  		}
   151  		return nil
   152  	})
   153  	// IGDv2
   154  	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
   155  		switch sc.Service.ServiceType {
   156  		case internetgateway2.URN_WANIPConnection_1:
   157  			return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
   158  		case internetgateway2.URN_WANIPConnection_2:
   159  			return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
   160  		case internetgateway2.URN_WANPPPConnection_1:
   161  			return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
   162  		}
   163  		return nil
   164  	})
   165  	for i := 0; i < cap(found); i++ {
   166  		if c := <-found; c != nil {
   167  			return c
   168  		}
   169  	}
   170  	return nil
   171  }
   172  
   173  // finds devices matching the given target and calls matcher for all
   174  // advertised services of each device. The first non-nil service found
   175  // is sent into out. If no service matched, nil is sent.
   176  func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
   177  	devs, err := goupnp.DiscoverDevices(target)
   178  	if err != nil {
   179  		out <- nil
   180  		return
   181  	}
   182  	found := false
   183  	for i := 0; i < len(devs) && !found; i++ {
   184  		if devs[i].Root == nil {
   185  			continue
   186  		}
   187  		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
   188  			if found {
   189  				return
   190  			}
   191  			// check for a matching IGD service
   192  			sc := goupnp.ServiceClient{
   193  				SOAPClient: service.NewSOAPClient(),
   194  				RootDevice: devs[i].Root,
   195  				Location:   devs[i].Location,
   196  				Service:    service,
   197  			}
   198  			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
   199  			upnp := matcher(sc)
   200  			if upnp == nil {
   201  				return
   202  			}
   203  			upnp.dev = devs[i].Root
   204  
   205  			// check whether port mapping is enabled
   206  			if upnp.natEnabled() {
   207  				out <- upnp
   208  				found = true
   209  			}
   210  		})
   211  	}
   212  	if !found {
   213  		out <- nil
   214  	}
   215  }