github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/nat/nat.go (about)

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