From 7ec1a674e34c5d30868c2d936d689ffe7b011ecb Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Mon, 12 Jan 2015 12:21:04 -0800 Subject: [PATCH] p2p/net/swarm: do not usre link local addrs --- core/core.go | 10 +- p2p/net/conn/dial.go | 33 +-- p2p/net/conn/listen.go | 8 +- p2p/net/swarm/addr.go | 193 ------------- p2p/net/swarm/addr/addr.go | 258 ++++++++++++++++++ p2p/net/swarm/addr/addr_test.go | 198 ++++++++++++++ p2p/net/swarm/swarm.go | 11 + p2p/net/swarm/swarm_addr.go | 39 +++ .../{addr_test.go => swarm_addr_test.go} | 39 ++- p2p/net/swarm/swarm_dial.go | 6 + p2p/net/swarm/swarm_listen.go | 5 +- routing/dht/dht.go | 19 +- 12 files changed, 586 insertions(+), 233 deletions(-) delete mode 100644 p2p/net/swarm/addr.go create mode 100644 p2p/net/swarm/addr/addr.go create mode 100644 p2p/net/swarm/addr/addr_test.go create mode 100644 p2p/net/swarm/swarm_addr.go rename p2p/net/swarm/{addr_test.go => swarm_addr_test.go} (65%) diff --git a/core/core.go b/core/core.go index 9408d76ff..fb30730bb 100644 --- a/core/core.go +++ b/core/core.go @@ -24,6 +24,7 @@ import ( p2phost "github.com/jbenet/go-ipfs/p2p/host" p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic" 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" path "github.com/jbenet/go-ipfs/path" pin "github.com/jbenet/go-ipfs/pin" @@ -352,11 +353,16 @@ func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) { // isolates the complex initialization steps func constructPeerHost(ctx context.Context, ctxg ctxgroup.ContextGroup, cfg *config.Config, id peer.ID, ps peer.Peerstore) (p2phost.Host, error) { listenAddrs, err := listenAddresses(cfg) - // make sure we dont error out if our config includes some addresses we cant use. - filteredAddrs := swarm.FilterAddrs(listenAddrs) if err != nil { return nil, debugerror.Wrap(err) } + + // make sure we error out if our config does not have addresses we can use + filteredAddrs := addrutil.FilterAddrs(listenAddrs) + if len(filteredAddrs) < 1 { + return nil, debugerror.Errorf("addresses in config not usable: %s", listenAddrs) + } + network, err := swarm.NewNetwork(ctx, filteredAddrs, id, ps) if err != nil { return nil, debugerror.Wrap(err) diff --git a/p2p/net/conn/dial.go b/p2p/net/conn/dial.go index a56d3d545..a9df7d3c4 100644 --- a/p2p/net/conn/dial.go +++ b/p2p/net/conn/dial.go @@ -22,7 +22,7 @@ func (d *Dialer) String() string { // Example: d.DialAddr(ctx, peer.Addresses()[0], peer) func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (Conn, error) { - network, _, err := manet.DialArgs(raddr) + _, _, err := manet.DialArgs(raddr) if err != nil { return nil, err } @@ -31,17 +31,20 @@ func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) ( return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr) } - var laddr ma.Multiaddr if len(d.LocalAddrs) > 0 { - // laddr := MultiaddrNetMatch(raddr, d.LocalAddrs) - laddr = NetAddress(network, d.LocalAddrs) - if laddr == nil { - return nil, debugerror.Errorf("No local address for network %s", network) + laddrs := manet.AddrMatch(raddr, d.LocalAddrs) + if len(laddrs) < 1 { + return nil, debugerror.Errorf("No local address matches %s %s", raddr, d.LocalAddrs) } - } - // TODO: try to get reusing addr/ports to work. - // d.Dialer.LocalAddr = laddr + // TODO pick with a good heuristic + // we use a random one for now to prevent bad addresses from making nodes unreachable + // with a random selection, multiple tries may work. + // laddr := laddrs[rand.Intn(len(laddrs))] + + // TODO: try to get reusing addr/ports to work. + // d.Dialer.LocalAddr = laddr + } log.Debugf("%s dialing %s %s", d.LocalPeer, remote, raddr) maconn, err := d.Dialer.Dial(raddr) @@ -116,15 +119,3 @@ func MultiaddrNetMatch(tgt ma.Multiaddr, srcs []ma.Multiaddr) ma.Multiaddr { } return nil } - -// NetAddress returns the first Multiaddr found for a given network. -func NetAddress(n string, addrs []ma.Multiaddr) ma.Multiaddr { - for _, a := range addrs { - for _, p := range a.Protocols() { - if p.Name == n { - return a - } - } - } - return nil -} diff --git a/p2p/net/conn/listen.go b/p2p/net/conn/listen.go index bec01d71e..32011651e 100644 --- a/p2p/net/conn/listen.go +++ b/p2p/net/conn/listen.go @@ -102,11 +102,7 @@ func (l *listener) Addr() net.Addr { // If there is an error converting from net.Addr to ma.Multiaddr, // the return value will be nil. func (l *listener) Multiaddr() ma.Multiaddr { - maddr, err := manet.FromNetAddr(l.Addr()) - if err != nil { - return nil // error - } - return maddr + return l.Listener.Multiaddr() } // LocalPeer is the identity of the local Peer. @@ -140,7 +136,7 @@ func Listen(ctx context.Context, addr ma.Multiaddr, local peer.ID, sk ic.PrivKey } l.cg.SetTeardown(l.teardown) - log.Infof("swarm listening on %s", l.Multiaddr()) + log.Debugf("Conn Listener on %s", l.Multiaddr()) log.Event(ctx, "swarmListen", l) return l, nil } diff --git a/p2p/net/swarm/addr.go b/p2p/net/swarm/addr.go deleted file mode 100644 index 497466c57..000000000 --- a/p2p/net/swarm/addr.go +++ /dev/null @@ -1,193 +0,0 @@ -package swarm - -import ( - conn "github.com/jbenet/go-ipfs/p2p/net/conn" - eventlog "github.com/jbenet/go-ipfs/util/eventlog" - - context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" - 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" -) - -// SupportedTransportStrings is the list of supported transports for the swarm. -// These are strings of encapsulated multiaddr protocols. E.g.: -// /ip4/tcp -var SupportedTransportStrings = []string{ - "/ip4/tcp", - "/ip6/tcp", - // "/ip4/udp/utp", disabled because the lib is broken - // "/ip6/udp/utp", disabled because the lib is broken - // "/ip4/udp/udt", disabled because the lib doesnt work on arm - // "/ip6/udp/udt", disabled because the lib doesnt work on arm -} - -// SupportedTransportProtocols is the list of supported transports for the swarm. -// These are []ma.Protocol lists. Populated at runtime from SupportedTransportStrings -var SupportedTransportProtocols = [][]ma.Protocol{} - -func init() { - // initialize SupportedTransportProtocols - transports := make([][]ma.Protocol, len(SupportedTransportStrings)) - for _, s := range SupportedTransportStrings { - t, err := ma.ProtocolsWithString(s) - if err != nil { - panic(err) // important to fix this in the codebase - } - transports = append(transports, t) - } - SupportedTransportProtocols = transports -} - -// FilterAddrs is a filter that removes certain addresses -// from a list. the addresses removed are those known NOT -// to work with swarm. Namely, addresses with UTP. -func FilterAddrs(a []ma.Multiaddr) []ma.Multiaddr { - b := make([]ma.Multiaddr, 0, len(a)) - for _, addr := range a { - if AddrUsable(addr) { - b = append(b, addr) - } - } - return b -} - -// AddrUsable returns whether the swarm can use this addr. -func AddrUsable(a ma.Multiaddr) bool { - // test the address protocol list is in SupportedTransportProtocols - - matches := func(a, b []ma.Protocol) bool { - if len(a) != len(b) { - return false - } - - for i := range a { - if a[i].Code != b[i].Code { - return false - } - } - return true - } - - transport := a.Protocols() - for _, supported := range SupportedTransportProtocols { - if matches(supported, transport) { - return true - } - } - - return false -} - -// ListenAddresses returns a list of addresses at which this swarm listens. -func (s *Swarm) ListenAddresses() []ma.Multiaddr { - listeners := s.swarm.Listeners() - addrs := make([]ma.Multiaddr, 0, len(listeners)) - for _, l := range listeners { - if l2, ok := l.NetListener().(conn.Listener); ok { - addrs = append(addrs, l2.Multiaddr()) - } - } - return addrs -} - -// InterfaceListenAddresses returns a list of addresses at which this swarm -// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to -// use the known local interfaces. -func InterfaceListenAddresses(s *Swarm) ([]ma.Multiaddr, error) { - return resolveUnspecifiedAddresses(s.ListenAddresses()) -} - -// resolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to -// use the known local interfaces. -func resolveUnspecifiedAddresses(unspecifiedAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { - var outputAddrs []ma.Multiaddr - - // todo optimize: only fetch these if we have a "any" addr. - ifaceAddrs, err := interfaceAddresses() - if err != nil { - return nil, err - } - - for _, a := range unspecifiedAddrs { - - // split address into its components - split := ma.Split(a) - - // if first component (ip) is not unspecified, use it as is. - if !manet.IsIPUnspecified(split[0]) { - outputAddrs = append(outputAddrs, a) - continue - } - - // unspecified? add one address per interface. - for _, ia := range ifaceAddrs { - split[0] = ia - joined := ma.Join(split...) - outputAddrs = append(outputAddrs, joined) - } - } - - log.Event(context.TODO(), "interfaceListenAddresses", func() eventlog.Loggable { - var addrs []string - for _, addr := range outputAddrs { - addrs = append(addrs, addr.String()) - } - return eventlog.Metadata{"addresses": addrs} - }()) - log.Debug("InterfaceListenAddresses:", outputAddrs) - return outputAddrs, nil -} - -// interfaceAddresses returns a list of addresses associated with local machine -func interfaceAddresses() ([]ma.Multiaddr, error) { - maddrs, err := manet.InterfaceMultiaddrs() - if err != nil { - return nil, err - } - - var nonLoopback []ma.Multiaddr - for _, a := range maddrs { - if !manet.IsIPLoopback(a) { - nonLoopback = append(nonLoopback, a) - } - } - - return nonLoopback, nil -} - -// addrInList returns whether or not an address is part of a list. -// this is useful to check if NAT is happening (or other bugs?) -func addrInList(addr ma.Multiaddr, list []ma.Multiaddr) bool { - for _, addr2 := range list { - if addr.Equal(addr2) { - return true - } - } - return false -} - -// checkNATWarning checks if our observed addresses differ. if so, -// informs the user that certain things might not work yet -func checkNATWarning(s *Swarm, observed ma.Multiaddr, expected ma.Multiaddr) { - if observed.Equal(expected) { - return - } - - listen, err := InterfaceListenAddresses(s) - if err != nil { - log.Errorf("Error retrieving swarm.InterfaceListenAddresses: %s", err) - return - } - - if !addrInList(observed, listen) { // probably a nat - log.Warningf(natWarning, observed, listen) - } -} - -const natWarning = `Remote peer observed our address to be: %s -The local addresses are: %s -Thus, connection is going through NAT, and other connections may fail. - -IPFS NAT traversal is still under development. Please bug us on github or irc to fix this. -Baby steps: http://jbenet.static.s3.amazonaws.com/271dfcf/baby-steps.gif -` diff --git a/p2p/net/swarm/addr/addr.go b/p2p/net/swarm/addr/addr.go new file mode 100644 index 000000000..91e8febf4 --- /dev/null +++ b/p2p/net/swarm/addr/addr.go @@ -0,0 +1,258 @@ +package addrutil + +import ( + "fmt" + + eventlog "github.com/jbenet/go-ipfs/util/eventlog" + + context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" + 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" +) + +var log = eventlog.Logger("p2p/net/swarm/addr") + +// SupportedTransportStrings is the list of supported transports for the swarm. +// These are strings of encapsulated multiaddr protocols. E.g.: +// /ip4/tcp +var SupportedTransportStrings = []string{ + "/ip4/tcp", + "/ip6/tcp", + // "/ip4/udp/utp", disabled because the lib is broken + // "/ip6/udp/utp", disabled because the lib is broken + // "/ip4/udp/udt", disabled because the lib doesnt work on arm + // "/ip6/udp/udt", disabled because the lib doesnt work on arm +} + +// SupportedTransportProtocols is the list of supported transports for the swarm. +// These are []ma.Protocol lists. Populated at runtime from SupportedTransportStrings +var SupportedTransportProtocols = [][]ma.Protocol{} + +func init() { + // initialize SupportedTransportProtocols + transports := make([][]ma.Protocol, len(SupportedTransportStrings)) + for _, s := range SupportedTransportStrings { + t, err := ma.ProtocolsWithString(s) + if err != nil { + panic(err) // important to fix this in the codebase + } + transports = append(transports, t) + } + SupportedTransportProtocols = transports +} + +// FilterAddrs is a filter that removes certain addresses +// from a list. the addresses removed are those known NOT +// to work with our network. Namely, addresses with UTP. +func FilterAddrs(a []ma.Multiaddr) []ma.Multiaddr { + b := make([]ma.Multiaddr, 0, len(a)) + for _, addr := range a { + if AddrUsable(addr, false) { + b = append(b, addr) + } + } + return b +} + +// AddrOverNonLocalIP returns whether the addr uses a non-local ip link +func AddrOverNonLocalIP(a ma.Multiaddr) bool { + split := ma.Split(a) + if len(split) < 1 { + return false + } + if manet.IsIP6LinkLocal(split[0]) { + return false + } + return true +} + +// AddrUsable returns whether our network can use this addr. +// We only use the transports in SupportedTransportStrings, +// and we do not link local addresses. Loopback is ok +// as we need to be able to connect to multiple ipfs nodes +// in the same machine. +func AddrUsable(a ma.Multiaddr, partial bool) bool { + + if !AddrOverNonLocalIP(a) { + return false + } + + // test the address protocol list is in SupportedTransportProtocols + matches := func(supported, test []ma.Protocol) bool { + if len(test) > len(supported) { + return false + } + + // when partial, it's ok if test < supported. + if !partial && len(supported) != len(test) { + return false + } + + for i := range test { + if supported[i].Code != test[i].Code { + return false + } + } + return true + } + + transport := a.Protocols() + for _, supported := range SupportedTransportProtocols { + if matches(supported, transport) { + return true + } + } + + return false +} + +// ResolveUnspecifiedAddress expands an unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. If ifaceAddr is nil, we request interface addresses +// from the network stack. (this is so you can provide a cached value if resolving many addrs) +func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // split address into its components + split := ma.Split(resolve) + + // if first component (ip) is not unspecified, use it as is. + if !manet.IsIPUnspecified(split[0]) { + return []ma.Multiaddr{resolve}, nil + } + + out := make([]ma.Multiaddr, 0, len(ifaceAddrs)) + for _, ia := range ifaceAddrs { + // must match the first protocol to be resolve. + if ia.Protocols()[0].Code != resolve.Protocols()[0].Code { + continue + } + + split[0] = ia + joined := ma.Join(split...) + out = append(out, joined) + log.Debug("adding resolved addr:", resolve, joined, out) + } + if len(out) < 1 { + return nil, fmt.Errorf("failed to resolve: %s", resolve) + } + return out, nil +} + +// ResolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. +func ResolveUnspecifiedAddresses(unspecAddrs, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + + // todo optimize: only fetch these if we have a "any" addr. + if len(ifaceAddrs) < 1 { + var err error + ifaceAddrs, err = InterfaceAddresses() + if err != nil { + return nil, err + } + // log.Debug("InterfaceAddresses:", ifaceAddrs) + } + + var outputAddrs []ma.Multiaddr + for _, a := range unspecAddrs { + // unspecified? + resolved, err := ResolveUnspecifiedAddress(a, ifaceAddrs) + if err != nil { + continue // optimistic. if we cant resolve anything, we'll know at the bottom. + } + // log.Debug("resolved:", a, resolved) + outputAddrs = append(outputAddrs, resolved...) + } + + if len(outputAddrs) < 1 { + return nil, fmt.Errorf("failed to specify addrs: %s", unspecAddrs) + } + + log.Event(context.TODO(), "interfaceListenAddresses", func() eventlog.Loggable { + var addrs []string + for _, addr := range outputAddrs { + addrs = append(addrs, addr.String()) + } + return eventlog.Metadata{"addresses": addrs} + }()) + + log.Debug("ResolveUnspecifiedAddresses:", unspecAddrs, ifaceAddrs, outputAddrs) + return outputAddrs, nil +} + +// InterfaceAddresses returns a list of addresses associated with local machine +// Note: we do not return link local addresses. IP loopback is ok, because we +// may be connecting to other nodes in the same machine. +func InterfaceAddresses() ([]ma.Multiaddr, error) { + maddrs, err := manet.InterfaceMultiaddrs() + if err != nil { + return nil, err + } + log.Debug("InterfaceAddresses: from manet:", maddrs) + + var out []ma.Multiaddr + for _, a := range maddrs { + if !AddrUsable(a, true) { // partial + // log.Debug("InterfaceAddresses: skipping unusable:", a) + continue + } + + out = append(out, a) + } + + log.Debug("InterfaceAddresses: usable:", out) + return out, nil +} + +// AddrInList returns whether or not an address is part of a list. +// this is useful to check if NAT is happening (or other bugs?) +func AddrInList(addr ma.Multiaddr, list []ma.Multiaddr) bool { + for _, addr2 := range list { + if addr.Equal(addr2) { + return true + } + } + return false +} + +// AddrIsShareableOnWAN returns whether the given address should be shareable on the +// wide area network (wide internet). +func AddrIsShareableOnWAN(addr ma.Multiaddr) bool { + s := ma.Split(addr) + if len(s) < 1 { + return false + } + a := s[0] + if manet.IsIPLoopback(a) || manet.IsIP6LinkLocal(a) || manet.IsIPUnspecified(a) { + return false + } + return manet.IsThinWaist(a) +} + +// WANShareableAddrs filters addresses based on whether they're shareable on WAN +func WANShareableAddrs(inp []ma.Multiaddr) []ma.Multiaddr { + out := make([]ma.Multiaddr, 0, len(inp)) + for _, a := range inp { + if AddrIsShareableOnWAN(a) { + out = append(out, a) + } + } + return out +} + +// CheckNATWarning checks if our observed addresses differ. if so, +// informs the user that certain things might not work yet +func CheckNATWarning(observed, expected ma.Multiaddr, listen []ma.Multiaddr) { + if observed.Equal(expected) { + return + } + + if !AddrInList(observed, listen) { // probably a nat + log.Warningf(natWarning, observed, listen) + } +} + +const natWarning = `Remote peer observed our address to be: %s +The local addresses are: %s +Thus, connection is going through NAT, and other connections may fail. + +IPFS NAT traversal is still under development. Please bug us on github or irc to fix this. +Baby steps: http://jbenet.static.s3.amazonaws.com/271dfcf/baby-steps.gif +` diff --git a/p2p/net/swarm/addr/addr_test.go b/p2p/net/swarm/addr/addr_test.go new file mode 100644 index 000000000..7f355ef5e --- /dev/null +++ b/p2p/net/swarm/addr/addr_test.go @@ -0,0 +1,198 @@ +package addrutil + +import ( + "testing" + + 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" +) + +func newMultiaddr(t *testing.T, s string) ma.Multiaddr { + maddr, err := ma.NewMultiaddr(s) + if err != nil { + t.Fatal(err) + } + return maddr +} + +func TestFilterAddrs(t *testing.T) { + + bad := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234"), // unreliable + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/sctp/1234"), // not in manet + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), // utp is broken + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/udt"), // udt is broken on arm + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), // link local + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), // link local + } + + good := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + } + + goodAndBad := append(good, bad...) + + // test filters + + for _, a := range bad { + if AddrUsable(a, false) { + t.Errorf("addr %s should be unusable", a) + } + if AddrUsable(a, true) { + t.Errorf("addr %s should be unusable", a) + } + } + + for _, a := range good { + if !AddrUsable(a, false) { + t.Errorf("addr %s should be usable", a) + } + if !AddrUsable(a, true) { + t.Errorf("addr %s should be usable", a) + } + } + + subtestAddrsEqual(t, FilterAddrs(bad), []ma.Multiaddr{}) + subtestAddrsEqual(t, FilterAddrs(good), good) + subtestAddrsEqual(t, FilterAddrs(goodAndBad), good) +} + +func subtestAddrsEqual(t *testing.T, a, b []ma.Multiaddr) { + if len(a) != len(b) { + t.Error(t) + } + + in := func(addr ma.Multiaddr, l []ma.Multiaddr) bool { + for _, addr2 := range l { + if addr.Equal(addr2) { + return true + } + } + return false + } + + for _, aa := range a { + if !in(aa, b) { + t.Errorf("%s not in %s", aa, b) + } + } +} + +func TestInterfaceAddrs(t *testing.T) { + addrs, err := InterfaceAddresses() + if err != nil { + t.Fatal(err) + } + + if len(addrs) < 1 { + t.Error("no addresses") + } + + for _, a := range addrs { + if manet.IsIP6LinkLocal(a) { + t.Error("should not return ip link local addresses", a) + } + } + + if len(addrs) < 1 { + t.Error("no good interface addrs") + } +} + +func TestResolvingAddrs(t *testing.T) { + + unspec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/0.0.0.0/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + } + + iface := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + newMultiaddr(t, "/ip6/::1"), + newMultiaddr(t, "/ip6/::f"), + } + + spec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/10.20.30.40/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::f/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + } + + actual, err := ResolveUnspecifiedAddresses(unspec, iface) + if err != nil { + t.Fatal(err) + } + + for i, a := range actual { + if !a.Equal(spec[i]) { + t.Error(a, " != ", spec[i]) + } + } + + ip4u := []ma.Multiaddr{newMultiaddr(t, "/ip4/0.0.0.0")} + ip4i := []ma.Multiaddr{newMultiaddr(t, "/ip4/1.2.3.4")} + + ip6u := []ma.Multiaddr{newMultiaddr(t, "/ip6/::")} + ip6i := []ma.Multiaddr{newMultiaddr(t, "/ip6/::1")} + + if _, err := ResolveUnspecifiedAddress(ip4u[0], ip6i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddress(ip6u[0], ip4i); err == nil { + t.Fatal("should have failed") + } + + if _, err := ResolveUnspecifiedAddresses(ip6u, ip4i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddresses(ip4u, ip6i); err == nil { + t.Fatal("should have failed") + } + +} + +func TestWANShareable(t *testing.T) { + + wanok := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/abcd::1/tcp/1234"), + } + + wanbad := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/0.0.0.0/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::/tcp/1234"), + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), + newMultiaddr(t, "/ip6/fe80::/tcp/1234"), + } + + for _, a := range wanok { + if !AddrIsShareableOnWAN(a) { + t.Error("should be true", a) + } + } + + for _, a := range wanbad { + if AddrIsShareableOnWAN(a) { + t.Error("should be false", a) + } + } + + wanok2 := WANShareableAddrs(wanok) + if len(wanok) != len(wanok2) { + t.Error("should be the same") + } + + wanbad2 := WANShareableAddrs(wanbad) + if len(wanbad2) != 0 { + t.Error("should be zero") + } +} diff --git a/p2p/net/swarm/swarm.go b/p2p/net/swarm/swarm.go index 1f42bdddd..54d53f8d9 100644 --- a/p2p/net/swarm/swarm.go +++ b/p2p/net/swarm/swarm.go @@ -3,7 +3,10 @@ package swarm import ( + "fmt" + inet "github.com/jbenet/go-ipfs/p2p/net" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" peer "github.com/jbenet/go-ipfs/p2p/peer" eventlog "github.com/jbenet/go-ipfs/util/eventlog" @@ -37,6 +40,14 @@ type Swarm struct { func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr, local peer.ID, peers peer.Peerstore) (*Swarm, error) { + if len(listenAddrs) > 0 { + filtered := addrutil.FilterAddrs(listenAddrs) + if len(filtered) < 1 { + return nil, fmt.Errorf("swarm cannot use any addr in: %s", listenAddrs) + } + listenAddrs = filtered + } + s := &Swarm{ swarm: ps.NewSwarm(PSTransport), local: local, diff --git a/p2p/net/swarm/swarm_addr.go b/p2p/net/swarm/swarm_addr.go new file mode 100644 index 000000000..336523152 --- /dev/null +++ b/p2p/net/swarm/swarm_addr.go @@ -0,0 +1,39 @@ +package swarm + +import ( + conn "github.com/jbenet/go-ipfs/p2p/net/conn" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" + + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" +) + +// ListenAddresses returns a list of addresses at which this swarm listens. +func (s *Swarm) ListenAddresses() []ma.Multiaddr { + listeners := s.swarm.Listeners() + addrs := make([]ma.Multiaddr, 0, len(listeners)) + for _, l := range listeners { + if l2, ok := l.NetListener().(conn.Listener); ok { + addrs = append(addrs, l2.Multiaddr()) + } + } + return addrs +} + +// InterfaceListenAddresses returns a list of addresses at which this swarm +// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. +func InterfaceListenAddresses(s *Swarm) ([]ma.Multiaddr, error) { + return addrutil.ResolveUnspecifiedAddresses(s.ListenAddresses(), nil) +} + +// checkNATWarning checks if our observed addresses differ. if so, +// informs the user that certain things might not work yet +func checkNATWarning(s *Swarm, observed ma.Multiaddr, expected ma.Multiaddr) { + listen, err := InterfaceListenAddresses(s) + if err != nil { + log.Errorf("Error retrieving swarm.InterfaceListenAddresses: %s", err) + return + } + + addrutil.CheckNATWarning(observed, expected, listen) +} diff --git a/p2p/net/swarm/addr_test.go b/p2p/net/swarm/swarm_addr_test.go similarity index 65% rename from p2p/net/swarm/addr_test.go rename to p2p/net/swarm/swarm_addr_test.go index 4703cdaf8..65e196efa 100644 --- a/p2p/net/swarm/addr_test.go +++ b/p2p/net/swarm/swarm_addr_test.go @@ -3,6 +3,7 @@ package swarm import ( "testing" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" peer "github.com/jbenet/go-ipfs/p2p/peer" testutil "github.com/jbenet/go-ipfs/util/testutil" @@ -25,6 +26,8 @@ func TestFilterAddrs(t *testing.T) { m("/ip4/1.2.3.4/udp/1234/sctp/1234"), // not in manet m("/ip4/1.2.3.4/udp/1234/utp"), // utp is broken m("/ip4/1.2.3.4/udp/1234/udt"), // udt is broken on arm + m("/ip6/fe80::1/tcp/1234"), // link local + m("/ip6/fe80::100/tcp/1234"), // link local } good := []ma.Multiaddr{ @@ -37,20 +40,20 @@ func TestFilterAddrs(t *testing.T) { // test filters for _, a := range bad { - if AddrUsable(a) { + if addrutil.AddrUsable(a, true) { t.Errorf("addr %s should be unusable", a) } } for _, a := range good { - if !AddrUsable(a) { + if !addrutil.AddrUsable(a, true) { t.Errorf("addr %s should be usable", a) } } - subtestAddrsEqual(t, FilterAddrs(bad), []ma.Multiaddr{}) - subtestAddrsEqual(t, FilterAddrs(good), good) - subtestAddrsEqual(t, FilterAddrs(goodAndBad), good) + subtestAddrsEqual(t, addrutil.FilterAddrs(bad), []ma.Multiaddr{}) + subtestAddrsEqual(t, addrutil.FilterAddrs(good), good) + subtestAddrsEqual(t, addrutil.FilterAddrs(goodAndBad), good) // now test it with swarm @@ -95,3 +98,29 @@ func subtestAddrsEqual(t *testing.T, a, b []ma.Multiaddr) { } } } + +func TestDialBadAddrs(t *testing.T) { + + m := func(s string) ma.Multiaddr { + maddr, err := ma.NewMultiaddr(s) + if err != nil { + t.Fatal(err) + } + return maddr + } + + ctx := context.Background() + s := makeSwarms(ctx, t, 1)[0] + + test := func(a ma.Multiaddr) { + p := testutil.RandPeerIDFatal(t) + s.peers.AddAddress(p, a) + if _, err := s.Dial(ctx, p); err == nil { + t.Error("swarm should not dial: %s", m) + } + } + + test(m("/ip6/fe80::1")) // link local + test(m("/ip6/fe80::100")) // link local + test(m("/ip4/127.0.0.1/udp/1234/utp")) // utp +} diff --git a/p2p/net/swarm/swarm_dial.go b/p2p/net/swarm/swarm_dial.go index dde967fbc..d17d31c8d 100644 --- a/p2p/net/swarm/swarm_dial.go +++ b/p2p/net/swarm/swarm_dial.go @@ -5,6 +5,7 @@ import ( "fmt" conn "github.com/jbenet/go-ipfs/p2p/net/conn" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" peer "github.com/jbenet/go-ipfs/p2p/peer" lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables" @@ -38,6 +39,8 @@ func (s *Swarm) Dial(ctx context.Context, p peer.ID) (*Conn, error) { } remoteAddrs := s.peers.Addresses(p) + // make sure we can use the addresses. + remoteAddrs = addrutil.FilterAddrs(remoteAddrs) if len(remoteAddrs) == 0 { return nil, errors.New("peer has no addresses") } @@ -67,6 +70,9 @@ func (s *Swarm) Dial(ctx context.Context, p peer.ID) (*Conn, error) { if err != nil { return nil, err } + if connC == nil { + err = fmt.Errorf("failed to dial %s", p) + } // ok try to setup the new connection. swarmC, err := dialConnSetup(ctx, s, connC) diff --git a/p2p/net/swarm/swarm_listen.go b/p2p/net/swarm/swarm_listen.go index d6380b9b5..56c889a92 100644 --- a/p2p/net/swarm/swarm_listen.go +++ b/p2p/net/swarm/swarm_listen.go @@ -4,6 +4,7 @@ import ( "fmt" conn "github.com/jbenet/go-ipfs/p2p/net/conn" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" @@ -16,7 +17,7 @@ import ( func (s *Swarm) listen(addrs []ma.Multiaddr) error { for _, addr := range addrs { - if !AddrUsable(addr) { + if !addrutil.AddrUsable(addr, true) { return fmt.Errorf("cannot use addr: %s", addr) } } @@ -59,10 +60,12 @@ func (s *Swarm) setupListener(maddr ma.Multiaddr) error { // may be fine for sk to be nil, just log a warning. log.Warning("Listener not given PrivateKey, so WILL NOT SECURE conns.") } + log.Infof("Swarm Listening at %s", maddr) list, err := conn.Listen(s.cg.Context(), maddr, s.local, sk) if err != nil { return err } + log.Infof("Swarm Listening at %s", s.ListenAddresses()) // AddListener to the peerstream Listener. this will begin accepting connections // and streams! diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 78fef653b..9b1279f10 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -66,8 +66,13 @@ func NewDHT(ctx context.Context, h host.Host, dstore ds.ThreadSafeDatastore) *Ip dht.peerstore = h.Peerstore() dht.ContextGroup = ctxgroup.WithContext(ctx) dht.host = h - h.SetStreamHandler(ProtocolDHT, dht.handleNewStream) + // sanity check. this should **never** happen + if len(dht.peerstore.Addresses(dht.self)) < 1 { + panic("attempt to initialize dht without addresses for self") + } + + h.SetStreamHandler(ProtocolDHT, dht.handleNewStream) dht.providers = NewProviderManager(dht.Context(), dht.self) dht.AddChildGroup(dht.providers) @@ -132,19 +137,23 @@ func (dht *IpfsDHT) putValueToPeer(ctx context.Context, p peer.ID, // can provide the value of 'key' func (dht *IpfsDHT) putProvider(ctx context.Context, p peer.ID, key string) error { - pmes := pb.NewMessage(pb.Message_ADD_PROVIDER, string(key), 0) - // add self as the provider pi := dht.peerstore.PeerInfo(dht.self) - pmes.ProviderPeers = pb.PeerInfosToPBPeers(dht.host.Network(), []peer.PeerInfo{pi}) + // // only share WAN-friendly addresses ?? + // pi.Addrs = addrutil.WANShareableAddrs(pi.Addrs) + if len(pi.Addrs) < 1 { + log.Errorf("%s putProvider: %s for %s error: no wan-friendly addresses", dht.self, p, u.Key(key), pi.Addrs) + return fmt.Errorf("no known addresses for self. cannot put provider.") + } + pmes := pb.NewMessage(pb.Message_ADD_PROVIDER, string(key), 0) + pmes.ProviderPeers = pb.PeerInfosToPBPeers(dht.host.Network(), []peer.PeerInfo{pi}) err := dht.sendMessage(ctx, p, pmes) if err != nil { return err } log.Debugf("%s putProvider: %s for %s (%s)", dht.self, p, u.Key(key), pi.Addrs) - return nil }