Skip to content

Commit d42cd45

Browse files
panjf2000gopherbot
authored andcommitted
net: add KeepAliveConfig and implement SetKeepAliveConfig
Fixes #62254 Fixes #48622 Change-Id: Ida598e7fa914c8737fdbc1c813bcd68adb5119c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/542275 Reviewed-by: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Damien Neil <[email protected]> Run-TryBot: Andy Pan <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent aaf8e84 commit d42cd45

32 files changed

+1131
-66
lines changed

api/next/62254.txt

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pkg net, method (*TCPConn) SetKeepAliveConfig(KeepAliveConfig) error #62254
2+
pkg net, type Dialer struct, KeepAliveConfig KeepAliveConfig #62254
3+
pkg net, type KeepAliveConfig struct #62254
4+
pkg net, type KeepAliveConfig struct, Count int #62254
5+
pkg net, type KeepAliveConfig struct, Enable bool #62254
6+
pkg net, type KeepAliveConfig struct, Idle time.Duration #62254
7+
pkg net, type KeepAliveConfig struct, Interval time.Duration #62254
8+
pkg net, type ListenConfig struct, KeepAliveConfig KeepAliveConfig #62254
9+
pkg syscall (windows-386), const WSAENOPROTOOPT = 10042 #62254
10+
pkg syscall (windows-386), const WSAENOPROTOOPT Errno #62254
11+
pkg syscall (windows-amd64), const WSAENOPROTOOPT = 10042 #62254
12+
pkg syscall (windows-amd64), const WSAENOPROTOOPT Errno #62254
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The new type [`KeepAliveConfig`](/net#KeepAliveConfig) permits fine-tuning
2+
the keep-alive options for TCP connections, via a new
3+
[`TCPConn.SetKeepAliveConfig`](/net#TCPConn.SetKeepAliveConfig) method and
4+
new KeepAliveConfig fields for [`Dialer`](net#Dialer) and [`ListenConfig`](net#ListenConfig).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The syscall package now defines WSAENOPROTOOPT on Windows.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
See `syscall (windows-386)/62254.md`.

src/net/dial.go

+38-7
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@ import (
1414
)
1515

1616
const (
17-
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
18-
// See go.dev/issue/31510
19-
defaultTCPKeepAlive = 15 * time.Second
17+
// defaultTCPKeepAliveIdle is a default constant value for TCP_KEEPIDLE.
18+
// See go.dev/issue/31510 for details.
19+
defaultTCPKeepAliveIdle = 15 * time.Second
20+
21+
// defaultTCPKeepAliveInterval is a default constant value for TCP_KEEPINTVL.
22+
// It is the same as defaultTCPKeepAliveIdle, see go.dev/issue/31510 for details.
23+
defaultTCPKeepAliveInterval = 15 * time.Second
24+
25+
// defaultTCPKeepAliveCount is a default constant value for TCP_KEEPCNT.
26+
defaultTCPKeepAliveCount = 9
2027

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

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

136+
// KeepAliveConfig specifies the keep-alive probe configuration
137+
// for an active network connection, when supported by the
138+
// protocol and operating system.
139+
//
140+
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
141+
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
142+
// keep-alive probes are disabled.
143+
KeepAliveConfig KeepAliveConfig
144+
126145
// Resolver optionally specifies an alternate resolver to use.
127146
Resolver *Resolver
128147

@@ -680,12 +699,24 @@ type ListenConfig struct {
680699

681700
// KeepAlive specifies the keep-alive period for network
682701
// connections accepted by this listener.
683-
// If zero, keep-alives are enabled if supported by the protocol
702+
//
703+
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
704+
//
705+
// If zero, keep-alive are enabled if supported by the protocol
684706
// and operating system. Network protocols or operating systems
685-
// that do not support keep-alives ignore this field.
686-
// If negative, keep-alives are disabled.
707+
// that do not support keep-alive ignore this field.
708+
// If negative, keep-alive are disabled.
687709
KeepAlive time.Duration
688710

711+
// KeepAliveConfig specifies the keep-alive probe configuration
712+
// for an active network connection, when supported by the
713+
// protocol and operating system.
714+
//
715+
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
716+
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
717+
// keep-alive probes are disabled.
718+
KeepAliveConfig KeepAliveConfig
719+
689720
// If mptcpStatus is set to a value allowing Multipath TCP (MPTCP) to be
690721
// used, any call to Listen with "tcp(4|6)" as network will use MPTCP if
691722
// supported by the operating system.

src/net/dial_test.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,10 @@ func TestDialerDualStack(t *testing.T) {
690690
}
691691

692692
func TestDialerKeepAlive(t *testing.T) {
693+
t.Cleanup(func() {
694+
testHookSetKeepAlive = func(KeepAliveConfig) {}
695+
})
696+
693697
handler := func(ls *localServer, ln Listener) {
694698
for {
695699
c, err := ln.Accept()
@@ -699,26 +703,30 @@ func TestDialerKeepAlive(t *testing.T) {
699703
c.Close()
700704
}
701705
}
702-
ls := newLocalServer(t, "tcp")
706+
ln := newLocalListener(t, "tcp", &ListenConfig{
707+
KeepAlive: -1, // prevent calling hook from accepting
708+
})
709+
ls := (&streamListener{Listener: ln}).newLocalServer()
703710
defer ls.teardown()
704711
if err := ls.buildup(handler); err != nil {
705712
t.Fatal(err)
706713
}
707-
defer func() { testHookSetKeepAlive = func(time.Duration) {} }()
708714

709715
tests := []struct {
710716
ka time.Duration
711717
expected time.Duration
712718
}{
713719
{-1, -1},
714-
{0, 15 * time.Second},
720+
{0, 0},
715721
{5 * time.Second, 5 * time.Second},
716722
{30 * time.Second, 30 * time.Second},
717723
}
718724

725+
var got time.Duration = -1
726+
testHookSetKeepAlive = func(cfg KeepAliveConfig) { got = cfg.Idle }
727+
719728
for _, test := range tests {
720-
var got time.Duration = -1
721-
testHookSetKeepAlive = func(d time.Duration) { got = d }
729+
got = -1
722730
d := Dialer{KeepAlive: test.ka}
723731
c, err := d.Dial("tcp", ls.Listener.Addr().String())
724732
if err != nil {

src/net/file_plan9.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func fileConn(f *os.File) (Conn, error) {
100100

101101
switch fd.laddr.(type) {
102102
case *TCPAddr:
103-
return newTCPConn(fd, defaultTCPKeepAlive, testHookSetKeepAlive), nil
103+
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
104104
case *UDPAddr:
105105
return newUDPConn(fd), nil
106106
}

src/net/file_unix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func fileConn(f *os.File) (Conn, error) {
7474
}
7575
switch fd.laddr.(type) {
7676
case *TCPAddr:
77-
return newTCPConn(fd, defaultTCPKeepAlive, testHookSetKeepAlive), nil
77+
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
7878
case *UDPAddr:
7979
return newUDPConn(fd), nil
8080
case *IPAddr:

src/net/hook.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package net
66

77
import (
88
"context"
9-
"time"
109
)
1110

1211
var (
@@ -21,7 +20,8 @@ var (
2120
) ([]IPAddr, error) {
2221
return fn(ctx, network, host)
2322
}
24-
testHookSetKeepAlive = func(time.Duration) {}
23+
testPreHookSetKeepAlive = func(*netFD) {}
24+
testHookSetKeepAlive = func(KeepAliveConfig) {}
2525

2626
// testHookStepTime sleeps until time has moved forward by a nonzero amount.
2727
// This helps to avoid flakes in timeout tests by ensuring that an implausibly

src/net/mockserver_test.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,7 @@ func newLocalListener(t testing.TB, network string, lcOpt ...*ListenConfig) List
6060
switch network {
6161
case "tcp":
6262
if supportsIPv4() {
63-
if !supportsIPv6() {
64-
return listen("tcp4", "127.0.0.1:0")
65-
}
66-
if ln, err := Listen("tcp4", "127.0.0.1:0"); err == nil {
67-
return ln
68-
}
63+
return listen("tcp4", "127.0.0.1:0")
6964
}
7065
if supportsIPv6() {
7166
return listen("tcp6", "[::1]:0")
+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build aix || freebsd || linux || netbsd || darwin || dragonfly
6+
7+
package net
8+
9+
import "time"
10+
11+
var testConfigs = []KeepAliveConfig{
12+
{
13+
Enable: true,
14+
Idle: 5 * time.Second,
15+
Interval: 3 * time.Second,
16+
Count: 10,
17+
},
18+
{
19+
Enable: true,
20+
Idle: 0,
21+
Interval: 0,
22+
Count: 0,
23+
},
24+
{
25+
Enable: true,
26+
Idle: -1,
27+
Interval: -1,
28+
Count: -1,
29+
},
30+
{
31+
Enable: true,
32+
Idle: -1,
33+
Interval: 3 * time.Second,
34+
Count: 10,
35+
},
36+
{
37+
Enable: true,
38+
Idle: 5 * time.Second,
39+
Interval: -1,
40+
Count: 10,
41+
},
42+
{
43+
Enable: true,
44+
Idle: 5 * time.Second,
45+
Interval: 3 * time.Second,
46+
Count: -1,
47+
},
48+
{
49+
Enable: true,
50+
Idle: -1,
51+
Interval: -1,
52+
Count: 10,
53+
},
54+
{
55+
Enable: true,
56+
Idle: -1,
57+
Interval: 3 * time.Second,
58+
Count: -1,
59+
},
60+
{
61+
Enable: true,
62+
Idle: 5 * time.Second,
63+
Interval: -1,
64+
Count: -1,
65+
},
66+
{
67+
Enable: true,
68+
Idle: 0,
69+
Interval: 3 * time.Second,
70+
Count: 10,
71+
},
72+
{
73+
Enable: true,
74+
Idle: 5 * time.Second,
75+
Interval: 0,
76+
Count: 10,
77+
},
78+
{
79+
Enable: true,
80+
Idle: 5 * time.Second,
81+
Interval: 3 * time.Second,
82+
Count: 0,
83+
},
84+
{
85+
Enable: true,
86+
Idle: 0,
87+
Interval: 0,
88+
Count: 10,
89+
},
90+
{
91+
Enable: true,
92+
Idle: 0,
93+
Interval: 3 * time.Second,
94+
Count: 0,
95+
},
96+
{
97+
Enable: true,
98+
Idle: 5 * time.Second,
99+
Interval: 0,
100+
Count: 0,
101+
},
102+
}
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin
6+
7+
package net
8+
9+
import (
10+
"syscall"
11+
"testing"
12+
"time"
13+
)
14+
15+
func getCurrentKeepAliveSettings(fd int) (cfg KeepAliveConfig, err error) {
16+
tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
17+
if err != nil {
18+
return
19+
}
20+
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE)
21+
if err != nil {
22+
return
23+
}
24+
tcpKeepAliveInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPINTVL)
25+
if err != nil {
26+
return
27+
}
28+
tcpKeepAliveCount, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPCNT)
29+
if err != nil {
30+
return
31+
}
32+
cfg = KeepAliveConfig{
33+
Enable: tcpKeepAlive != 0,
34+
Idle: time.Duration(tcpKeepAliveIdle) * time.Second,
35+
Interval: time.Duration(tcpKeepAliveInterval) * time.Second,
36+
Count: tcpKeepAliveCount,
37+
}
38+
return
39+
}
40+
41+
func verifyKeepAliveSettings(t *testing.T, fd int, oldCfg, cfg KeepAliveConfig) {
42+
if cfg.Idle == 0 {
43+
cfg.Idle = defaultTCPKeepAliveIdle
44+
}
45+
if cfg.Interval == 0 {
46+
cfg.Interval = defaultTCPKeepAliveInterval
47+
}
48+
if cfg.Count == 0 {
49+
cfg.Count = defaultTCPKeepAliveCount
50+
}
51+
if cfg.Idle == -1 {
52+
cfg.Idle = oldCfg.Idle
53+
}
54+
if cfg.Interval == -1 {
55+
cfg.Interval = oldCfg.Interval
56+
}
57+
if cfg.Count == -1 {
58+
cfg.Count = oldCfg.Count
59+
}
60+
61+
tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if (tcpKeepAlive != 0) != cfg.Enable {
66+
t.Fatalf("SO_KEEPALIVE: got %t; want %t", tcpKeepAlive != 0, cfg.Enable)
67+
}
68+
69+
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE)
70+
if err != nil {
71+
t.Fatal(err)
72+
}
73+
if time.Duration(tcpKeepAliveIdle)*time.Second != cfg.Idle {
74+
t.Fatalf("TCP_KEEPIDLE: got %ds; want %v", tcpKeepAliveIdle, cfg.Idle)
75+
}
76+
77+
tcpKeepAliveInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPINTVL)
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
if time.Duration(tcpKeepAliveInterval)*time.Second != cfg.Interval {
82+
t.Fatalf("TCP_KEEPINTVL: got %ds; want %v", tcpKeepAliveInterval, cfg.Interval)
83+
}
84+
85+
tcpKeepAliveCount, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, sysTCP_KEEPCNT)
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
if tcpKeepAliveCount != cfg.Count {
90+
t.Fatalf("TCP_KEEPCNT: got %d; want %d", tcpKeepAliveCount, cfg.Count)
91+
}
92+
}

0 commit comments

Comments
 (0)