reuseport.go 1.5 KB
Newer Older
Steven Allen's avatar
Steven Allen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
package tcpreuse

import (
	"context"
	"net"
	"syscall"

	reuseport "github.com/libp2p/go-reuseport"
)

// 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.
	}
}

// Dials using reusport and then redials normally if that fails.
func reuseDial(ctx context.Context, laddr *net.TCPAddr, network, raddr string) (net.Conn, error) {
	if laddr == nil {
		var d net.Dialer
		return d.DialContext(ctx, network, raddr)
	}

	d := reuseport.Dialer{
		D: net.Dialer{
			LocalAddr: laddr,
		},
	}

	con, err := d.DialContext(ctx, network, raddr)
53 54
	if err == nil {
		return con, nil
Steven Allen's avatar
Steven Allen committed
55 56 57
	}

	if ReuseErrShouldRetry(err) && ctx.Err() == nil {
58
		log.Debugf("failed to reuse port, dialing with a random port: %s", err)
Steven Allen's avatar
Steven Allen committed
59 60 61 62 63
		var d net.Dialer
		con, err = d.DialContext(ctx, network, raddr)
	}
	return con, err
}