diff --git a/p2p/net/conn/dial.go b/p2p/net/conn/dial.go index a9df7d3c4..9c4b78431 100644 --- a/p2p/net/conn/dial.go +++ b/p2p/net/conn/dial.go @@ -2,11 +2,14 @@ package conn import ( "fmt" + "math/rand" + "net" "strings" 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" + reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport" peer "github.com/jbenet/go-ipfs/p2p/peer" debugerror "github.com/jbenet/go-ipfs/util/debugerror" @@ -22,32 +25,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) { - _, _, err := manet.DialArgs(raddr) - if err != nil { - return nil, err - } - - if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") { - return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr) - } - - if len(d.LocalAddrs) > 0 { - laddrs := manet.AddrMatch(raddr, d.LocalAddrs) - if len(laddrs) < 1 { - return nil, debugerror.Errorf("No local address matches %s %s", raddr, d.LocalAddrs) - } - - // 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) + maconn, err := d.rawConnDial(ctx, raddr, remote) if err != nil { return nil, err } @@ -92,6 +70,78 @@ func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) ( return connOut, errOut } +// rawConnDial dials the underlying net.Conn + manet.Conns +func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (manet.Conn, error) { + + // before doing anything, check we're going to be able to dial. + // we may not support the given address. + _, _, err := manet.DialArgs(raddr) + if err != nil { + return nil, err + } + + if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") { + return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr) + } + + // get local addr to use. + laddr := pickLocalAddr(d.LocalAddrs, raddr) + + log.Debugf("%s dialing %s -- %s --> %s", d.LocalPeer, remote, laddr, raddr) + if laddr != nil { + // dial using reuseport.Dialer, because we're probably reusing addrs. + // this is optimistic, as the reuseDial may fail to bind the port. + log.Debugf("trying to reuse: %s", laddr) + if nconn, err := d.reuseDial(laddr, raddr); err == nil { + // if it worked, wrap the raw net.Conn with our manet.Conn + log.Debugf("reuse worked! %s %s %s", laddr, nconn.RemoteAddr(), nconn) + return manet.WrapNetConn(nconn) + } + // if not, we fall back to regular Dial without a local addr specified. + } + + // no local addr, or failed to reuse. just dial straight with a new port. + return d.Dialer.Dial(raddr) +} + +func (d *Dialer) reuseDial(laddr, raddr ma.Multiaddr) (net.Conn, error) { + // give reuse.Dialer the manet.Dialer's Dialer. + // (wow, Dialer should've so been an interface...) + rd := reuseport.Dialer{d.Dialer.Dialer} + + // get the local net.Addr manually + var err error + rd.D.LocalAddr, err = manet.ToNetAddr(laddr) + if err != nil { + return nil, err + } + + // get the raddr dial args for rd.dial + network, netraddr, err := manet.DialArgs(raddr) + if err != nil { + return nil, err + } + + // rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set. + return rd.Dial(network, netraddr) +} + +func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiaddr) { + if len(laddrs) < 1 { + return nil + } + + laddrs = manet.AddrMatch(raddr, laddrs) + if len(laddrs) < 1 { + return nil + } + + // 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. + return laddrs[rand.Intn(len(laddrs))] +} + // MultiaddrProtocolsMatch returns whether two multiaddrs match in protocol stacks. func MultiaddrProtocolsMatch(a, b ma.Multiaddr) bool { ap := a.Protocols() diff --git a/p2p/net/conn/listen.go b/p2p/net/conn/listen.go index 32011651e..05d3c229e 100644 --- a/p2p/net/conn/listen.go +++ b/p2p/net/conn/listen.go @@ -9,7 +9,9 @@ import ( ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup" 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" + reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport" tec "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher" + ic "github.com/jbenet/go-ipfs/p2p/crypto" peer "github.com/jbenet/go-ipfs/p2p/peer" ) @@ -123,11 +125,23 @@ func (l *listener) Loggable() map[string]interface{} { // Listen listens on the particular multiaddr, with given peer and peerstore. func Listen(ctx context.Context, addr ma.Multiaddr, local peer.ID, sk ic.PrivKey) (Listener, error) { - ml, err := manet.Listen(addr) + network, naddr, err := manet.DialArgs(addr) + if err != nil { + return nil, err + } + + // _ := reuseport.Listen + // ml, err := manet.Listen(addr) + nl, err := reuseport.Listen(network, naddr) if err != nil { return nil, fmt.Errorf("Failed to listen on %s: %s", addr, err) } + ml, err := manet.WrapNetListener(nl) + if err != nil { + return nil, err + } + l := &listener{ Listener: ml, local: local,