Skip to content

Commit

Permalink
net: add KeepAliveConfig and implement SetKeepAliveConfig
Browse files Browse the repository at this point in the history
Fixes #62254
Fixes #48622

Change-Id: Ida598e7fa914c8737fdbc1c813bcd68adb5119c3
Reviewed-on: https://go-review.googlesource.com/c/go/+/542275
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Andy Pan <panjf2000@gmail.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
panjf2000 authored and gopherbot committed Feb 20, 2024
1 parent aaf8e84 commit d42cd45
Show file tree
Hide file tree
Showing 32 changed files with 1,131 additions and 66 deletions.
12 changes: 12 additions & 0 deletions api/next/62254.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pkg net, method (*TCPConn) SetKeepAliveConfig(KeepAliveConfig) error #62254
pkg net, type Dialer struct, KeepAliveConfig KeepAliveConfig #62254
pkg net, type KeepAliveConfig struct #62254
pkg net, type KeepAliveConfig struct, Count int #62254
pkg net, type KeepAliveConfig struct, Enable bool #62254
pkg net, type KeepAliveConfig struct, Idle time.Duration #62254
pkg net, type KeepAliveConfig struct, Interval time.Duration #62254
pkg net, type ListenConfig struct, KeepAliveConfig KeepAliveConfig #62254
pkg syscall (windows-386), const WSAENOPROTOOPT = 10042 #62254
pkg syscall (windows-386), const WSAENOPROTOOPT Errno #62254
pkg syscall (windows-amd64), const WSAENOPROTOOPT = 10042 #62254
pkg syscall (windows-amd64), const WSAENOPROTOOPT Errno #62254
4 changes: 4 additions & 0 deletions doc/next/6-stdlib/99-minor/net/62254.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The new type [`KeepAliveConfig`](/net#KeepAliveConfig) permits fine-tuning
the keep-alive options for TCP connections, via a new
[`TCPConn.SetKeepAliveConfig`](/net#TCPConn.SetKeepAliveConfig) method and
new KeepAliveConfig fields for [`Dialer`](net#Dialer) and [`ListenConfig`](net#ListenConfig).
1 change: 1 addition & 0 deletions doc/next/6-stdlib/99-minor/syscall (windows-386)/62254.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The syscall package now defines WSAENOPROTOOPT on Windows.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See `syscall (windows-386)/62254.md`.
45 changes: 38 additions & 7 deletions src/net/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@ import (
)

const (
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See go.dev/issue/31510
defaultTCPKeepAlive = 15 * time.Second
// defaultTCPKeepAliveIdle is a default constant value for TCP_KEEPIDLE.
// See go.dev/issue/31510 for details.
defaultTCPKeepAliveIdle = 15 * time.Second

// defaultTCPKeepAliveInterval is a default constant value for TCP_KEEPINTVL.
// It is the same as defaultTCPKeepAliveIdle, see go.dev/issue/31510 for details.
defaultTCPKeepAliveInterval = 15 * time.Second

// defaultTCPKeepAliveCount is a default constant value for TCP_KEEPCNT.
defaultTCPKeepAliveCount = 9

// For the moment, MultiPath TCP is not used by default
// See go.dev/issue/56539
Expand Down Expand Up @@ -116,13 +123,25 @@ type Dialer struct {

// KeepAlive specifies the interval between keep-alive
// probes for an active network connection.
//
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
//
// If zero, keep-alive probes are sent with a default value
// (currently 15 seconds), if supported by the protocol and operating
// system. Network protocols or operating systems that do
// not support keep-alives ignore this field.
// not support keep-alive ignore this field.
// If negative, keep-alive probes are disabled.
KeepAlive time.Duration

// KeepAliveConfig specifies the keep-alive probe configuration
// for an active network connection, when supported by the
// protocol and operating system.
//
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
// keep-alive probes are disabled.
KeepAliveConfig KeepAliveConfig

// Resolver optionally specifies an alternate resolver to use.
Resolver *Resolver

Expand Down Expand Up @@ -680,12 +699,24 @@ type ListenConfig struct {

// KeepAlive specifies the keep-alive period for network
// connections accepted by this listener.
// If zero, keep-alives are enabled if supported by the protocol
//
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
//
// If zero, keep-alive are enabled if supported by the protocol
// and operating system. Network protocols or operating systems
// that do not support keep-alives ignore this field.
// If negative, keep-alives are disabled.
// that do not support keep-alive ignore this field.
// If negative, keep-alive are disabled.
KeepAlive time.Duration

// KeepAliveConfig specifies the keep-alive probe configuration
// for an active network connection, when supported by the
// protocol and operating system.
//
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
// keep-alive probes are disabled.
KeepAliveConfig KeepAliveConfig

// If mptcpStatus is set to a value allowing Multipath TCP (MPTCP) to be
// used, any call to Listen with "tcp(4|6)" as network will use MPTCP if
// supported by the operating system.
Expand Down
18 changes: 13 additions & 5 deletions src/net/dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,10 @@ func TestDialerDualStack(t *testing.T) {
}

func TestDialerKeepAlive(t *testing.T) {
t.Cleanup(func() {
testHookSetKeepAlive = func(KeepAliveConfig) {}
})

handler := func(ls *localServer, ln Listener) {
for {
c, err := ln.Accept()
Expand All @@ -699,26 +703,30 @@ func TestDialerKeepAlive(t *testing.T) {
c.Close()
}
}
ls := newLocalServer(t, "tcp")
ln := newLocalListener(t, "tcp", &ListenConfig{
KeepAlive: -1, // prevent calling hook from accepting
})
ls := (&streamListener{Listener: ln}).newLocalServer()
defer ls.teardown()
if err := ls.buildup(handler); err != nil {
t.Fatal(err)
}
defer func() { testHookSetKeepAlive = func(time.Duration) {} }()

tests := []struct {
ka time.Duration
expected time.Duration
}{
{-1, -1},
{0, 15 * time.Second},
{0, 0},
{5 * time.Second, 5 * time.Second},
{30 * time.Second, 30 * time.Second},
}

var got time.Duration = -1
testHookSetKeepAlive = func(cfg KeepAliveConfig) { got = cfg.Idle }

for _, test := range tests {
var got time.Duration = -1
testHookSetKeepAlive = func(d time.Duration) { got = d }
got = -1
d := Dialer{KeepAlive: test.ka}
c, err := d.Dial("tcp", ls.Listener.Addr().String())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion src/net/file_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func fileConn(f *os.File) (Conn, error) {

switch fd.laddr.(type) {
case *TCPAddr:
return newTCPConn(fd, defaultTCPKeepAlive, testHookSetKeepAlive), nil
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
case *UDPAddr:
return newUDPConn(fd), nil
}
Expand Down
2 changes: 1 addition & 1 deletion src/net/file_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func fileConn(f *os.File) (Conn, error) {
}
switch fd.laddr.(type) {
case *TCPAddr:
return newTCPConn(fd, defaultTCPKeepAlive, testHookSetKeepAlive), nil
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
case *UDPAddr:
return newUDPConn(fd), nil
case *IPAddr:
Expand Down
4 changes: 2 additions & 2 deletions src/net/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package net

import (
"context"
"time"
)

var (
Expand All @@ -21,7 +20,8 @@ var (
) ([]IPAddr, error) {
return fn(ctx, network, host)
}
testHookSetKeepAlive = func(time.Duration) {}
testPreHookSetKeepAlive = func(*netFD) {}
testHookSetKeepAlive = func(KeepAliveConfig) {}

// testHookStepTime sleeps until time has moved forward by a nonzero amount.
// This helps to avoid flakes in timeout tests by ensuring that an implausibly
Expand Down
7 changes: 1 addition & 6 deletions src/net/mockserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,7 @@ func newLocalListener(t testing.TB, network string, lcOpt ...*ListenConfig) List
switch network {
case "tcp":
if supportsIPv4() {
if !supportsIPv6() {
return listen("tcp4", "127.0.0.1:0")
}
if ln, err := Listen("tcp4", "127.0.0.1:0"); err == nil {
return ln
}
return listen("tcp4", "127.0.0.1:0")
}
if supportsIPv6() {
return listen("tcp6", "[::1]:0")
Expand Down
102 changes: 102 additions & 0 deletions src/net/tcpconn_keepalive_conf_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build aix || freebsd || linux || netbsd || darwin || dragonfly

package net

import "time"

var testConfigs = []KeepAliveConfig{
{
Enable: true,
Idle: 5 * time.Second,
Interval: 3 * time.Second,
Count: 10,
},
{
Enable: true,
Idle: 0,
Interval: 0,
Count: 0,
},
{
Enable: true,
Idle: -1,
Interval: -1,
Count: -1,
},
{
Enable: true,
Idle: -1,
Interval: 3 * time.Second,
Count: 10,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: -1,
Count: 10,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: 3 * time.Second,
Count: -1,
},
{
Enable: true,
Idle: -1,
Interval: -1,
Count: 10,
},
{
Enable: true,
Idle: -1,
Interval: 3 * time.Second,
Count: -1,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: -1,
Count: -1,
},
{
Enable: true,
Idle: 0,
Interval: 3 * time.Second,
Count: 10,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: 0,
Count: 10,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: 3 * time.Second,
Count: 0,
},
{
Enable: true,
Idle: 0,
Interval: 0,
Count: 10,
},
{
Enable: true,
Idle: 0,
Interval: 3 * time.Second,
Count: 0,
},
{
Enable: true,
Idle: 5 * time.Second,
Interval: 0,
Count: 0,
},
}
92 changes: 92 additions & 0 deletions src/net/tcpconn_keepalive_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin

package net

import (
"syscall"
"testing"
"time"
)

func getCurrentKeepAliveSettings(fd int) (cfg KeepAliveConfig, err error) {
tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
if err != nil {
return
}
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE)
if err != nil {
return
}
tcpKeepAliveInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPINTVL)
if err != nil {
return
}
tcpKeepAliveCount, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPCNT)
if err != nil {
return
}
cfg = KeepAliveConfig{
Enable: tcpKeepAlive != 0,
Idle: time.Duration(tcpKeepAliveIdle) * time.Second,
Interval: time.Duration(tcpKeepAliveInterval) * time.Second,
Count: tcpKeepAliveCount,
}
return
}

func verifyKeepAliveSettings(t *testing.T, fd int, oldCfg, cfg KeepAliveConfig) {
if cfg.Idle == 0 {
cfg.Idle = defaultTCPKeepAliveIdle
}
if cfg.Interval == 0 {
cfg.Interval = defaultTCPKeepAliveInterval
}
if cfg.Count == 0 {
cfg.Count = defaultTCPKeepAliveCount
}
if cfg.Idle == -1 {
cfg.Idle = oldCfg.Idle
}
if cfg.Interval == -1 {
cfg.Interval = oldCfg.Interval
}
if cfg.Count == -1 {
cfg.Count = oldCfg.Count
}

tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
if err != nil {
t.Fatal(err)
}
if (tcpKeepAlive != 0) != cfg.Enable {
t.Fatalf("SO_KEEPALIVE: got %t; want %t", tcpKeepAlive != 0, cfg.Enable)
}

tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE)
if err != nil {
t.Fatal(err)
}
if time.Duration(tcpKeepAliveIdle)*time.Second != cfg.Idle {
t.Fatalf("TCP_KEEPIDLE: got %ds; want %v", tcpKeepAliveIdle, cfg.Idle)
}

tcpKeepAliveInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPINTVL)
if err != nil {
t.Fatal(err)
}
if time.Duration(tcpKeepAliveInterval)*time.Second != cfg.Interval {
t.Fatalf("TCP_KEEPINTVL: got %ds; want %v", tcpKeepAliveInterval, cfg.Interval)
}

tcpKeepAliveCount, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPCNT)
if err != nil {
t.Fatal(err)
}
if tcpKeepAliveCount != cfg.Count {
t.Fatalf("TCP_KEEPCNT: got %d; want %d", tcpKeepAliveCount, cfg.Count)
}
}

0 comments on commit d42cd45

Please sign in to comment.