Files
go-libp2p/p2p/host/basic/natmgr.go
Steven Allen c83318bf08 Revert "Merge pull request #386 from libp2p/fix/correct-external-addr"
This reverts commit aca83b9b63, reversing
changes made to 86b8929d4d.

This was is not the correct fix. We already expose these addresses via the
host's AllAddrs method. The real problem is probably that we just don't ever
tell anyone about them (unless we disconnect and reconnect to our nearby DHT
nodes).

We need an address gossip protocol.
2018-08-13 16:52:49 -07:00

241 lines
6.9 KiB
Go

package basichost
import (
"context"
"sync"
goprocess "github.com/jbenet/goprocess"
lgbl "github.com/libp2p/go-libp2p-loggables"
inat "github.com/libp2p/go-libp2p-nat"
inet "github.com/libp2p/go-libp2p-net"
ma "github.com/multiformats/go-multiaddr"
)
// A simple interface to manage NAT devices.
type NATManager interface {
// Get the NAT device managed by the NAT manager.
NAT() *inat.NAT
// Receive a notification when the NAT device is ready for use.
Ready() <-chan struct{}
// Close all resources associated with a NAT manager.
Close() error
}
// Create a NAT manager.
func NewNATManager(net inet.Network) NATManager {
return newNatManager(net)
}
// natManager takes care of adding + removing port mappings to the nat.
// Initialized with the host if it has a NATPortMap option enabled.
// natManager receives signals from the network, and check on nat mappings:
// * natManager listens to the network and adds or closes port mappings
// as the network signals Listen() or ListenClose().
// * closing the natManager closes the nat and its mappings.
type natManager struct {
net inet.Network
natmu sync.RWMutex // guards nat (ready could obviate this mutex, but safety first.)
nat *inat.NAT
ready chan struct{} // closed once the nat is ready to process port mappings
proc goprocess.Process // natManager has a process + children. can be closed.
}
func newNatManager(net inet.Network) *natManager {
nmgr := &natManager{
net: net,
ready: make(chan struct{}),
}
nmgr.proc = goprocess.WithTeardown(func() error {
// on closing, unregister from network notifications.
net.StopNotify((*nmgrNetNotifiee)(nmgr))
return nil
})
// discover the nat.
nmgr.discoverNAT()
return nmgr
}
// Close closes the natManager, closing the underlying nat
// and unregistering from network events.
func (nmgr *natManager) Close() error {
return nmgr.proc.Close()
}
// Ready returns a channel which will be closed when the NAT has been found
// and is ready to be used, or the search process is done.
func (nmgr *natManager) Ready() <-chan struct{} {
return nmgr.ready
}
func (nmgr *natManager) discoverNAT() {
nmgr.proc.Go(func(worker goprocess.Process) {
// inat.DiscoverNAT blocks until the nat is found or a timeout
// is reached. we unfortunately cannot specify timeouts-- the
// library we're using just blocks.
//
// Note: on early shutdown, there may be a case where we're trying
// to close before DiscoverNAT() returns. Since we cant cancel it
// (library) we can choose to (1) drop the result and return early,
// or (2) wait until it times out to exit. For now we choose (2),
// to avoid leaking resources in a non-obvious way. the only case
// this affects is when the daemon is being started up and _immediately_
// asked to close. other services are also starting up, so ok to wait.
discoverdone := make(chan struct{})
var nat *inat.NAT
go func() {
defer close(discoverdone)
nat = inat.DiscoverNAT()
}()
// by this point -- after finding the NAT -- we may have already
// be closing. if so, just exit.
select {
case <-worker.Closing():
return
case <-discoverdone:
if nat == nil { // no nat, or failed to get it.
return
}
}
// wire up the nat to close when nmgr closes.
// nmgr.proc is our parent, and waiting for us.
nmgr.proc.AddChild(nat.Process())
// set the nat.
nmgr.natmu.Lock()
nmgr.nat = nat
nmgr.natmu.Unlock()
// signal that we're ready to process nat mappings:
close(nmgr.ready)
// sign natManager up for network notifications
// we need to sign up here to avoid missing some notifs
// before the NAT has been found.
nmgr.net.Notify((*nmgrNetNotifiee)(nmgr))
// if any interfaces were brought up while we were setting up
// the nat, now is the time to setup port mappings for them.
// we release ready, then grab them to avoid losing any. adding
// a port mapping is idempotent, so its ok to add the same twice.
addrs := nmgr.net.ListenAddresses()
for _, addr := range addrs {
// we do it async because it's slow and we may want to close beforehand
go addPortMapping(nmgr, addr)
}
})
}
// NAT returns the natManager's nat object. this may be nil, if
// (a) the search process is still ongoing, or (b) the search process
// found no nat. Clients must check whether the return value is nil.
func (nmgr *natManager) NAT() *inat.NAT {
nmgr.natmu.Lock()
defer nmgr.natmu.Unlock()
return nmgr.nat
}
func addPortMapping(nmgr *natManager, intaddr ma.Multiaddr) {
nat := nmgr.NAT()
if nat == nil {
panic("natManager addPortMapping called without a nat.")
}
// first, check if the port mapping already exists.
for _, mapping := range nat.Mappings() {
if mapping.InternalAddr().Equal(intaddr) {
return // it exists! return.
}
}
ctx := context.TODO()
lm := make(lgbl.DeferredMap)
lm["internalAddr"] = func() interface{} { return intaddr.String() }
defer log.EventBegin(ctx, "natMgrAddPortMappingWait", lm).Done()
select {
case <-nmgr.proc.Closing():
lm["outcome"] = "cancelled"
return // no use.
case <-nmgr.ready: // wait until it's ready.
}
// actually start the port map (sub-event because waiting may take a while)
defer log.EventBegin(ctx, "natMgrAddPortMapping", lm).Done()
// get the nat
m, err := nat.NewMapping(intaddr)
if err != nil {
lm["outcome"] = "failure"
lm["error"] = err
return
}
extaddr, err := m.ExternalAddr()
if err != nil {
lm["outcome"] = "failure"
lm["error"] = err
return
}
lm["outcome"] = "success"
lm["externalAddr"] = func() interface{} { return extaddr.String() }
log.Infof("established nat port mapping: %s <--> %s", intaddr, extaddr)
}
func rmPortMapping(nmgr *natManager, intaddr ma.Multiaddr) {
nat := nmgr.NAT()
if nat == nil {
panic("natManager rmPortMapping called without a nat.")
}
// list the port mappings (it may be gone on it's own, so we need to
// check this list, and not store it ourselves behind the scenes)
// close mappings for this internal address.
for _, mapping := range nat.Mappings() {
if mapping.InternalAddr().Equal(intaddr) {
mapping.Close()
}
}
}
// nmgrNetNotifiee implements the network notification listening part
// of the natManager. this is merely listening to Listen() and ListenClose()
// events.
type nmgrNetNotifiee natManager
func (nn *nmgrNetNotifiee) natManager() *natManager {
return (*natManager)(nn)
}
func (nn *nmgrNetNotifiee) Listen(n inet.Network, addr ma.Multiaddr) {
if nn.natManager().NAT() == nil {
return // not ready or doesnt exist.
}
addPortMapping(nn.natManager(), addr)
}
func (nn *nmgrNetNotifiee) ListenClose(n inet.Network, addr ma.Multiaddr) {
if nn.natManager().NAT() == nil {
return // not ready or doesnt exist.
}
rmPortMapping(nn.natManager(), addr)
}
func (nn *nmgrNetNotifiee) Connected(inet.Network, inet.Conn) {}
func (nn *nmgrNetNotifiee) Disconnected(inet.Network, inet.Conn) {}
func (nn *nmgrNetNotifiee) OpenedStream(inet.Network, inet.Stream) {}
func (nn *nmgrNetNotifiee) ClosedStream(inet.Network, inet.Stream) {}