github.com/ethereum/go-ethereum@v1.16.1/p2p/nat/nat.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 provides access to common network port mapping protocols.
    18  package nat
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/log"
    29  	natpmp "github.com/jackpal/go-nat-pmp"
    30  )
    31  
    32  // Interface An implementation of nat.Interface can map local ports to ports
    33  // accessible from the Internet.
    34  type Interface interface {
    35  	// These methods manage a mapping between a port on the local
    36  	// machine to a port that can be connected to from the internet.
    37  	//
    38  	// protocol is "UDP" or "TCP". Some implementations allow setting
    39  	// a display name for the mapping. The mapping may be removed by
    40  	// the gateway when its lifetime ends.
    41  	AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error)
    42  	DeleteMapping(protocol string, extport, intport int) error
    43  
    44  	// ExternalIP should return the external (Internet-facing)
    45  	// address of the gateway device.
    46  	ExternalIP() (net.IP, error)
    47  
    48  	// String should return name of the method. This is used for logging.
    49  	String() string
    50  }
    51  
    52  // Parse parses a NAT interface description.
    53  // The following formats are currently accepted.
    54  // Note that mechanism names are not case-sensitive.
    55  //
    56  //	"" or "none"         return nil
    57  //	"extip:77.12.33.4"   will assume the local machine is reachable on the given IP
    58  //	"any"                uses the first auto-detected mechanism
    59  //	"upnp"               uses the Universal Plug and Play protocol
    60  //	"pmp"                uses NAT-PMP with an auto-detected gateway address
    61  //	"pmp:192.168.0.1"    uses NAT-PMP with the given gateway address
    62  //	"stun"       uses stun protocol with default stun server
    63  //	"stun:192.168.0.1:1234"   uses stun protocol with stun server address 192.168.0.1:1234
    64  func Parse(spec string) (Interface, error) {
    65  	var (
    66  		before, after, found = strings.Cut(spec, ":")
    67  		mech                 = strings.ToLower(before)
    68  		ip                   net.IP
    69  	)
    70  	// stun is not a valid ip
    71  	if found && mech != "stun" {
    72  		ip = net.ParseIP(after)
    73  		if ip == nil {
    74  			return nil, errors.New("invalid IP address")
    75  		}
    76  	}
    77  	switch mech {
    78  	case "", "none", "off":
    79  		return nil, nil
    80  	case "any", "auto", "on":
    81  		return Any(), nil
    82  	case "extip", "ip":
    83  		if ip == nil {
    84  			return nil, errors.New("missing IP address")
    85  		}
    86  		return ExtIP(ip), nil
    87  	case "upnp":
    88  		return UPnP(), nil
    89  	case "pmp", "natpmp", "nat-pmp":
    90  		return PMP(ip), nil
    91  	case "stun":
    92  		return newSTUN(after)
    93  	default:
    94  		return nil, fmt.Errorf("unknown mechanism %q", before)
    95  	}
    96  }
    97  
    98  const (
    99  	DefaultMapTimeout = 10 * time.Minute
   100  )
   101  
   102  // Map adds a port mapping on m and keeps it alive until c is closed.
   103  // This function is typically invoked in its own goroutine.
   104  //
   105  // Note that Map does not handle the situation where the NAT interface assigns a different
   106  // external port than the requested one.
   107  func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
   108  	log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
   109  	refresh := time.NewTimer(DefaultMapTimeout)
   110  	defer func() {
   111  		refresh.Stop()
   112  		log.Debug("Deleting port mapping")
   113  		m.DeleteMapping(protocol, extport, intport)
   114  	}()
   115  	if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
   116  		log.Debug("Couldn't add port mapping", "err", err)
   117  	} else {
   118  		log.Info("Mapped network port")
   119  	}
   120  	for {
   121  		select {
   122  		case _, ok := <-c:
   123  			if !ok {
   124  				return
   125  			}
   126  		case <-refresh.C:
   127  			log.Trace("Refreshing port mapping")
   128  			if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
   129  				log.Debug("Couldn't add port mapping", "err", err)
   130  			}
   131  			refresh.Reset(DefaultMapTimeout)
   132  		}
   133  	}
   134  }
   135  
   136  // ExtIP assumes that the local machine is reachable on the given
   137  // external IP address, and that any required ports were mapped manually.
   138  // Mapping operations will not return an error but won't actually do anything.
   139  type ExtIP net.IP
   140  
   141  func (n ExtIP) ExternalIP() (net.IP, error)  { return net.IP(n), nil }
   142  func (n ExtIP) String() string               { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
   143  func (n ExtIP) MarshalText() ([]byte, error) { return fmt.Appendf(nil, "extip:%v", net.IP(n)), nil }
   144  
   145  // These do nothing.
   146  
   147  func (ExtIP) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
   148  	return uint16(extport), nil
   149  }
   150  func (ExtIP) DeleteMapping(string, int, int) error { return nil }
   151  
   152  // Any returns a port mapper that tries to discover any supported
   153  // mechanism on the local network.
   154  func Any() Interface {
   155  	// TODO: attempt to discover whether the local machine has an
   156  	// Internet-class address. Return ExtIP in this case.
   157  	return startautodisc("any", func() Interface {
   158  		found := make(chan Interface, 2)
   159  		go func() { found <- discoverUPnP() }()
   160  		go func() { found <- discoverPMP() }()
   161  		for i := 0; i < cap(found); i++ {
   162  			if c := <-found; c != nil {
   163  				return c
   164  			}
   165  		}
   166  		return nil
   167  	})
   168  }
   169  
   170  // UPnP returns a port mapper that uses UPnP. It will attempt to
   171  // discover the address of your router using UDP broadcasts.
   172  func UPnP() Interface {
   173  	return startautodisc("upnp", discoverUPnP)
   174  }
   175  
   176  // PMP returns a port mapper that uses NAT-PMP. The provided gateway
   177  // address should be the IP of your router. If the given gateway
   178  // address is nil, PMP will attempt to auto-discover the router.
   179  func PMP(gateway net.IP) Interface {
   180  	if gateway != nil {
   181  		return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
   182  	}
   183  	return startautodisc("natpmp", discoverPMP)
   184  }
   185  
   186  // autodisc represents a port mapping mechanism that is still being
   187  // auto-discovered. Calls to the Interface methods on this type will
   188  // wait until the discovery is done and then call the method on the
   189  // discovered mechanism.
   190  //
   191  // This type is useful because discovery can take a while but we
   192  // want return an Interface value from UPnP, PMP and Auto immediately.
   193  type autodisc struct {
   194  	what string // type of interface being autodiscovered
   195  	once sync.Once
   196  	doit func() Interface
   197  
   198  	mu    sync.Mutex
   199  	found Interface
   200  }
   201  
   202  func startautodisc(what string, doit func() Interface) Interface {
   203  	// TODO: monitor network configuration and rerun doit when it changes.
   204  	return &autodisc{what: what, doit: doit}
   205  }
   206  
   207  func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
   208  	if err := n.wait(); err != nil {
   209  		return 0, err
   210  	}
   211  	return n.found.AddMapping(protocol, extport, intport, name, lifetime)
   212  }
   213  
   214  func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
   215  	if err := n.wait(); err != nil {
   216  		return err
   217  	}
   218  	return n.found.DeleteMapping(protocol, extport, intport)
   219  }
   220  
   221  func (n *autodisc) ExternalIP() (net.IP, error) {
   222  	if err := n.wait(); err != nil {
   223  		return nil, err
   224  	}
   225  	return n.found.ExternalIP()
   226  }
   227  
   228  func (n *autodisc) String() string {
   229  	n.mu.Lock()
   230  	defer n.mu.Unlock()
   231  	if n.found == nil {
   232  		return n.what
   233  	}
   234  	return n.found.String()
   235  }
   236  
   237  func (n *autodisc) MarshalText() ([]byte, error) {
   238  	return []byte(n.what), nil
   239  }
   240  
   241  // wait blocks until auto-discovery has been performed.
   242  func (n *autodisc) wait() error {
   243  	n.once.Do(func() {
   244  		n.mu.Lock()
   245  		n.found = n.doit()
   246  		n.mu.Unlock()
   247  	})
   248  	if n.found == nil {
   249  		return fmt.Errorf("no %s router discovered", n.what)
   250  	}
   251  	return nil
   252  }