Skip to content

Commit 91c0482

Browse files
panjf2000gopherbot
authored andcommitted
net: implement TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT on Solaris 11.4
Also simulate TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT with TCP_KEEPALIVE_THRESHOLD + TCP_KEEPALIVE_ABORT_THRESHOLD for Solaris prior to 11.4 Fixes #9614 Fixes #64251 Change-Id: Ia0777076a7952630bc52761cddd0b06b0d81c6a0 Reviewed-on: https://go-review.googlesource.com/c/go/+/577195 Commit-Queue: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent c0a0ba2 commit 91c0482

9 files changed

+384
-80
lines changed

src/internal/syscall/unix/kernel_version_solaris.go

+7
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,10 @@ var SupportAccept4 = sync.OnceValue(func() bool {
9797
return err != syscall.ENOSYS
9898
}
9999
})
100+
101+
// SupportTCPKeepAliveIdleIntvlCNT determines whether the TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
102+
// are available by checking the kernel version for Solaris 11.4.
103+
var SupportTCPKeepAliveIdleIntvlCNT = sync.OnceValue(func() bool {
104+
major, minor := KernelVersion()
105+
return major > 11 || (major == 11 && minor >= 4)
106+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2024 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 solaris && !illumos
6+
7+
package net
8+
9+
import (
10+
"testing"
11+
"time"
12+
)
13+
14+
const (
15+
syscall_TCP_KEEPIDLE = sysTCP_KEEPIDLE
16+
syscall_TCP_KEEPCNT = sysTCP_KEEPCNT
17+
syscall_TCP_KEEPINTVL = sysTCP_KEEPINTVL
18+
)
19+
20+
type fdType = int
21+
22+
func maybeSkipKeepAliveTest(_ *testing.T) {}
23+
24+
var testConfigs = []KeepAliveConfig{
25+
{
26+
Enable: true,
27+
Idle: 20 * time.Second, // the minimum value is ten seconds on Solaris
28+
Interval: 10 * time.Second, // ditto
29+
Count: 10,
30+
},
31+
{
32+
Enable: true,
33+
Idle: 0,
34+
Interval: 0,
35+
Count: 0,
36+
},
37+
{
38+
Enable: true,
39+
Idle: -1,
40+
Interval: -1,
41+
Count: -1,
42+
},
43+
{
44+
Enable: true,
45+
Idle: -1,
46+
Interval: 10 * time.Second,
47+
Count: 10,
48+
},
49+
{
50+
Enable: true,
51+
Idle: 20 * time.Second,
52+
Interval: -1,
53+
Count: 10,
54+
},
55+
{
56+
Enable: true,
57+
Idle: 20 * time.Second,
58+
Interval: 10 * time.Second,
59+
Count: -1,
60+
},
61+
{
62+
Enable: true,
63+
Idle: -1,
64+
Interval: -1,
65+
Count: 10,
66+
},
67+
{
68+
Enable: true,
69+
Idle: -1,
70+
Interval: 10 * time.Second,
71+
Count: -1,
72+
},
73+
{
74+
Enable: true,
75+
Idle: 20 * time.Second,
76+
Interval: -1,
77+
Count: -1,
78+
},
79+
{
80+
Enable: true,
81+
Idle: 0,
82+
Interval: 10 * time.Second,
83+
Count: 10,
84+
},
85+
{
86+
Enable: true,
87+
Idle: 20 * time.Second,
88+
Interval: 0,
89+
Count: 10,
90+
},
91+
{
92+
Enable: true,
93+
Idle: 20 * time.Second,
94+
Interval: 10 * time.Second,
95+
Count: 0,
96+
},
97+
{
98+
Enable: true,
99+
Idle: 0,
100+
Interval: 0,
101+
Count: 10,
102+
},
103+
{
104+
Enable: true,
105+
Idle: 0,
106+
Interval: 10 * time.Second,
107+
Count: 0,
108+
},
109+
{
110+
Enable: true,
111+
Idle: 20 * time.Second,
112+
Interval: 0,
113+
Count: 0,
114+
},
115+
}

src/net/tcpconn_keepalive_conf_unix_test.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build aix || dragonfly || freebsd || linux || netbsd || solaris
5+
//go:build aix || dragonfly || freebsd || illumos || linux || netbsd
66

77
package net
88

99
import (
10-
"runtime"
1110
"syscall"
1211
"testing"
1312
)
@@ -20,10 +19,4 @@ const (
2019

2120
type fdType = int
2221

23-
func maybeSkipKeepAliveTest(t *testing.T) {
24-
// TODO(panjf2000): stop skipping this test on Solaris
25-
// when https://go.dev/issue/64251 is fixed.
26-
if runtime.GOOS == "solaris" {
27-
t.Skip("skipping on solaris for now")
28-
}
29-
}
22+
func maybeSkipKeepAliveTest(_ *testing.T) {}

src/net/tcpconn_keepalive_solaris_test.go

+116-35
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,71 @@
77
package net
88

99
import (
10+
"internal/syscall/unix"
1011
"syscall"
1112
"testing"
1213
"time"
1314
)
1415

15-
var testConfigs = []KeepAliveConfig{
16-
{
17-
Enable: true,
18-
Idle: 2 * time.Second,
19-
Interval: -1,
20-
Count: -1,
21-
},
22-
{
23-
Enable: true,
24-
Idle: 0,
25-
Interval: -1,
26-
Count: -1,
27-
},
28-
{
29-
Enable: true,
30-
Idle: -1,
31-
Interval: -1,
32-
Count: -1,
33-
},
34-
}
35-
3616
func getCurrentKeepAliveSettings(fd fdType) (cfg KeepAliveConfig, err error) {
3717
tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
3818
if err != nil {
3919
return
4020
}
41-
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD)
42-
if err != nil {
43-
return
21+
22+
var (
23+
tcpKeepAliveIdle int
24+
tcpKeepAliveInterval int
25+
tcpKeepAliveIdleTime time.Duration
26+
tcpKeepAliveIntervalTime time.Duration
27+
tcpKeepAliveCount int
28+
)
29+
if unix.SupportTCPKeepAliveIdleIntvlCNT() {
30+
tcpKeepAliveIdle, err = syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPIDLE)
31+
if err != nil {
32+
return
33+
}
34+
tcpKeepAliveIdleTime = time.Duration(tcpKeepAliveIdle) * time.Second
35+
36+
tcpKeepAliveInterval, err = syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPINTVL)
37+
if err != nil {
38+
return
39+
}
40+
tcpKeepAliveIntervalTime = time.Duration(tcpKeepAliveInterval) * time.Second
41+
42+
tcpKeepAliveCount, err = syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPCNT)
43+
if err != nil {
44+
return
45+
}
46+
} else {
47+
tcpKeepAliveIdle, err = syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD)
48+
if err != nil {
49+
return
50+
}
51+
tcpKeepAliveIdleTime = time.Duration(tcpKeepAliveIdle) * time.Millisecond
52+
53+
// TCP_KEEPINTVL and TCP_KEEPCNT are not available on Solaris prior to 11.4,
54+
// so we have to use the value of TCP_KEEPALIVE_ABORT_THRESHOLD for Interval
55+
// and 1 for Count to keep this test going.
56+
tcpKeepAliveInterval, err = syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_ABORT_THRESHOLD)
57+
if err != nil {
58+
return
59+
}
60+
tcpKeepAliveIntervalTime = time.Duration(tcpKeepAliveInterval) * time.Millisecond
61+
tcpKeepAliveCount = 1
4462
}
4563
cfg = KeepAliveConfig{
4664
Enable: tcpKeepAlive != 0,
47-
Idle: time.Duration(tcpKeepAliveIdle) * time.Millisecond,
48-
Interval: -1,
49-
Count: -1,
65+
Idle: tcpKeepAliveIdleTime,
66+
Interval: tcpKeepAliveIntervalTime,
67+
Count: tcpKeepAliveCount,
5068
}
5169
return
5270
}
5371

5472
func verifyKeepAliveSettings(t *testing.T, fd fdType, oldCfg, cfg KeepAliveConfig) {
73+
const defaultTcpKeepAliveAbortThreshold = 8 * time.Minute // default value on Solaris
74+
5575
if cfg.Idle == 0 {
5676
cfg.Idle = defaultTCPKeepAliveIdle
5777
}
@@ -64,11 +84,32 @@ func verifyKeepAliveSettings(t *testing.T, fd fdType, oldCfg, cfg KeepAliveConfi
6484
if cfg.Idle == -1 {
6585
cfg.Idle = oldCfg.Idle
6686
}
67-
if cfg.Interval == -1 {
68-
cfg.Interval = oldCfg.Interval
69-
}
70-
if cfg.Count == -1 {
71-
cfg.Count = oldCfg.Count
87+
88+
tcpKeepAliveAbortThreshold := defaultTcpKeepAliveAbortThreshold
89+
if unix.SupportTCPKeepAliveIdleIntvlCNT() {
90+
// Check out the comment on KeepAliveConfig to understand the following logic.
91+
switch {
92+
case cfg.Interval == -1 && cfg.Count == -1:
93+
cfg.Interval = oldCfg.Interval
94+
cfg.Count = oldCfg.Count
95+
case cfg.Interval == -1 && cfg.Count > 0:
96+
cfg.Interval = defaultTcpKeepAliveAbortThreshold / time.Duration(cfg.Count)
97+
case cfg.Count == -1 && cfg.Interval > 0:
98+
cfg.Count = int(defaultTcpKeepAliveAbortThreshold / cfg.Interval)
99+
case cfg.Interval > 0 && cfg.Count > 0:
100+
// TCP_KEEPALIVE_ABORT_THRESHOLD will be recalculated only when both TCP_KEEPINTVL
101+
// and TCP_KEEPCNT are set, otherwise it will remain the default value.
102+
tcpKeepAliveAbortThreshold = cfg.Interval * time.Duration(cfg.Count)
103+
}
104+
} else {
105+
cfg.Interval = cfg.Interval * time.Duration(cfg.Count)
106+
// Either Interval or Count is set to a negative value, TCP_KEEPALIVE_ABORT_THRESHOLD
107+
// will remain the default value, so use the old Interval for the subsequent test.
108+
if cfg.Interval == -1 || cfg.Count == -1 {
109+
cfg.Interval = oldCfg.Interval
110+
}
111+
cfg.Count = 1
112+
tcpKeepAliveAbortThreshold = cfg.Interval
72113
}
73114

74115
tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
@@ -79,11 +120,51 @@ func verifyKeepAliveSettings(t *testing.T, fd fdType, oldCfg, cfg KeepAliveConfi
79120
t.Fatalf("SO_KEEPALIVE: got %t; want %t", tcpKeepAlive != 0, cfg.Enable)
80121
}
81122

82-
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD)
123+
// TCP_KEEPALIVE_THRESHOLD and TCP_KEEPALIVE_ABORT_THRESHOLD are both available on Solaris 11.4
124+
// and previous versions, so we can verify these two options regardless of the kernel version.
125+
tcpKeepAliveThreshold, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD)
126+
if err != nil {
127+
t.Fatal(err)
128+
}
129+
if time.Duration(tcpKeepAliveThreshold)*time.Millisecond != cfg.Idle {
130+
t.Fatalf("TCP_KEEPIDLE: got %dms; want %v", tcpKeepAliveThreshold, cfg.Idle)
131+
}
132+
133+
tcpKeepAliveAbortInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_ABORT_THRESHOLD)
83134
if err != nil {
84135
t.Fatal(err)
85136
}
86-
if time.Duration(tcpKeepAliveIdle)*time.Millisecond != cfg.Idle {
87-
t.Fatalf("TCP_KEEPIDLE: got %dms; want %v", tcpKeepAliveIdle, cfg.Idle)
137+
if time.Duration(tcpKeepAliveAbortInterval)*time.Millisecond != tcpKeepAliveAbortThreshold {
138+
t.Fatalf("TCP_KEEPALIVE_ABORT_THRESHOLD: got %dms; want %v", tcpKeepAliveAbortInterval, tcpKeepAliveAbortThreshold)
139+
}
140+
141+
if unix.SupportTCPKeepAliveIdleIntvlCNT() {
142+
tcpKeepAliveIdle, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPIDLE)
143+
if err != nil {
144+
t.Fatal(err)
145+
}
146+
if time.Duration(tcpKeepAliveIdle)*time.Second != cfg.Idle {
147+
t.Fatalf("TCP_KEEPIDLE: got %ds; want %v", tcpKeepAliveIdle, cfg.Idle)
148+
}
149+
150+
tcpKeepAliveInterval, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPINTVL)
151+
if err != nil {
152+
t.Fatal(err)
153+
}
154+
if time.Duration(tcpKeepAliveInterval)*time.Second != cfg.Interval {
155+
t.Fatalf("TCP_KEEPINTVL: got %ds; want %v", tcpKeepAliveInterval, cfg.Interval)
156+
}
157+
158+
tcpKeepAliveCount, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_TCP, syscall_TCP_KEEPCNT)
159+
if err != nil {
160+
t.Fatal(err)
161+
}
162+
if tcpKeepAliveCount != cfg.Count {
163+
t.Fatalf("TCP_KEEPCNT: got %d; want %d", tcpKeepAliveCount, cfg.Count)
164+
}
165+
} else {
166+
if cfg.Count != 1 {
167+
t.Fatalf("TCP_KEEPCNT: got %d; want 1", cfg.Count)
168+
}
88169
}
89170
}

src/net/tcpconn_keepalive_test.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package net
88

9-
import "testing"
9+
import (
10+
"runtime"
11+
"testing"
12+
)
1013

1114
func TestTCPConnKeepAliveConfigDialer(t *testing.T) {
1215
maybeSkipKeepAliveTest(t)
@@ -164,8 +167,18 @@ func TestTCPConnKeepAliveConfig(t *testing.T) {
164167
t.Fatal(errHook)
165168
}
166169

167-
if err := c.(*TCPConn).SetKeepAliveConfig(cfg); err != nil {
168-
t.Fatal(err)
170+
err = c.(*TCPConn).SetKeepAliveConfig(cfg)
171+
if err != nil {
172+
if runtime.GOOS == "solaris" {
173+
// Solaris prior to 11.4 does not support TCP_KEEPINTVL and TCP_KEEPCNT,
174+
// so it will return syscall.ENOPROTOOPT when only one of Interval and Count
175+
// is negative. This is expected, so skip the error check in this case.
176+
if cfg.Interval >= 0 && cfg.Count >= 0 {
177+
t.Fatal(err)
178+
}
179+
} else {
180+
t.Fatal(err)
181+
}
169182
}
170183

171184
if err := sc.Control(func(fd uintptr) {

src/net/tcpsock.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,8 @@ type TCPConn struct {
127127
// the other will be set to the system default value, and ultimately,
128128
// set both Idle and Interval to negative values if you want to leave them unchanged.
129129
//
130-
// Also note that on illumos distributions like OmniOS that support TCP Keep-Alive,
131-
// setting only one of Idle and Interval to a non-negative value along with the
132-
// negative other one will result in the negative one being recalculated as the
133-
// quotient of tcp_keepalive_abort_interval(eight minutes as default) and the
134-
// non-negative one. Thus, you may as well set the other one to a non-negative
135-
// value if you've already set one of Idle and Interval.
130+
// Note that Solaris and its derivatives do not support setting Interval to a non-negative value
131+
// and Count to a negative value, or vice-versa.
136132
type KeepAliveConfig struct {
137133
// If Enable is true, keep-alive probes are enabled.
138134
Enable bool

0 commit comments

Comments
 (0)