Skip to content

Commit

Permalink
Merge pull request #14 from libp2p/feat/no-multiddr
Browse files Browse the repository at this point in the history
remove all uses of multiaddrs
  • Loading branch information
Stebalien committed Mar 6, 2019
2 parents 3011636 + d6a73bd commit 77f74e3
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 196 deletions.
81 changes: 30 additions & 51 deletions p2p/net/nat/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package nat

import (
"fmt"
"net"
"sync"
"time"

"github.com/jbenet/goprocess"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr-net"
)

// Mapping represents a port mapping in a NAT.
Expand All @@ -27,12 +26,9 @@ type Mapping interface {
// established, port will be 0
ExternalPort() int

// InternalAddr returns the internal address.
InternalAddr() ma.Multiaddr

// ExternalAddr returns the external facing address. If the mapping is not
// established, addr will be nil, and and ErrNoMapping will be returned.
ExternalAddr() (addr ma.Multiaddr, err error)
ExternalAddr() (addr net.Addr, err error)

// Close closes the port mapping
Close() error
Expand All @@ -47,12 +43,9 @@ type mapping struct {
intport int
extport int
permanent bool
intaddr ma.Multiaddr
proc goprocess.Process

comment string

cached ma.Multiaddr
cached net.IP
cacheTime time.Time
cacheLk sync.Mutex
}
Expand Down Expand Up @@ -87,55 +80,41 @@ func (m *mapping) setExternalPort(p int) {
m.extport = p
}

func (m *mapping) InternalAddr() ma.Multiaddr {
m.Lock()
defer m.Unlock()
return m.intaddr
}

func (m *mapping) ExternalAddr() (ma.Multiaddr, error) {
func (m *mapping) ExternalAddr() (net.Addr, error) {
m.cacheLk.Lock()
ctime := m.cacheTime
cval := m.cached
m.cacheLk.Unlock()
if time.Since(ctime) < CacheTime {
return cval, nil
}

if m.ExternalPort() == 0 { // dont even try right now.
defer m.cacheLk.Unlock()
oport := m.ExternalPort()
if oport == 0 {
// dont even try right now.
return nil, ErrNoMapping
}

m.nat.natmu.Lock()
ip, err := m.nat.nat.GetExternalAddress()
m.nat.natmu.Unlock()
if err != nil {
return nil, err
}
if time.Since(m.cacheTime) >= CacheTime {
m.nat.natmu.Lock()
cval, err := m.nat.nat.GetExternalAddress()
m.nat.natmu.Unlock()

ipmaddr, err := manet.FromIP(ip)
if err != nil {
return nil, fmt.Errorf("error parsing ip")
}
if err != nil {
return nil, err
}

// call m.ExternalPort again, as mapping may have changed under our feet. (tocttou)
extport := m.ExternalPort()
if extport == 0 {
return nil, ErrNoMapping
m.cached = cval
m.cacheTime = time.Now()
}

tcp, err := ma.NewMultiaddr(fmt.Sprintf("/%s/%d", m.Protocol(), extport))
if err != nil {
return nil, err
switch m.Protocol() {
case "tcp":
return &net.TCPAddr{
IP: m.cached,
Port: oport,
}, nil
case "udp":
return &net.UDPAddr{
IP: m.cached,
Port: oport,
}, nil
default:
panic(fmt.Sprintf("invalid protocol %q", m.Protocol()))
}

maddr2 := ipmaddr.Encapsulate(tcp)

m.cacheLk.Lock()
m.cached = maddr2
m.cacheTime = time.Now()
m.cacheLk.Unlock()
return maddr2, nil
}

func (m *mapping) Close() error {
Expand Down
184 changes: 40 additions & 144 deletions p2p/net/nat/nat.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package nat

import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"

nat "github.com/fd/go-nat"
logging "github.com/ipfs/go-log"
goprocess "github.com/jbenet/goprocess"
periodic "github.com/jbenet/goprocess/periodic"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr-net"
)

var (
Expand All @@ -33,19 +29,38 @@ 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() *NAT {
nat, err := nat.DiscoverGateway()
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 {
log.Debug("DiscoverGateway error:", err)
return nil
return nil, err
}
addr, err := nat.GetDeviceAddress()

// 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(nat)

return newNAT(natInstance), nil
}

// NAT is an object that manages address port mappings in
Expand All @@ -55,7 +70,7 @@ func DiscoverNAT() *NAT {
type NAT struct {
natmu sync.Mutex
nat nat.NAT
proc goprocess.Process // manages nat mappings lifecycle
proc goprocess.Process

mappingmu sync.RWMutex // guards mappings
mappings map[*mapping]struct{}
Expand Down Expand Up @@ -116,66 +131,31 @@ func (nat *NAT) rmMapping(m *mapping) {
// 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(maddr ma.Multiaddr) (Mapping, error) {
func (nat *NAT) NewMapping(protocol string, port int) (Mapping, error) {
if nat == nil {
return nil, fmt.Errorf("no nat available")
}

network, addr, err := manet.DialArgs(maddr)
if err != nil {
return nil, fmt.Errorf("DialArgs failed on addr: %s", maddr.String())
}

var ip net.IP
switch network {
case "tcp", "tcp4", "tcp6":
addr, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, err
}
ip = addr.IP
network = "tcp"
case "udp", "udp4", "udp6":
addr, err := net.ResolveUDPAddr(network, addr)
if err != nil {
return nil, err
}
ip = addr.IP
network = "udp"
switch protocol {
case "tcp", "udp":
default:
return nil, fmt.Errorf("transport not supported by NAT: %s", network)
}

// XXX: Known limitation: doesn't handle multiple internal addresses.
// If this applies to you, you can figure it out yourself. Ideally, the
// NAT library would allow us to handle this case but the "go way"
// appears to be to just "shrug" at edge-cases.
if !ip.IsUnspecified() {
internalAddr, err := nat.nat.GetInternalAddress()
if err != nil {
return nil, fmt.Errorf("failed to discover address on nat: %s", err)
}
if !ip.Equal(internalAddr) {
return nil, fmt.Errorf("nat address is %s, refusing to map %s", internalAddr, ip)
}
}

intports := strings.Split(addr, ":")[1]
intport, err := strconv.Atoi(intports)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid protocol: %s", protocol)
}

m := &mapping{
intport: port,
nat: nat,
proto: network,
intport: intport,
intaddr: maddr,
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) {
Expand All @@ -193,9 +173,6 @@ func (nat *NAT) establishMapping(m *mapping) {

log.Debugf("Attempting port map: %s/%d", m.Protocol(), m.InternalPort())
comment := "libp2p"
if m.comment != "" {
comment = "libp2p-" + m.comment
}

nat.natmu.Lock()
newport, err := nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), comment, MappingDuration)
Expand All @@ -205,7 +182,7 @@ func (nat *NAT) establishMapping(m *mapping) {
}
nat.natmu.Unlock()

failure := func() {
if err != nil || newport == 0 {
m.setExternalPort(0) // clear mapping
// TODO: log.Event
log.Warningf("failed to establish port mapping: %s", err)
Expand All @@ -215,22 +192,11 @@ func (nat *NAT) establishMapping(m *mapping) {

// we do not close if the mapping failed,
// because it may work again next time.
}

if err != nil || newport == 0 {
failure()
return
}

m.setExternalPort(newport)
ext, err := m.ExternalAddr()
if err != nil {
log.Debugf("NAT Mapping addr error: %s %s", m.InternalAddr(), err)
failure()
return
}

log.Debugf("NAT Mapping: %s --> %s", m.InternalAddr(), ext)
log.Debugf("NAT Mapping: %s --> %s (%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)
nat.Notifier.notifyAll(func(n Notifiee) {
Expand All @@ -242,73 +208,3 @@ func (nat *NAT) establishMapping(m *mapping) {
n.MappingSuccess(nat, m)
})
}

// PortMapAddrs attempts to open (and continue to keep open)
// port mappings for given addrs. This function blocks until
// all addresses have been tried. This allows clients to
// retrieve results immediately after:
//
// nat.PortMapAddrs(addrs)
// mapped := nat.ExternalAddrs()
//
// Some 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) PortMapAddrs(addrs []ma.Multiaddr) {
// spin off addr mappings independently.
var wg sync.WaitGroup
for _, addr := range addrs {
// do all of them concurrently
wg.Add(1)
go func(addr ma.Multiaddr) {
defer wg.Done()
nat.NewMapping(addr)
}(addr)
}
wg.Wait()
}

// MappedAddrs returns address mappings NAT believes have been
// successfully established. Unsuccessful mappings are nil. This is:
//
// map[internalAddr]externalAddr
//
// This set of mappings _may not_ be correct, as NAT devices are finicky.
// Consider this with _best effort_ semantics.
func (nat *NAT) MappedAddrs() map[ma.Multiaddr]ma.Multiaddr {

mappings := nat.Mappings()
addrmap := make(map[ma.Multiaddr]ma.Multiaddr, len(mappings))

for _, m := range mappings {
i := m.InternalAddr()
e, err := m.ExternalAddr()
if err != nil {
addrmap[i] = nil
} else {
addrmap[i] = e
}
}
return addrmap
}

// ExternalAddrs returns a list of addresses that NAT believes have
// been successfully established. Unsuccessful mappings are omitted,
// so nat.ExternalAddrs() may return less addresses than nat.InternalAddrs().
// To see which addresses are mapped, use nat.MappedAddrs().
//
// This set of mappings _may not_ be correct, as NAT devices are finicky.
// Consider this with _best effort_ semantics.
func (nat *NAT) ExternalAddrs() []ma.Multiaddr {
mappings := nat.Mappings()
addrs := make([]ma.Multiaddr, 0, len(mappings))
for _, m := range mappings {
a, err := m.ExternalAddr()
if err != nil {
continue // this mapping not currently successful.
}
addrs = append(addrs, a)
}
return addrs
}
Loading

0 comments on commit 77f74e3

Please sign in to comment.