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