reuseport.go 1.63 KB
Newer Older
Steven Allen's avatar
Steven Allen committed
1 2 3 4 5 6 7 8 9 10
package tcpreuse

import (
	"context"
	"net"
	"syscall"

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

Steven Allen's avatar
Steven Allen committed
11 12
var fallbackDialer net.Dialer

Steven Allen's avatar
Steven Allen committed
13
// reuseErrShouldRetry diagnoses whether to retry after a reuse error.
Steven Allen's avatar
Steven Allen committed
14 15
// 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.
Steven Allen's avatar
Steven Allen committed
16
func reuseErrShouldRetry(err error) bool {
Steven Allen's avatar
Steven Allen committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
	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.
	}
}

Matt Joiner's avatar
Matt Joiner committed
41 42
// 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) {
Steven Allen's avatar
Steven Allen committed
43
	if laddr == nil {
Steven Allen's avatar
Steven Allen committed
44
		return fallbackDialer.DialContext(ctx, network, raddr)
Steven Allen's avatar
Steven Allen committed
45 46
	}

Matt Joiner's avatar
Matt Joiner committed
47 48 49
	d := net.Dialer{
		LocalAddr: laddr,
		Control:   reuseport.Control,
Steven Allen's avatar
Steven Allen committed
50 51
	}

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

Steven Allen's avatar
Steven Allen committed
57
	if reuseErrShouldRetry(err) && ctx.Err() == nil {
Steven Allen's avatar
Steven Allen committed
58 59
		// We could have an existing socket open or we could have one
		// stuck in TIME-WAIT.
Steven Allen's avatar
Steven Allen committed
60
		log.Debugf("failed to reuse port, will try again with a random port: %s", err)
Steven Allen's avatar
Steven Allen committed
61
		con, err = fallbackDialer.DialContext(ctx, network, raddr)
Steven Allen's avatar
Steven Allen committed
62 63 64
	}
	return con, err
}