mirror of https://github.com/libp2p/go-libp2p.git
Marten Seemann
3 years ago
10 changed files with 781 additions and 0 deletions
@ -0,0 +1,113 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"net" |
||||
|
|
||||
|
"github.com/libp2p/go-reuseport" |
||||
|
ma "github.com/multiformats/go-multiaddr" |
||||
|
manet "github.com/multiformats/go-multiaddr/net" |
||||
|
) |
||||
|
|
||||
|
type dialer interface { |
||||
|
Dial(network, addr string) (net.Conn, error) |
||||
|
DialContext(ctx context.Context, network, addr string) (net.Conn, error) |
||||
|
} |
||||
|
|
||||
|
// Dial dials the given multiaddr, reusing ports we're currently listening on if
|
||||
|
// possible.
|
||||
|
//
|
||||
|
// Dial attempts to be smart about choosing the source port. For example, If
|
||||
|
// we're dialing a loopback address and we're listening on one or more loopback
|
||||
|
// ports, Dial will randomly choose one of the loopback ports and addresses and
|
||||
|
// reuse it.
|
||||
|
func (t *Transport) Dial(raddr ma.Multiaddr) (manet.Conn, error) { |
||||
|
return t.DialContext(context.Background(), raddr) |
||||
|
} |
||||
|
|
||||
|
// DialContext is like Dial but takes a context.
|
||||
|
func (t *Transport) DialContext(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { |
||||
|
network, addr, err := manet.DialArgs(raddr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
var d dialer |
||||
|
switch network { |
||||
|
case "tcp4": |
||||
|
d = t.v4.getDialer(network) |
||||
|
case "tcp6": |
||||
|
d = t.v6.getDialer(network) |
||||
|
default: |
||||
|
return nil, ErrWrongProto |
||||
|
} |
||||
|
conn, err := d.DialContext(ctx, network, addr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
maconn, err := manet.WrapNetConn(conn) |
||||
|
if err != nil { |
||||
|
conn.Close() |
||||
|
return nil, err |
||||
|
} |
||||
|
return maconn, nil |
||||
|
} |
||||
|
|
||||
|
func (n *network) getDialer(network string) dialer { |
||||
|
n.mu.RLock() |
||||
|
d := n.dialer |
||||
|
n.mu.RUnlock() |
||||
|
if d == nil { |
||||
|
n.mu.Lock() |
||||
|
defer n.mu.Unlock() |
||||
|
|
||||
|
if n.dialer == nil { |
||||
|
n.dialer = n.makeDialer(network) |
||||
|
} |
||||
|
d = n.dialer |
||||
|
} |
||||
|
return d |
||||
|
} |
||||
|
|
||||
|
func (n *network) makeDialer(network string) dialer { |
||||
|
if !reuseport.Available() { |
||||
|
log.Debug("reuseport not available") |
||||
|
return &net.Dialer{} |
||||
|
} |
||||
|
|
||||
|
var unspec net.IP |
||||
|
switch network { |
||||
|
case "tcp4": |
||||
|
unspec = net.IPv4zero |
||||
|
case "tcp6": |
||||
|
unspec = net.IPv6unspecified |
||||
|
default: |
||||
|
panic("invalid network: must be either tcp4 or tcp6") |
||||
|
} |
||||
|
|
||||
|
// How many ports are we listening on.
|
||||
|
var port = 0 |
||||
|
for l := range n.listeners { |
||||
|
newPort := l.Addr().(*net.TCPAddr).Port |
||||
|
switch { |
||||
|
case newPort == 0: // Any port, ignore (really, we shouldn't get this case...).
|
||||
|
case port == 0: // Haven't selected a port yet, choose this one.
|
||||
|
port = newPort |
||||
|
case newPort == port: // Same as the selected port, continue...
|
||||
|
default: // Multiple ports, use the multi dialer
|
||||
|
return newMultiDialer(unspec, n.listeners) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// None.
|
||||
|
if port == 0 { |
||||
|
return &net.Dialer{} |
||||
|
} |
||||
|
|
||||
|
// One. Always dial from the single port we're listening on.
|
||||
|
laddr := &net.TCPAddr{ |
||||
|
IP: unspec, |
||||
|
Port: port, |
||||
|
} |
||||
|
|
||||
|
return (*singleDialer)(laddr) |
||||
|
} |
@ -0,0 +1,80 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"net" |
||||
|
|
||||
|
"github.com/libp2p/go-reuseport" |
||||
|
ma "github.com/multiformats/go-multiaddr" |
||||
|
manet "github.com/multiformats/go-multiaddr/net" |
||||
|
) |
||||
|
|
||||
|
type listener struct { |
||||
|
manet.Listener |
||||
|
network *network |
||||
|
} |
||||
|
|
||||
|
func (l *listener) Close() error { |
||||
|
l.network.mu.Lock() |
||||
|
delete(l.network.listeners, l) |
||||
|
l.network.dialer = nil |
||||
|
l.network.mu.Unlock() |
||||
|
return l.Listener.Close() |
||||
|
} |
||||
|
|
||||
|
// Listen listens on the given multiaddr.
|
||||
|
//
|
||||
|
// If reuseport is supported, it will be enabled for this listener and future
|
||||
|
// dials from this transport may reuse the port.
|
||||
|
//
|
||||
|
// Note: You can listen on the same multiaddr as many times as you want
|
||||
|
// (although only *one* listener will end up handling the inbound connection).
|
||||
|
func (t *Transport) Listen(laddr ma.Multiaddr) (manet.Listener, error) { |
||||
|
nw, naddr, err := manet.DialArgs(laddr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
var n *network |
||||
|
switch nw { |
||||
|
case "tcp4": |
||||
|
n = &t.v4 |
||||
|
case "tcp6": |
||||
|
n = &t.v6 |
||||
|
default: |
||||
|
return nil, ErrWrongProto |
||||
|
} |
||||
|
|
||||
|
if !reuseport.Available() { |
||||
|
return manet.Listen(laddr) |
||||
|
} |
||||
|
nl, err := reuseport.Listen(nw, naddr) |
||||
|
if err != nil { |
||||
|
return manet.Listen(laddr) |
||||
|
} |
||||
|
|
||||
|
if _, ok := nl.Addr().(*net.TCPAddr); !ok { |
||||
|
nl.Close() |
||||
|
return nil, ErrWrongProto |
||||
|
} |
||||
|
|
||||
|
malist, err := manet.WrapNetListener(nl) |
||||
|
if err != nil { |
||||
|
nl.Close() |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
list := &listener{ |
||||
|
Listener: malist, |
||||
|
network: n, |
||||
|
} |
||||
|
|
||||
|
n.mu.Lock() |
||||
|
defer n.mu.Unlock() |
||||
|
|
||||
|
if n.listeners == nil { |
||||
|
n.listeners = make(map[*listener]struct{}) |
||||
|
} |
||||
|
n.listeners[list] = struct{}{} |
||||
|
n.dialer = nil |
||||
|
|
||||
|
return list, nil |
||||
|
} |
@ -0,0 +1,90 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"math/rand" |
||||
|
"net" |
||||
|
|
||||
|
"github.com/libp2p/go-netroute" |
||||
|
) |
||||
|
|
||||
|
type multiDialer struct { |
||||
|
listeningAddresses []*net.TCPAddr |
||||
|
loopback []*net.TCPAddr |
||||
|
unspecified []*net.TCPAddr |
||||
|
fallback net.TCPAddr |
||||
|
} |
||||
|
|
||||
|
func (d *multiDialer) Dial(network, addr string) (net.Conn, error) { |
||||
|
return d.DialContext(context.Background(), network, addr) |
||||
|
} |
||||
|
|
||||
|
func randAddr(addrs []*net.TCPAddr) *net.TCPAddr { |
||||
|
if len(addrs) > 0 { |
||||
|
return addrs[rand.Intn(len(addrs))] |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// DialContext dials a target addr.
|
||||
|
// Dialing preference is
|
||||
|
// * If there is a listener on the local interface the OS expects to use to route towards addr, use that.
|
||||
|
// * If there is a listener on a loopback address, addr is loopback, use that.
|
||||
|
// * If there is a listener on an undefined address (0.0.0.0 or ::), use that.
|
||||
|
// * Use the fallback IP specified during construction, with a port that's already being listened on, if one exists.
|
||||
|
func (d *multiDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { |
||||
|
tcpAddr, err := net.ResolveTCPAddr(network, addr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
ip := tcpAddr.IP |
||||
|
if !ip.IsLoopback() && !ip.IsGlobalUnicast() { |
||||
|
return nil, fmt.Errorf("undialable IP: %s", ip) |
||||
|
} |
||||
|
|
||||
|
if router, err := netroute.New(); err == nil { |
||||
|
if _, _, preferredSrc, err := router.Route(ip); err == nil { |
||||
|
for _, optAddr := range d.listeningAddresses { |
||||
|
if optAddr.IP.Equal(preferredSrc) { |
||||
|
return reuseDial(ctx, optAddr, network, addr) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ip.IsLoopback() && len(d.loopback) > 0 { |
||||
|
return reuseDial(ctx, randAddr(d.loopback), network, addr) |
||||
|
} |
||||
|
if len(d.unspecified) == 0 { |
||||
|
return reuseDial(ctx, &d.fallback, network, addr) |
||||
|
} |
||||
|
|
||||
|
return reuseDial(ctx, randAddr(d.unspecified), network, addr) |
||||
|
} |
||||
|
|
||||
|
func newMultiDialer(unspec net.IP, listeners map[*listener]struct{}) (m dialer) { |
||||
|
addrs := make([]*net.TCPAddr, 0) |
||||
|
loopback := make([]*net.TCPAddr, 0) |
||||
|
unspecified := make([]*net.TCPAddr, 0) |
||||
|
existingPort := 0 |
||||
|
|
||||
|
for l := range listeners { |
||||
|
addr := l.Addr().(*net.TCPAddr) |
||||
|
addrs = append(addrs, addr) |
||||
|
if addr.IP.IsLoopback() { |
||||
|
loopback = append(loopback, addr) |
||||
|
} else if addr.IP.IsGlobalUnicast() && existingPort == 0 { |
||||
|
existingPort = addr.Port |
||||
|
} else if addr.IP.IsUnspecified() { |
||||
|
unspecified = append(unspecified, addr) |
||||
|
} |
||||
|
} |
||||
|
m = &multiDialer{ |
||||
|
listeningAddresses: addrs, |
||||
|
loopback: loopback, |
||||
|
unspecified: unspecified, |
||||
|
fallback: net.TCPAddr{IP: unspec, Port: existingPort}, |
||||
|
} |
||||
|
return |
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"net" |
||||
|
|
||||
|
reuseport "github.com/libp2p/go-reuseport" |
||||
|
) |
||||
|
|
||||
|
var fallbackDialer net.Dialer |
||||
|
|
||||
|
// Dials using reuseport and then redials normally if that fails.
|
||||
|
func reuseDial(ctx context.Context, laddr *net.TCPAddr, network, raddr string) (con net.Conn, err error) { |
||||
|
if laddr == nil { |
||||
|
return fallbackDialer.DialContext(ctx, network, raddr) |
||||
|
} |
||||
|
|
||||
|
d := net.Dialer{ |
||||
|
LocalAddr: laddr, |
||||
|
Control: reuseport.Control, |
||||
|
} |
||||
|
|
||||
|
con, err = d.DialContext(ctx, network, raddr) |
||||
|
if err == nil { |
||||
|
return con, nil |
||||
|
} |
||||
|
|
||||
|
if reuseErrShouldRetry(err) && ctx.Err() == nil { |
||||
|
// We could have an existing socket open or we could have one
|
||||
|
// stuck in TIME-WAIT.
|
||||
|
log.Debugf("failed to reuse port, will try again with a random port: %s", err) |
||||
|
con, err = fallbackDialer.DialContext(ctx, network, raddr) |
||||
|
} |
||||
|
return con, err |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"net" |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
EADDRINUSE = "address in use" |
||||
|
ECONNREFUSED = "connection refused" |
||||
|
) |
||||
|
|
||||
|
// reuseErrShouldRetry diagnoses whether to retry after a reuse error.
|
||||
|
// if we failed to bind, we should retry. if bind worked and this is a
|
||||
|
// real dial error (remote end didnt answer) then we should not retry.
|
||||
|
func reuseErrShouldRetry(err error) bool { |
||||
|
if err == nil { |
||||
|
return false // hey, it worked! no need to retry.
|
||||
|
} |
||||
|
|
||||
|
// if it's a network timeout error, it's a legitimate failure.
|
||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
e, ok := err.(*net.OpError) |
||||
|
if !ok { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
e1, ok := e.Err.(*os.PathError) |
||||
|
if !ok { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
switch e1.Err.Error() { |
||||
|
case EADDRINUSE: |
||||
|
return true |
||||
|
case ECONNREFUSED: |
||||
|
return false |
||||
|
default: |
||||
|
return true // optimistically default to retry.
|
||||
|
} |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
//go:build !plan9
|
||||
|
// +build !plan9
|
||||
|
|
||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"net" |
||||
|
"syscall" |
||||
|
) |
||||
|
|
||||
|
// reuseErrShouldRetry diagnoses whether to retry after a reuse error.
|
||||
|
// if we failed to bind, we should retry. if bind worked and this is a
|
||||
|
// real dial error (remote end didnt answer) then we should not retry.
|
||||
|
func reuseErrShouldRetry(err error) bool { |
||||
|
if err == nil { |
||||
|
return false // hey, it worked! no need to retry.
|
||||
|
} |
||||
|
|
||||
|
// if it's a network timeout error, it's a legitimate failure.
|
||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
errno, ok := err.(syscall.Errno) |
||||
|
if !ok { // not an errno? who knows what this is. retry.
|
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
switch errno { |
||||
|
case syscall.EADDRINUSE, syscall.EADDRNOTAVAIL: |
||||
|
return true // failure to bind. retry.
|
||||
|
case syscall.ECONNREFUSED: |
||||
|
return false // real dial error
|
||||
|
default: |
||||
|
return true // optimistically default to retry.
|
||||
|
} |
||||
|
} |
@ -0,0 +1,51 @@ |
|||||
|
//go:build !plan9
|
||||
|
// +build !plan9
|
||||
|
|
||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"net" |
||||
|
"syscall" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
type netTimeoutErr struct { |
||||
|
timeout bool |
||||
|
} |
||||
|
|
||||
|
func (e netTimeoutErr) Error() string { |
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
func (e netTimeoutErr) Timeout() bool { |
||||
|
return e.timeout |
||||
|
} |
||||
|
|
||||
|
func (e netTimeoutErr) Temporary() bool { |
||||
|
panic("not checked") |
||||
|
} |
||||
|
|
||||
|
func TestReuseError(t *testing.T) { |
||||
|
var nte1 net.Error = &netTimeoutErr{true} |
||||
|
var nte2 net.Error = &netTimeoutErr{false} |
||||
|
|
||||
|
cases := map[error]bool{ |
||||
|
nil: false, |
||||
|
syscall.EADDRINUSE: true, |
||||
|
syscall.EADDRNOTAVAIL: true, |
||||
|
syscall.ECONNREFUSED: false, |
||||
|
|
||||
|
nte1: false, |
||||
|
nte2: true, // this ones a little weird... we should check neterror.Temporary() too
|
||||
|
|
||||
|
// test 'default' to true
|
||||
|
syscall.EBUSY: true, |
||||
|
} |
||||
|
|
||||
|
for k, v := range cases { |
||||
|
if reuseErrShouldRetry(k) != v { |
||||
|
t.Fatalf("expected %t for %#v", v, k) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"net" |
||||
|
) |
||||
|
|
||||
|
type singleDialer net.TCPAddr |
||||
|
|
||||
|
func (d *singleDialer) Dial(network, address string) (net.Conn, error) { |
||||
|
return d.DialContext(context.Background(), network, address) |
||||
|
} |
||||
|
|
||||
|
func (d *singleDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { |
||||
|
return reuseDial(ctx, (*net.TCPAddr)(d), network, address) |
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
// Package tcpreuse provides a basic transport for automatically (and intelligently) reusing TCP ports.
|
||||
|
//
|
||||
|
// To use, construct a new Transport and configure listeners tr.Listen(...).
|
||||
|
// When dialing (tr.Dial(...)), the transport will attempt to reuse the ports it's currently listening on,
|
||||
|
// choosing the best one depending on the destination address.
|
||||
|
//
|
||||
|
// It is recommended to set set SO_LINGER to 0 for all connections, otherwise
|
||||
|
// reusing the port may fail when re-dialing a recently closed connection.
|
||||
|
// See https://hea-www.harvard.edu/~fine/Tech/addrinuse.html for details.
|
||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"sync" |
||||
|
|
||||
|
logging "github.com/ipfs/go-log/v2" |
||||
|
) |
||||
|
|
||||
|
var log = logging.Logger("reuseport-transport") |
||||
|
|
||||
|
// ErrWrongProto is returned when dialing a protocol other than tcp.
|
||||
|
var ErrWrongProto = errors.New("can only dial TCP over IPv4 or IPv6") |
||||
|
|
||||
|
// Transport is a TCP reuse transport that reuses listener ports.
|
||||
|
// The zero value is safe to use.
|
||||
|
type Transport struct { |
||||
|
v4 network |
||||
|
v6 network |
||||
|
} |
||||
|
|
||||
|
type network struct { |
||||
|
mu sync.RWMutex |
||||
|
listeners map[*listener]struct{} |
||||
|
dialer dialer |
||||
|
} |
@ -0,0 +1,280 @@ |
|||||
|
package tcpreuse |
||||
|
|
||||
|
import ( |
||||
|
"net" |
||||
|
"runtime" |
||||
|
"testing" |
||||
|
|
||||
|
ma "github.com/multiformats/go-multiaddr" |
||||
|
manet "github.com/multiformats/go-multiaddr/net" |
||||
|
) |
||||
|
|
||||
|
var loopbackV4, _ = ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0") |
||||
|
var loopbackV6, _ = ma.NewMultiaddr("/ip6/::1/tcp/0") |
||||
|
var unspecV6, _ = ma.NewMultiaddr("/ip6/::/tcp/0") |
||||
|
var unspecV4, _ = ma.NewMultiaddr("/ip4/0.0.0.0/tcp/0") |
||||
|
|
||||
|
var globalV4 ma.Multiaddr |
||||
|
var globalV6 ma.Multiaddr |
||||
|
|
||||
|
func init() { |
||||
|
addrs, err := manet.InterfaceMultiaddrs() |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
for _, addr := range addrs { |
||||
|
if !manet.IsIP6LinkLocal(addr) && !manet.IsIPLoopback(addr) { |
||||
|
tcp, _ := ma.NewMultiaddr("/tcp/0") |
||||
|
switch addr.Protocols()[0].Code { |
||||
|
case ma.P_IP4: |
||||
|
if globalV4 == nil { |
||||
|
globalV4 = addr.Encapsulate(tcp) |
||||
|
} |
||||
|
case ma.P_IP6: |
||||
|
if globalV6 == nil { |
||||
|
globalV6 = addr.Encapsulate(tcp) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func setLingerZero(c manet.Conn) { |
||||
|
if runtime.GOOS == "darwin" { |
||||
|
c.(interface{ SetLinger(int) error }).SetLinger(0) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func acceptOne(t *testing.T, listener manet.Listener) <-chan manet.Conn { |
||||
|
t.Helper() |
||||
|
done := make(chan manet.Conn, 1) |
||||
|
go func() { |
||||
|
defer close(done) |
||||
|
c, err := listener.Accept() |
||||
|
if err != nil { |
||||
|
t.Error(err) |
||||
|
return |
||||
|
} |
||||
|
setLingerZero(c) |
||||
|
done <- c |
||||
|
}() |
||||
|
return done |
||||
|
} |
||||
|
|
||||
|
func dialOne(t *testing.T, tr *Transport, listener manet.Listener, expected ...int) int { |
||||
|
t.Helper() |
||||
|
|
||||
|
connChan := acceptOne(t, listener) |
||||
|
c, err := tr.Dial(listener.Multiaddr()) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
setLingerZero(c) |
||||
|
port := c.LocalAddr().(*net.TCPAddr).Port |
||||
|
serverConn := <-connChan |
||||
|
serverConn.Close() |
||||
|
c.Close() |
||||
|
if len(expected) == 0 { |
||||
|
return port |
||||
|
} |
||||
|
for _, p := range expected { |
||||
|
if p == port { |
||||
|
return port |
||||
|
} |
||||
|
} |
||||
|
t.Errorf("dialed %s from %v. expected to dial from port %v", listener.Multiaddr(), c.LocalAddr(), expected) |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
func TestNoneAndSingle(t *testing.T) { |
||||
|
var trA Transport |
||||
|
var trB Transport |
||||
|
listenerA, err := trA.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerA.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA) |
||||
|
|
||||
|
listenerB, err := trB.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA, listenerB.Addr().(*net.TCPAddr).Port) |
||||
|
} |
||||
|
|
||||
|
func TestTwoLocal(t *testing.T) { |
||||
|
var trA Transport |
||||
|
var trB Transport |
||||
|
listenerA, err := trA.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerA.Close() |
||||
|
|
||||
|
listenerB1, err := trB.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB1.Close() |
||||
|
|
||||
|
listenerB2, err := trB.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB2.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA, |
||||
|
listenerB1.Addr().(*net.TCPAddr).Port, |
||||
|
listenerB2.Addr().(*net.TCPAddr).Port) |
||||
|
} |
||||
|
|
||||
|
func TestGlobalPreferenceV4(t *testing.T) { |
||||
|
if globalV4 == nil { |
||||
|
t.Skip("no global IPv4 addresses configured") |
||||
|
return |
||||
|
} |
||||
|
t.Logf("when listening on %v, should prefer %v over %v", loopbackV4, loopbackV4, globalV4) |
||||
|
testPrefer(t, loopbackV4, loopbackV4, globalV4) |
||||
|
t.Logf("when listening on %v, should prefer %v over %v", loopbackV4, unspecV4, globalV4) |
||||
|
testPrefer(t, loopbackV4, unspecV4, globalV4) |
||||
|
|
||||
|
t.Logf("when listening on %v, should prefer %v over %v", globalV4, unspecV4, loopbackV4) |
||||
|
testPrefer(t, globalV4, unspecV4, loopbackV4) |
||||
|
} |
||||
|
|
||||
|
func TestGlobalPreferenceV6(t *testing.T) { |
||||
|
if globalV6 == nil { |
||||
|
t.Skip("no global IPv6 addresses configured") |
||||
|
return |
||||
|
} |
||||
|
testPrefer(t, loopbackV6, loopbackV6, globalV6) |
||||
|
testPrefer(t, loopbackV6, unspecV6, globalV6) |
||||
|
|
||||
|
testPrefer(t, globalV6, unspecV6, loopbackV6) |
||||
|
} |
||||
|
|
||||
|
func TestLoopbackPreference(t *testing.T) { |
||||
|
testPrefer(t, loopbackV4, loopbackV4, unspecV4) |
||||
|
testPrefer(t, loopbackV6, loopbackV6, unspecV6) |
||||
|
} |
||||
|
|
||||
|
func testPrefer(t *testing.T, listen, prefer, avoid ma.Multiaddr) { |
||||
|
var trA Transport |
||||
|
var trB Transport |
||||
|
listenerA, err := trA.Listen(listen) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerA.Close() |
||||
|
|
||||
|
listenerB1, err := trB.Listen(avoid) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB1.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA, listenerB1.Addr().(*net.TCPAddr).Port) |
||||
|
|
||||
|
listenerB2, err := trB.Listen(prefer) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB2.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA, listenerB2.Addr().(*net.TCPAddr).Port) |
||||
|
|
||||
|
// Closing the listener should reset the dialer.
|
||||
|
listenerB2.Close() |
||||
|
|
||||
|
dialOne(t, &trB, listenerA, listenerB1.Addr().(*net.TCPAddr).Port) |
||||
|
} |
||||
|
|
||||
|
func TestV6V4(t *testing.T) { |
||||
|
if runtime.GOOS == "darwin" { |
||||
|
t.Skip("This test is failing on OSX: https://github.com/libp2p/go-reuseport-transport/issues/40") |
||||
|
} |
||||
|
testUseFirst(t, loopbackV4, loopbackV4, loopbackV6) |
||||
|
testUseFirst(t, loopbackV6, loopbackV6, loopbackV4) |
||||
|
} |
||||
|
|
||||
|
func TestGlobalToGlobal(t *testing.T) { |
||||
|
if globalV4 == nil { |
||||
|
t.Skip("no globalV4 addresses configured") |
||||
|
return |
||||
|
} |
||||
|
testUseFirst(t, globalV4, globalV4, loopbackV4) |
||||
|
testUseFirst(t, globalV6, globalV6, loopbackV6) |
||||
|
} |
||||
|
|
||||
|
func testUseFirst(t *testing.T, listen, use, never ma.Multiaddr) { |
||||
|
var trA Transport |
||||
|
var trB Transport |
||||
|
listenerA, err := trA.Listen(globalV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerA.Close() |
||||
|
|
||||
|
listenerB1, err := trB.Listen(loopbackV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB1.Close() |
||||
|
|
||||
|
// It works (random port)
|
||||
|
dialOne(t, &trB, listenerA) |
||||
|
|
||||
|
listenerB2, err := trB.Listen(globalV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB2.Close() |
||||
|
|
||||
|
// Uses globalV4 port.
|
||||
|
dialOne(t, &trB, listenerA, listenerB2.Addr().(*net.TCPAddr).Port) |
||||
|
|
||||
|
// Closing the listener should reset the dialer.
|
||||
|
listenerB2.Close() |
||||
|
|
||||
|
// It still works.
|
||||
|
dialOne(t, &trB, listenerA) |
||||
|
} |
||||
|
|
||||
|
func TestDuplicateGlobal(t *testing.T) { |
||||
|
if globalV4 == nil { |
||||
|
t.Skip("no globalV4 addresses configured") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
var trA Transport |
||||
|
var trB Transport |
||||
|
listenerA, err := trA.Listen(globalV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerA.Close() |
||||
|
|
||||
|
listenerB1, err := trB.Listen(globalV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB1.Close() |
||||
|
|
||||
|
listenerB2, err := trB.Listen(globalV4) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
defer listenerB2.Close() |
||||
|
|
||||
|
// Check which port we're using
|
||||
|
port := dialOne(t, &trB, listenerA) |
||||
|
|
||||
|
// Check consistency
|
||||
|
for i := 0; i < 10; i++ { |
||||
|
dialOne(t, &trB, listenerA, port) |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue