1// Copyright 2015 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 !illumos
6
7package net
8
9import (
10	"internal/syscall/unix"
11	"runtime"
12	"syscall"
13	"time"
14)
15
16// Some macros of TCP Keep-Alive options on Solaris 11.4 may
17// differ from those on OpenSolaris-based derivatives.
18const (
19	sysTCP_KEEPIDLE  = 0x1D
20	sysTCP_KEEPINTVL = 0x1E
21	sysTCP_KEEPCNT   = 0x1F
22)
23
24func setKeepAliveIdle(fd *netFD, d time.Duration) error {
25	if !unix.SupportTCPKeepAliveIdleIntvlCNT() {
26		return setKeepAliveIdleAndIntervalAndCount(fd, d, -1, -1)
27	}
28
29	if d == 0 {
30		d = defaultTCPKeepAliveIdle
31	} else if d < 0 {
32		return nil
33	}
34	// The kernel expects seconds so round to next highest second.
35	secs := int(roundDurationUp(d, time.Second))
36	err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, sysTCP_KEEPIDLE, secs)
37	runtime.KeepAlive(fd)
38	return wrapSyscallError("setsockopt", err)
39}
40
41func setKeepAliveInterval(fd *netFD, d time.Duration) error {
42	if !unix.SupportTCPKeepAliveIdleIntvlCNT() {
43		return syscall.EPROTOTYPE
44	}
45
46	if d == 0 {
47		d = defaultTCPKeepAliveInterval
48	} else if d < 0 {
49		return nil
50	}
51	// The kernel expects seconds so round to next highest second.
52	secs := int(roundDurationUp(d, time.Second))
53	err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, sysTCP_KEEPINTVL, secs)
54	runtime.KeepAlive(fd)
55	return wrapSyscallError("setsockopt", err)
56}
57
58func setKeepAliveCount(fd *netFD, n int) error {
59	if !unix.SupportTCPKeepAliveIdleIntvlCNT() {
60		return syscall.EPROTOTYPE
61	}
62
63	if n == 0 {
64		n = defaultTCPKeepAliveCount
65	} else if n < 0 {
66		return nil
67	}
68	err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, sysTCP_KEEPCNT, n)
69	runtime.KeepAlive(fd)
70	return wrapSyscallError("setsockopt", err)
71}
72
73// setKeepAliveIdleAndIntervalAndCount serves for Solaris prior to 11.4 by simulating
74// the TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT with `TCP_KEEPALIVE_THRESHOLD` + `TCP_KEEPALIVE_ABORT_THRESHOLD`.
75func setKeepAliveIdleAndIntervalAndCount(fd *netFD, idle, interval time.Duration, count int) error {
76	if idle == 0 {
77		idle = defaultTCPKeepAliveIdle
78	}
79
80	// The kernel expects milliseconds so round to next highest
81	// millisecond.
82	if idle > 0 {
83		msecs := int(roundDurationUp(idle, time.Millisecond))
84		err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD, msecs)
85		runtime.KeepAlive(fd)
86		if err != nil {
87			return wrapSyscallError("setsockopt", err)
88		}
89	}
90
91	if interval == 0 {
92		interval = defaultTCPKeepAliveInterval
93	}
94	if count == 0 {
95		count = defaultTCPKeepAliveCount
96	}
97	// TCP_KEEPINTVL and TCP_KEEPCNT are not available on Solaris
98	// prior to 11.4, so it's pointless to "leave it unchanged"
99	// with negative value for only one of them. On the other hand,
100	// setting both to negative values should pragmatically leave the
101	// TCP_KEEPALIVE_ABORT_THRESHOLD unchanged.
102	abortIdle := int(roundDurationUp(interval, time.Millisecond)) * count
103	if abortIdle < 0 {
104		return syscall.ENOPROTOOPT
105	}
106	if interval < 0 && count < 0 {
107		abortIdle = -1
108	}
109
110	if abortIdle > 0 {
111		// Note that the consequent probes will not be sent at equal intervals on Solaris,
112		// but will be sent using the exponential backoff algorithm.
113		err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_ABORT_THRESHOLD, abortIdle)
114		runtime.KeepAlive(fd)
115		return wrapSyscallError("setsockopt", err)
116	}
117
118	return nil
119}
120