github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/network/p2p/nat/nat.go (about)

     1  package nat
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	natpmp "github.com/jackpal/go-nat-pmp"
    12  	"github.com/neatlab/neatio/chain/log"
    13  )
    14  
    15  type Interface interface {
    16  	AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
    17  	DeleteMapping(protocol string, extport, intport int) error
    18  
    19  	ExternalIP() (net.IP, error)
    20  
    21  	String() string
    22  }
    23  
    24  func Parse(spec string) (Interface, error) {
    25  	var (
    26  		parts = strings.SplitN(spec, ":", 2)
    27  		mech  = strings.ToLower(parts[0])
    28  		ip    net.IP
    29  	)
    30  	if len(parts) > 1 {
    31  		ip = net.ParseIP(parts[1])
    32  		if ip == nil {
    33  			return nil, errors.New("invalid IP address")
    34  		}
    35  	}
    36  	switch mech {
    37  	case "", "none", "off":
    38  		return nil, nil
    39  	case "any", "auto", "on":
    40  		return Any(), nil
    41  	case "extip", "ip":
    42  		if ip == nil {
    43  			return nil, errors.New("missing IP address")
    44  		}
    45  		return ExtIP(ip), nil
    46  	case "upnp":
    47  		return UPnP(), nil
    48  	case "pmp", "natpmp", "nat-pmp":
    49  		return PMP(ip), nil
    50  	default:
    51  		return nil, fmt.Errorf("unknown mechanism %q", parts[0])
    52  	}
    53  }
    54  
    55  const (
    56  	mapTimeout        = 20 * time.Minute
    57  	mapUpdateInterval = 15 * time.Minute
    58  )
    59  
    60  func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) {
    61  	log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
    62  	refresh := time.NewTimer(mapUpdateInterval)
    63  	defer func() {
    64  		refresh.Stop()
    65  		log.Debug("Deleting port mapping")
    66  		m.DeleteMapping(protocol, extport, intport)
    67  	}()
    68  	if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
    69  		log.Debug("Couldn't add port mapping", "err", err)
    70  	}
    71  
    72  	for {
    73  		select {
    74  		case _, ok := <-c:
    75  			if !ok {
    76  				return
    77  			}
    78  		case <-refresh.C:
    79  			log.Trace("Refreshing port mapping")
    80  			if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
    81  				log.Debug("Couldn't add port mapping", "err", err)
    82  			}
    83  			refresh.Reset(mapUpdateInterval)
    84  		}
    85  	}
    86  }
    87  
    88  func ExtIP(ip net.IP) Interface {
    89  	if ip == nil {
    90  		panic("IP must not be nil")
    91  	}
    92  	return extIP(ip)
    93  }
    94  
    95  type extIP net.IP
    96  
    97  func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
    98  func (n extIP) String() string              { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
    99  
   100  func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
   101  func (extIP) DeleteMapping(string, int, int) error                     { return nil }
   102  
   103  func Any() Interface {
   104  
   105  	return startautodisc("UPnP or NAT-PMP", func() Interface {
   106  		found := make(chan Interface, 2)
   107  		go func() { found <- discoverUPnP() }()
   108  		go func() { found <- discoverPMP() }()
   109  		for i := 0; i < cap(found); i++ {
   110  			if c := <-found; c != nil {
   111  				return c
   112  			}
   113  		}
   114  		return nil
   115  	})
   116  }
   117  
   118  func UPnP() Interface {
   119  	return startautodisc("UPnP", discoverUPnP)
   120  }
   121  
   122  func PMP(gateway net.IP) Interface {
   123  	if gateway != nil {
   124  		return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
   125  	}
   126  	return startautodisc("NAT-PMP", discoverPMP)
   127  }
   128  
   129  type autodisc struct {
   130  	what string
   131  	once sync.Once
   132  	doit func() Interface
   133  
   134  	mu    sync.Mutex
   135  	found Interface
   136  }
   137  
   138  func startautodisc(what string, doit func() Interface) Interface {
   139  
   140  	return &autodisc{what: what, doit: doit}
   141  }
   142  
   143  func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
   144  	if err := n.wait(); err != nil {
   145  		return err
   146  	}
   147  	return n.found.AddMapping(protocol, extport, intport, name, lifetime)
   148  }
   149  
   150  func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
   151  	if err := n.wait(); err != nil {
   152  		return err
   153  	}
   154  	return n.found.DeleteMapping(protocol, extport, intport)
   155  }
   156  
   157  func (n *autodisc) ExternalIP() (net.IP, error) {
   158  	if err := n.wait(); err != nil {
   159  		return nil, err
   160  	}
   161  	return n.found.ExternalIP()
   162  }
   163  
   164  func (n *autodisc) String() string {
   165  	n.mu.Lock()
   166  	defer n.mu.Unlock()
   167  	if n.found == nil {
   168  		return n.what
   169  	} else {
   170  		return n.found.String()
   171  	}
   172  }
   173  
   174  func (n *autodisc) wait() error {
   175  	n.once.Do(func() {
   176  		n.mu.Lock()
   177  		n.found = n.doit()
   178  		n.mu.Unlock()
   179  	})
   180  	if n.found == nil {
   181  		return fmt.Errorf("no %s router discovered", n.what)
   182  	}
   183  	return nil
   184  }