X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=socket.go;h=2d4ea863ac292f39e4d97effd297fe2b98afcfa5;hb=HEAD;hp=4de2592a094e59b52cbeb6f2685f5c97c4aab573;hpb=48fa9b59fd8387d2497286943872dd917e0729a5;p=btrtrc.git diff --git a/socket.go b/socket.go index 4de2592a..2d4ea863 100644 --- a/socket.go +++ b/socket.go @@ -4,7 +4,9 @@ import ( "context" "net" "strconv" + "syscall" + "github.com/anacrolix/log" "github.com/anacrolix/missinggo/perf" "github.com/anacrolix/missinggo/v2" "github.com/pkg/errors" @@ -24,26 +26,84 @@ type socket interface { Close() error } -func listen(n network, addr string, f firewallCallback) (socket, error) { +func listen(n network, addr string, f firewallCallback, logger log.Logger) (socket, error) { switch { case n.Tcp: return listenTcp(n.String(), addr) case n.Udp: - return listenUtp(n.String(), addr, f) + return listenUtp(n.String(), addr, f, logger) default: panic(n) } } +// Dialing TCP from a local port limits us to a single outgoing TCP connection to each remote +// client. Instead, this should be a last resort if we need to use holepunching, and only then to +// connect to other clients that actually try to holepunch TCP. +const dialTcpFromListenPort = false + +var tcpListenConfig = net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) (err error) { + controlErr := c.Control(func(fd uintptr) { + if dialTcpFromListenPort { + err = setReusePortSockOpts(fd) + } + }) + if err != nil { + return + } + err = controlErr + return + }, + // BitTorrent connections manage their own keep-alives. + KeepAlive: -1, +} + func listenTcp(network, address string) (s socket, err error) { - l, err := net.Listen(network, address) - return tcpSocket{ + l, err := tcpListenConfig.Listen(context.Background(), network, address) + if err != nil { + return + } + netDialer := net.Dialer{ + // We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction ourselves, + // although it's probably not triggered as I think the network is already constrained to + // tcp4 or tcp6 at this point. + FallbackDelay: -1, + // BitTorrent connections manage their own keepalives. + KeepAlive: tcpListenConfig.KeepAlive, + Control: func(network, address string, c syscall.RawConn) (err error) { + controlErr := c.Control(func(fd uintptr) { + err = setSockNoLinger(fd) + if err != nil { + // Failing to disable linger is undesirable, but not fatal. + log.Levelf(log.Debug, "error setting linger socket option on tcp socket: %v", err) + err = nil + } + // This is no longer required I think, see + // https://github.com/anacrolix/torrent/discussions/856. I added this originally to + // allow dialling out from the client's listen port, but that doesn't really work. I + // think Linux older than ~2013 doesn't support SO_REUSEPORT. + if dialTcpFromListenPort { + err = setReusePortSockOpts(fd) + } + }) + if err == nil { + err = controlErr + } + return + }, + } + if dialTcpFromListenPort { + netDialer.LocalAddr = l.Addr() + } + s = tcpSocket{ Listener: l, NetworkDialer: NetworkDialer{ Network: network, - Dialer: DefaultNetDialer, + Dialer: &netDialer, }, - }, err + } + return } type tcpSocket struct { @@ -51,7 +111,7 @@ type tcpSocket struct { NetworkDialer } -func listenAll(networks []network, getHost func(string) string, port int, f firewallCallback) ([]socket, error) { +func listenAll(networks []network, getHost func(string) string, port int, f firewallCallback, logger log.Logger) ([]socket, error) { if len(networks) == 0 { return nil, nil } @@ -60,7 +120,7 @@ func listenAll(networks []network, getHost func(string) string, port int, f fire nahs = append(nahs, networkAndHost{n, getHost(n.String())}) } for { - ss, retry, err := listenAllRetry(nahs, port, f) + ss, retry, err := listenAllRetry(nahs, port, f, logger) if !retry { return ss, err } @@ -72,10 +132,10 @@ type networkAndHost struct { Host string } -func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback) (ss []socket, retry bool, err error) { +func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback, logger log.Logger) (ss []socket, retry bool, err error) { ss = make([]socket, 1, len(nahs)) portStr := strconv.FormatInt(int64(port), 10) - ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), f) + ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), f, logger) if err != nil { return nil, false, errors.Wrap(err, "first listen") } @@ -89,7 +149,7 @@ func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback) (ss []s }() portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10) for _, nah := range nahs[1:] { - s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f) + s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger) if err != nil { return ss, missinggo.IsAddrInUse(err) && port == 0, @@ -103,8 +163,8 @@ func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback) (ss []s // This isn't aliased from go-libutp since that assumes CGO. type firewallCallback func(net.Addr) bool -func listenUtp(network, addr string, fc firewallCallback) (socket, error) { - us, err := NewUtpSocket(network, addr, fc) +func listenUtp(network, addr string, fc firewallCallback, logger log.Logger) (socket, error) { + us, err := NewUtpSocket(network, addr, fc, logger) return utpSocketSocket{us, network}, err }