github.com/klaytn/klaytn@v1.12.1/networks/p2p/nat/nat.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from p2p/nat/nat.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package nat
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"net"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	natpmp "github.com/jackpal/go-nat-pmp"
    32  	"github.com/klaytn/klaytn/log"
    33  )
    34  
    35  var logger = log.NewModuleLogger(log.NetworksP2PNat)
    36  
    37  // An implementation of nat.Interface can map local ports to ports
    38  // accessible from the Internet.
    39  type Interface interface {
    40  	// These methods manage a mapping between a port on the local
    41  	// machine to a port that can be connected to from the internet.
    42  	//
    43  	// protocol is "UDP" or "TCP". Some implementations allow setting
    44  	// a display name for the mapping. The mapping may be removed by
    45  	// the gateway when its lifetime ends.
    46  	AddMapping(protocol string, extport, inport int, name string, lifetime time.Duration) error
    47  	DeleteMapping(protocol string, extport, inport int) error
    48  
    49  	// This method should return the external (Internet-facing)
    50  	// address of the gateway device.
    51  	ExternalIP() (net.IP, error)
    52  
    53  	// Should return name of the method. This is used for logging.
    54  	String() string
    55  }
    56  
    57  // Parse parses a NAT interface description.
    58  // The following formats are currently accepted.
    59  // Note that mechanism names are not case-sensitive.
    60  //
    61  //     "" or "none"         return nil
    62  //     "extip:77.12.33.4"   will assume the local machine is reachable on the given IP
    63  //     "any"                uses the first auto-detected mechanism
    64  //     "upnp"               uses the Universal Plug and Play protocol
    65  //     "pmp"                uses NAT-PMP with an auto-detected gateway address
    66  //     "pmp:192.168.0.1"    uses NAT-PMP with the given gateway address
    67  func Parse(spec string) (Interface, error) {
    68  	var (
    69  		parts = strings.SplitN(spec, ":", 2)
    70  		mech  = strings.ToLower(parts[0])
    71  		ip    net.IP
    72  	)
    73  	if len(parts) > 1 {
    74  		ip = net.ParseIP(parts[1])
    75  		if ip == nil {
    76  			return nil, errors.New("invalid IP address")
    77  		}
    78  	}
    79  	switch mech {
    80  	case "", "none", "off":
    81  		return nil, nil
    82  	case "any", "auto", "on":
    83  		return Any(), nil
    84  	case "extip", "ip":
    85  		if ip == nil {
    86  			return nil, errors.New("missing IP address")
    87  		}
    88  		return ExtIP(ip), nil
    89  	case "upnp":
    90  		return UPnP(), nil
    91  	case "pmp", "natpmp", "nat-pmp":
    92  		return PMP(ip), nil
    93  	default:
    94  		return nil, fmt.Errorf("unknown mechanism %q", parts[0])
    95  	}
    96  }
    97  
    98  const (
    99  	mapTimeout        = 20 * time.Minute
   100  	mapUpdateInterval = 15 * time.Minute
   101  )
   102  
   103  // Map adds a port mapping on m and keeps it alive until c is closed.
   104  // This function is typically invoked in its own goroutine.
   105  func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) {
   106  	localLogger := logger.NewWith("protobuf", protocol, "extport", extport, "intport", intport, "interface", m)
   107  	refresh := time.NewTimer(mapUpdateInterval)
   108  	defer func() {
   109  		refresh.Stop()
   110  		localLogger.Debug("Deleting port mapping")
   111  		m.DeleteMapping(protocol, extport, intport)
   112  	}()
   113  	if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
   114  		localLogger.Debug("Couldn't add port mapping", "err", err)
   115  	} else {
   116  		localLogger.Info("Mapped network port")
   117  	}
   118  	for {
   119  		select {
   120  		case _, ok := <-c:
   121  			if !ok {
   122  				return
   123  			}
   124  		case <-refresh.C:
   125  			localLogger.Trace("Refreshing port mapping")
   126  			if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
   127  				localLogger.Debug("Couldn't add port mapping", "err", err)
   128  			}
   129  			refresh.Reset(mapUpdateInterval)
   130  		}
   131  	}
   132  }
   133  
   134  // ExtIP assumes that the local machine is reachable on the given
   135  // external IP address, and that any required ports were mapped manually.
   136  // Mapping operations will not return an error but won't actually do anything.
   137  func ExtIP(ip net.IP) Interface {
   138  	if ip == nil {
   139  		panic("IP must not be nil")
   140  	}
   141  	return extIP(ip)
   142  }
   143  
   144  type extIP net.IP
   145  
   146  func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
   147  func (n extIP) String() string              { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
   148  
   149  // These do nothing.
   150  func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
   151  func (extIP) DeleteMapping(string, int, int) error                     { return nil }
   152  
   153  // Any returns a port mapper that tries to discover any supported
   154  // mechanism on the local network.
   155  func Any() Interface {
   156  	// TODO: attempt to discover whether the local machine has an
   157  	// Internet-class address. Return ExtIP in this case.
   158  	return startautodisc("UPnP or NAT-PMP", func() Interface {
   159  		found := make(chan Interface, 2)
   160  		go func() { found <- discoverUPnP() }()
   161  		go func() { found <- discoverPMP() }()
   162  		for i := 0; i < cap(found); i++ {
   163  			if c := <-found; c != nil {
   164  				return c
   165  			}
   166  		}
   167  		return nil
   168  	})
   169  }
   170  
   171  // UPnP returns a port mapper that uses UPnP. It will attempt to
   172  // discover the address of your router using UDP broadcasts.
   173  func UPnP() Interface {
   174  	return startautodisc("UPnP", discoverUPnP)
   175  }
   176  
   177  // PMP returns a port mapper that uses NAT-PMP. The provided gateway
   178  // address should be the IP of your router. If the given gateway
   179  // address is nil, PMP will attempt to auto-discover the router.
   180  func PMP(gateway net.IP) Interface {
   181  	if gateway != nil {
   182  		return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
   183  	}
   184  	return startautodisc("NAT-PMP", discoverPMP)
   185  }
   186  
   187  // autodisc represents a port mapping mechanism that is still being
   188  // auto-discovered. Calls to the Interface methods on this type will
   189  // wait until the discovery is done and then call the method on the
   190  // discovered mechanism.
   191  //
   192  // This type is useful because discovery can take a while but we
   193  // want return an Interface value from UPnP, PMP and Auto immediately.
   194  type autodisc struct {
   195  	what string // type of interface being autodiscovered
   196  	once sync.Once
   197  	doit func() Interface
   198  
   199  	mu    sync.Mutex
   200  	found Interface
   201  }
   202  
   203  func startautodisc(what string, doit func() Interface) Interface {
   204  	// TODO: monitor network configuration and rerun doit when it changes.
   205  	return &autodisc{what: what, doit: doit}
   206  }
   207  
   208  func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
   209  	if err := n.wait(); err != nil {
   210  		return err
   211  	}
   212  	return n.found.AddMapping(protocol, extport, intport, name, lifetime)
   213  }
   214  
   215  func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
   216  	if err := n.wait(); err != nil {
   217  		return err
   218  	}
   219  	return n.found.DeleteMapping(protocol, extport, intport)
   220  }
   221  
   222  func (n *autodisc) ExternalIP() (net.IP, error) {
   223  	if err := n.wait(); err != nil {
   224  		return nil, err
   225  	}
   226  	return n.found.ExternalIP()
   227  }
   228  
   229  func (n *autodisc) String() string {
   230  	n.mu.Lock()
   231  	defer n.mu.Unlock()
   232  	if n.found == nil {
   233  		return n.what
   234  	} else {
   235  		return n.found.String()
   236  	}
   237  }
   238  
   239  // wait blocks until auto-discovery has been performed.
   240  func (n *autodisc) wait() error {
   241  	n.once.Do(func() {
   242  		n.mu.Lock()
   243  		n.found = n.doit()
   244  		n.mu.Unlock()
   245  	})
   246  	if n.found == nil {
   247  		return fmt.Errorf("no %s router discovered", n.what)
   248  	}
   249  	return nil
   250  }