status-go/vendor/github.com/libp2p/go-libp2p-nat/nat.go

198 lines
4.9 KiB
Go

package nat
import (
"context"
"errors"
"fmt"
"sync"
"time"
logging "github.com/ipfs/go-log"
goprocess "github.com/jbenet/goprocess"
periodic "github.com/jbenet/goprocess/periodic"
nat "github.com/libp2p/go-nat"
)
var (
// ErrNoMapping signals no mapping exists for an address
ErrNoMapping = errors.New("mapping not established")
)
var log = logging.Logger("nat")
// MappingDuration is a default port mapping duration.
// Port mappings are renewed every (MappingDuration / 3)
const MappingDuration = time.Second * 60
// CacheTime is the time a mapping will cache an external address for
const CacheTime = time.Second * 15
// DiscoverNAT looks for a NAT device in the network and
// returns an object that can manage port mappings.
func DiscoverNAT(ctx context.Context) (*NAT, error) {
var (
natInstance nat.NAT
err error
)
done := make(chan struct{})
go func() {
defer close(done)
// This will abort in 10 seconds anyways.
natInstance, err = nat.DiscoverGateway()
}()
select {
case <-done:
case <-ctx.Done():
return nil, ctx.Err()
}
if err != nil {
return nil, err
}
// Log the device addr.
addr, err := natInstance.GetDeviceAddress()
if err != nil {
log.Debug("DiscoverGateway address error:", err)
} else {
log.Debug("DiscoverGateway address:", addr)
}
return newNAT(natInstance), nil
}
// NAT is an object that manages address port mappings in
// NATs (Network Address Translators). It is a long-running
// service that will periodically renew port mappings,
// and keep an up-to-date list of all the external addresses.
type NAT struct {
natmu sync.Mutex
nat nat.NAT
proc goprocess.Process
mappingmu sync.RWMutex // guards mappings
mappings map[*mapping]struct{}
}
func newNAT(realNAT nat.NAT) *NAT {
return &NAT{
nat: realNAT,
proc: goprocess.WithParent(goprocess.Background()),
mappings: make(map[*mapping]struct{}),
}
}
// Close shuts down all port mappings. NAT can no longer be used.
func (nat *NAT) Close() error {
return nat.proc.Close()
}
// Process returns the nat's life-cycle manager, for making it listen
// to close signals.
func (nat *NAT) Process() goprocess.Process {
return nat.proc
}
// Mappings returns a slice of all NAT mappings
func (nat *NAT) Mappings() []Mapping {
nat.mappingmu.Lock()
maps2 := make([]Mapping, 0, len(nat.mappings))
for m := range nat.mappings {
maps2 = append(maps2, m)
}
nat.mappingmu.Unlock()
return maps2
}
func (nat *NAT) addMapping(m *mapping) {
// make mapping automatically close when nat is closed.
nat.proc.AddChild(m.proc)
nat.mappingmu.Lock()
nat.mappings[m] = struct{}{}
nat.mappingmu.Unlock()
}
func (nat *NAT) rmMapping(m *mapping) {
nat.mappingmu.Lock()
delete(nat.mappings, m)
nat.mappingmu.Unlock()
}
// NewMapping attemps to construct a mapping on protocol and internal port
// It will also periodically renew the mapping until the returned Mapping
// -- or its parent NAT -- is Closed.
//
// May not succeed, and mappings may change over time;
// NAT devices may not respect our port requests, and even lie.
// Clients should not store the mapped results, but rather always
// poll our object for the latest mappings.
func (nat *NAT) NewMapping(protocol string, port int) (Mapping, error) {
if nat == nil {
return nil, fmt.Errorf("no nat available")
}
switch protocol {
case "tcp", "udp":
default:
return nil, fmt.Errorf("invalid protocol: %s", protocol)
}
m := &mapping{
intport: port,
nat: nat,
proto: protocol,
}
m.proc = goprocess.WithTeardown(func() error {
nat.rmMapping(m)
nat.natmu.Lock()
defer nat.natmu.Unlock()
nat.nat.DeletePortMapping(m.Protocol(), m.InternalPort())
return nil
})
nat.addMapping(m)
m.proc.AddChild(periodic.Every(MappingDuration/3, func(worker goprocess.Process) {
nat.establishMapping(m)
}))
// do it once synchronously, so first mapping is done right away, and before exiting,
// allowing users -- in the optimistic case -- to use results right after.
nat.establishMapping(m)
return m, nil
}
func (nat *NAT) establishMapping(m *mapping) {
oldport := m.ExternalPort()
log.Debugf("Attempting port map: %s/%d", m.Protocol(), m.InternalPort())
comment := "libp2p"
nat.natmu.Lock()
newport, err := nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), comment, MappingDuration)
if err != nil {
// Some hardware does not support mappings with timeout, so try that
newport, err = nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), comment, 0)
}
nat.natmu.Unlock()
if err != nil || newport == 0 {
m.setExternalPort(0) // clear mapping
// TODO: log.Event
log.Warningf("failed to establish port mapping: %s", err)
// we do not close if the mapping failed,
// because it may work again next time.
return
}
m.setExternalPort(newport)
log.Debugf("NAT Mapping: %d --> %d (%s)", m.ExternalPort(), m.InternalPort(), m.Protocol())
if oldport != 0 && newport != oldport {
log.Debugf("failed to renew same port mapping: ch %d -> %d", oldport, newport)
}
}