github.com/ethereum/go-ethereum@v1.16.1/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  	"math"
    23  	"math/rand"
    24  	"net"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/huin/goupnp"
    31  	"github.com/huin/goupnp/dcps/internetgateway1"
    32  	"github.com/huin/goupnp/dcps/internetgateway2"
    33  )
    34  
    35  const (
    36  	soapRequestTimeout = 3 * time.Second
    37  	rateLimit          = 200 * time.Millisecond
    38  	retryCount         = 3 // number of retries after a failed AddPortMapping
    39  	randomCount        = 3 // number of random ports to try
    40  )
    41  
    42  type upnp struct {
    43  	dev         *goupnp.RootDevice
    44  	service     string
    45  	client      upnpClient
    46  	mu          sync.Mutex
    47  	lastReqTime time.Time
    48  	rand        *rand.Rand
    49  }
    50  
    51  type upnpClient interface {
    52  	GetExternalIPAddress() (string, error)
    53  	AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
    54  	DeletePortMapping(string, uint16, string) error
    55  	GetNATRSIPStatus() (sip bool, nat bool, err error)
    56  }
    57  
    58  func (n *upnp) natEnabled() bool {
    59  	var ok bool
    60  	var err error
    61  	n.withRateLimit(func() error {
    62  		_, ok, err = n.client.GetNATRSIPStatus()
    63  		return err
    64  	})
    65  	return err == nil && ok
    66  }
    67  
    68  func (n *upnp) ExternalIP() (addr net.IP, err error) {
    69  	var ipString string
    70  	n.withRateLimit(func() error {
    71  		ipString, err = n.client.GetExternalIPAddress()
    72  		return err
    73  	})
    74  
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	ip := net.ParseIP(ipString)
    79  	if ip == nil {
    80  		return nil, errors.New("bad IP in response")
    81  	}
    82  	return ip, nil
    83  }
    84  
    85  func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
    86  	ip, err := n.internalAddress()
    87  	if err != nil {
    88  		return 0, err
    89  	}
    90  	protocol = strings.ToUpper(protocol)
    91  	lifetimeS := uint32(lifetime / time.Second)
    92  
    93  	if extport == 0 {
    94  		extport = intport
    95  	}
    96  
    97  	// Try to add port mapping, preferring the specified external port.
    98  	return n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
    99  }
   100  
   101  // addAnyPortMapping tries to add a port mapping with the specified external port.
   102  // If the external port is already in use, it will try to assign another port.
   103  func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
   104  	if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
   105  		return n.portWithRateLimit(func() (uint16, error) {
   106  			return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
   107  		})
   108  	}
   109  	// For IGDv1 and v1 services we should first try to add with extport.
   110  	for i := 0; i < retryCount+1; i++ {
   111  		err := n.withRateLimit(func() error {
   112  			return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
   113  		})
   114  		if err == nil {
   115  			return uint16(extport), nil
   116  		}
   117  		log.Debug("Failed to add port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err)
   118  	}
   119  
   120  	// If above fails, we retry with a random port.
   121  	// We retry several times because of possible port conflicts.
   122  	var err error
   123  	for i := 0; i < randomCount; i++ {
   124  		extport = n.randomPort()
   125  		err := n.withRateLimit(func() error {
   126  			return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
   127  		})
   128  		if err == nil {
   129  			return uint16(extport), nil
   130  		}
   131  		log.Debug("Failed to add random port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err)
   132  	}
   133  	return 0, err
   134  }
   135  
   136  func (n *upnp) randomPort() int {
   137  	if n.rand == nil {
   138  		n.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
   139  	}
   140  	return n.rand.Intn(math.MaxUint16-10000) + 10000
   141  }
   142  
   143  func (n *upnp) internalAddress() (net.IP, error) {
   144  	devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	ifaces, err := net.Interfaces()
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	for _, iface := range ifaces {
   153  		addrs, err := iface.Addrs()
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		for _, addr := range addrs {
   158  			if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
   159  				return x.IP, nil
   160  			}
   161  		}
   162  	}
   163  	return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
   164  }
   165  
   166  func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
   167  	return n.withRateLimit(func() error {
   168  		return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
   169  	})
   170  }
   171  
   172  func (n *upnp) String() string {
   173  	return "UPNP " + n.service
   174  }
   175  
   176  func (n *upnp) portWithRateLimit(pfn func() (uint16, error)) (uint16, error) {
   177  	var port uint16
   178  	var err error
   179  	fn := func() error {
   180  		port, err = pfn()
   181  		return err
   182  	}
   183  	n.withRateLimit(fn)
   184  	return port, err
   185  }
   186  
   187  func (n *upnp) withRateLimit(fn func() error) error {
   188  	n.mu.Lock()
   189  	defer n.mu.Unlock()
   190  
   191  	lastreq := time.Since(n.lastReqTime)
   192  	if lastreq < rateLimit {
   193  		time.Sleep(rateLimit - lastreq)
   194  	}
   195  	err := fn()
   196  	n.lastReqTime = time.Now()
   197  	return err
   198  }
   199  
   200  // discoverUPnP searches for Internet Gateway Devices
   201  // and returns the first one it can find on the local network.
   202  func discoverUPnP() Interface {
   203  	found := make(chan *upnp, 2)
   204  	// IGDv1
   205  	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
   206  		switch sc.Service.ServiceType {
   207  		case internetgateway1.URN_WANIPConnection_1:
   208  			return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
   209  		case internetgateway1.URN_WANPPPConnection_1:
   210  			return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
   211  		}
   212  		return nil
   213  	})
   214  	// IGDv2
   215  	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
   216  		switch sc.Service.ServiceType {
   217  		case internetgateway2.URN_WANIPConnection_1:
   218  			return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
   219  		case internetgateway2.URN_WANIPConnection_2:
   220  			return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
   221  		case internetgateway2.URN_WANPPPConnection_1:
   222  			return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
   223  		}
   224  		return nil
   225  	})
   226  	for i := 0; i < cap(found); i++ {
   227  		if c := <-found; c != nil {
   228  			return c
   229  		}
   230  	}
   231  	return nil
   232  }
   233  
   234  // discover finds devices matching the given target and calls matcher for
   235  // all advertised services of each device. The first non-nil service found
   236  // is sent into out. If no service matched, nil is sent.
   237  func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
   238  	devs, err := goupnp.DiscoverDevices(target)
   239  	if err != nil {
   240  		out <- nil
   241  		return
   242  	}
   243  	found := false
   244  	for i := 0; i < len(devs) && !found; i++ {
   245  		if devs[i].Root == nil {
   246  			continue
   247  		}
   248  		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
   249  			if found {
   250  				return
   251  			}
   252  			// check for a matching IGD service
   253  			sc := goupnp.ServiceClient{
   254  				SOAPClient: service.NewSOAPClient(),
   255  				RootDevice: devs[i].Root,
   256  				Location:   devs[i].Location,
   257  				Service:    service,
   258  			}
   259  			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
   260  			upnp := matcher(sc)
   261  			if upnp == nil {
   262  				return
   263  			}
   264  			upnp.dev = devs[i].Root
   265  
   266  			// check whether port mapping is enabled
   267  			if upnp.natEnabled() {
   268  				out <- upnp
   269  				found = true
   270  			}
   271  		})
   272  	}
   273  	if !found {
   274  		out <- nil
   275  	}
   276  }