reuseport.go 1.83 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 43
// Dials using reuseport and then redials normally if that fails.
// TODO(anacrolix): This shouldn't fail anymore: Remove fallback.
func reuseDial(ctx context.Context, laddr *net.TCPAddr, network, raddr string) (con net.Conn, err error) {
Steven Allen's avatar
Steven Allen committed
44
	if laddr == nil {
Steven Allen's avatar
Steven Allen committed
45
		return fallbackDialer.DialContext(ctx, network, raddr)
Steven Allen's avatar
Steven Allen committed
46 47
	}

Matt Joiner's avatar
Matt Joiner committed
48 49 50
	d := net.Dialer{
		LocalAddr: laddr,
		Control:   reuseport.Control,
Steven Allen's avatar
Steven Allen committed
51
	}
Matt Joiner's avatar
Matt Joiner committed
52 53 54 55 56 57 58 59 60
	defer func() {
		if err != nil {
			return
		}
		// This is transplanted from go-reuseport, which once set no linger on
		// dialing and may be a requirement for desired behaviour in this
		// package.
		con.(*net.TCPConn).SetLinger(0)
	}()
Steven Allen's avatar
Steven Allen committed
61

Matt Joiner's avatar
Matt Joiner committed
62
	con, err = d.DialContext(ctx, network, raddr)
63 64
	if err == nil {
		return con, nil
Steven Allen's avatar
Steven Allen committed
65 66
	}

Steven Allen's avatar
Steven Allen committed
67
	if reuseErrShouldRetry(err) && ctx.Err() == nil {
68
		log.Debugf("failed to reuse port, dialing with a random port: %s", err)
Steven Allen's avatar
Steven Allen committed
69
		con, err = fallbackDialer.DialContext(ctx, network, raddr)
Steven Allen's avatar
Steven Allen committed
70 71 72
	}
	return con, err
}