From 069cff3d256552f04a3339a032cf771716c44fef Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 24 Jan 2015 12:42:22 -0800 Subject: [PATCH] p2p/nat: upnp + pmp --- core/core.go | 24 ++++--- p2p/nat/nat.go | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 p2p/nat/nat.go diff --git a/core/core.go b/core/core.go index a2839f610..2536f313e 100644 --- a/core/core.go +++ b/core/core.go @@ -18,6 +18,7 @@ import ( ic "github.com/jbenet/go-ipfs/p2p/crypto" p2phost "github.com/jbenet/go-ipfs/p2p/host" p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic" + inat "github.com/jbenet/go-ipfs/p2p/nat" swarm "github.com/jbenet/go-ipfs/p2p/net/swarm" addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" peer "github.com/jbenet/go-ipfs/p2p/peer" @@ -389,15 +390,13 @@ func loadPrivateKey(cfg *config.Identity, id peer.ID) (ic.PrivKey, error) { } func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) { - - var err error - listen := make([]ma.Multiaddr, len(cfg.Addresses.Swarm)) - for i, addr := range cfg.Addresses.Swarm { - - listen[i], err = ma.NewMultiaddr(addr) + var listen []ma.Multiaddr + for _, addr := range cfg.Addresses.Swarm { + maddr, err := ma.NewMultiaddr(addr) if err != nil { - return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm[%d]: %s", i, cfg.Addresses.Swarm) + return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm: %s", cfg.Addresses.Swarm) } + listen = append(listen, maddr) } return listen, nil @@ -413,7 +412,7 @@ func constructPeerHost(ctx context.Context, cfg *config.Config, id peer.ID, ps p // make sure we error out if our config does not have addresses we can use log.Debugf("Config.Addresses.Swarm:%s", listenAddrs) filteredAddrs := addrutil.FilterUsableAddrs(listenAddrs) - log.Debugf("Config.Addresses.Swarm:%s (filtered)", listenAddrs) + log.Debugf("Config.Addresses.Swarm:%s (filtered)", filteredAddrs) if len(filteredAddrs) < 1 { return nil, debugerror.Errorf("addresses in config not usable: %s", listenAddrs) } @@ -431,7 +430,14 @@ func constructPeerHost(ctx context.Context, cfg *config.Config, id peer.ID, ps p if err != nil { return nil, debugerror.Wrap(err) } - log.Info("Swarm listening at: %s", addrs) + log.Infof("Swarm listening at: %s", addrs) + + mapAddrs := inat.MapAddrs(filteredAddrs) + if len(mapAddrs) > 0 { + log.Infof("NAT mapping addrs: %s", mapAddrs) + addrs = append(addrs, mapAddrs...) + } + ps.AddAddresses(id, addrs) return peerhost, nil } diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go new file mode 100644 index 000000000..d87031585 --- /dev/null +++ b/p2p/nat/nat.go @@ -0,0 +1,168 @@ +package nat + +import ( + "fmt" + "strconv" + "strings" + "time" + + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" + + nat "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/fd/go-nat" + goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess" + eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog" +) + +var log = eventlog.Logger("nat") + +const MappingDuration = time.Second * 60 + +func DiscoverGateway() nat.NAT { + nat, err := nat.DiscoverGateway() + if err != nil { + log.Debug("DiscoverGateway error:", err) + return nil + } + addr, err := nat.GetDeviceAddress() + if err != nil { + log.Debug("DiscoverGateway address error:", err) + } else { + log.Debug("DiscoverGateway address:", addr) + } + return nat +} + +type Mapping interface { + NAT() nat.NAT + Protocol() string + InternalPort() int + ExternalPort() int +} + +type mapping struct { + // keeps republishing + nat nat.NAT + proto string + intport int + extport int + proc goprocess.Process +} + +func (m *mapping) NAT() nat.NAT { + return m.nat +} +func (m *mapping) Protocol() string { + return m.proto +} +func (m *mapping) InternalPort() int { + return m.intport +} +func (m *mapping) ExternalPort() int { + return m.extport +} + +// NewMapping attemps to construct a mapping on protocl and internal port +func NewMapping(nat nat.NAT, protocol string, internalPort int) (Mapping, error) { + log.Debugf("Attempting port map: %s/%d", protocol, internalPort) + eport, err := nat.AddPortMapping(protocol, internalPort, "http", MappingDuration) + if err != nil { + return nil, err + } + + m := &mapping{ + nat: nat, + proto: protocol, + intport: internalPort, + extport: eport, + } + + m.proc = goprocess.Go(func(worker goprocess.Process) { + for { + select { + case <-worker.Closing(): + return + case <-time.After(MappingDuration / 3): + eport, err := m.NAT().AddPortMapping(protocol, internalPort, "http", MappingDuration) + if err != nil { + log.Warningf("failed to renew port mapping: %s", err) + continue + } + if eport != m.extport { + log.Warningf("failed to renew same port mapping: ch %d -> %d", m.extport, eport) + } + } + } + }) + + return m, nil +} + +func (m *mapping) Close() error { + return m.proc.Close() +} + +func MapAddr(n nat.NAT, maddr ma.Multiaddr) (ma.Multiaddr, error) { + if n == nil { + return nil, fmt.Errorf("no nat available") + } + + ip, err := n.GetExternalAddress() + if err != nil { + return nil, err + } + + ipmaddr, err := manet.FromIP(ip) + if err != nil { + return nil, fmt.Errorf("error parsing ip") + } + + network, addr, err := manet.DialArgs(maddr) + if err != nil { + return nil, fmt.Errorf("DialArgs failed on addr:", maddr.String()) + } + + switch network { + case "tcp", "tcp4", "tcp6": + network = "tcp" + case "udp", "udp4", "udp6": + network = "udp" + default: + return nil, fmt.Errorf("transport not supported by NAT: %s", network) + } + + port := strings.Split(addr, ":")[1] + intport, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + + m, err := NewMapping(n, "tcp", intport) + if err != nil { + return nil, err + } + + tcp, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", m.ExternalPort())) + if err != nil { + return nil, err + } + + maddr2 := ipmaddr.Encapsulate(tcp) + log.Debugf("NAT Mapping: %s --> %s", maddr, maddr2) + return maddr2, nil +} + +func MapAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { + nat := DiscoverGateway() + + var advertise []ma.Multiaddr + for _, maddr := range addrs { + maddr2, err := MapAddr(nat, maddr) + if err != nil || maddr2 == nil { + log.Debug("failed to map addr:", maddr, err) + continue + } + advertise = append(advertise, maddr2) + } + return advertise +}