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 solaris && !illumos
6
7package net
8
9import (
10	"internal/syscall/unix"
11	"syscall"
12	"testing"
13	"time"
14)
15
16func getCurrentKeepAliveSettings(fd fdType) (cfg KeepAliveConfig, err error) {
17	tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
18	if err != nil {
19		return
20	}
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
62	}
63	cfg = KeepAliveConfig{
64		Enable:   tcpKeepAlive != 0,
65		Idle:     tcpKeepAliveIdleTime,
66		Interval: tcpKeepAliveIntervalTime,
67		Count:    tcpKeepAliveCount,
68	}
69	return
70}
71
72func verifyKeepAliveSettings(t *testing.T, fd fdType, oldCfg, cfg KeepAliveConfig) {
73	const defaultTcpKeepAliveAbortThreshold = 8 * time.Minute // default value on Solaris
74
75	if cfg.Idle == 0 {
76		cfg.Idle = defaultTCPKeepAliveIdle
77	}
78	if cfg.Interval == 0 {
79		cfg.Interval = defaultTCPKeepAliveInterval
80	}
81	if cfg.Count == 0 {
82		cfg.Count = defaultTCPKeepAliveCount
83	}
84	if cfg.Idle == -1 {
85		cfg.Idle = oldCfg.Idle
86	}
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
113	}
114
115	tcpKeepAlive, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
116	if err != nil {
117		t.Fatal(err)
118	}
119	if (tcpKeepAlive != 0) != cfg.Enable {
120		t.Fatalf("SO_KEEPALIVE: got %t; want %t", tcpKeepAlive != 0, cfg.Enable)
121	}
122
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)
134	if err != nil {
135		t.Fatal(err)
136	}
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		}
169	}
170}
171