github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/nat/nat.go (about)

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