go/src/net/tcpsockopt_windows.go
qmuntal 68a508cdaf net,internal/syscall/windows: prove that keep alive options exists
The net package currently uses windows.SupportFullTCPKeepAlive to
know if TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT are available.
This function is a wrapper over the undocumented RtlGetNtVersionNumbers
API, which tests if the Windows version is at least 10.0.16299. This
approach artificially limits the use of TCP_KEEPCNT, which is
available since Windows 10.0.15063. It also uses an undocumented API,
which is not something we want to rely on.

This CL removes windows.SupportFullTCPKeepAlive in favor of dedicated
proves for each option which are not based on the Windows version.

While here, remove some assertions in setKeepAliveCount. It is better
to let the system decide if the value is valid or not.

Updates #65817.

Cq-Include-Trybots: luci.golang.try:gotip-windows-arm64
Change-Id: I0fe70d46c8675eab06c0e4628cf68571b6e50b80
Reviewed-on: https://go-review.googlesource.com/c/go/+/570077
Reviewed-by: Than McIntosh <thanm@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
2024-03-21 11:49:35 +00:00

119 lines
3.8 KiB
Go

// Copyright 2009 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.
package net
import (
"internal/syscall/windows"
"os"
"runtime"
"syscall"
"time"
"unsafe"
)
// Default values of KeepAliveTime and KeepAliveInterval on Windows,
// check out https://learn.microsoft.com/en-us/windows/win32/winsock/sio-keepalive-vals#remarks for details.
const (
defaultKeepAliveIdle = 2 * time.Hour
defaultKeepAliveInterval = time.Second
)
func setKeepAliveIdle(fd *netFD, d time.Duration) error {
if !windows.SupportTCPKeepAliveIdle() {
return setKeepAliveIdleAndInterval(fd, d, -1)
}
if d == 0 {
d = defaultTCPKeepAliveIdle
} else if d < 0 {
return nil
}
// The kernel expects seconds so round to next highest second.
secs := int(roundDurationUp(d, time.Second))
err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, windows.TCP_KEEPIDLE, secs)
runtime.KeepAlive(fd)
return os.NewSyscallError("setsockopt", err)
}
func setKeepAliveInterval(fd *netFD, d time.Duration) error {
if !windows.SupportTCPKeepAliveInterval() {
return setKeepAliveIdleAndInterval(fd, -1, d)
}
if d == 0 {
d = defaultTCPKeepAliveInterval
} else if d < 0 {
return nil
}
// The kernel expects seconds so round to next highest second.
secs := int(roundDurationUp(d, time.Second))
err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, windows.TCP_KEEPINTVL, secs)
runtime.KeepAlive(fd)
return os.NewSyscallError("setsockopt", err)
}
func setKeepAliveCount(fd *netFD, n int) error {
if n == 0 {
n = defaultTCPKeepAliveCount
} else if n < 0 {
return nil
}
err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, windows.TCP_KEEPCNT, n)
runtime.KeepAlive(fd)
return os.NewSyscallError("setsockopt", err)
}
// setKeepAliveIdleAndInterval serves for kernels prior to Windows 10, version 1709.
func setKeepAliveIdleAndInterval(fd *netFD, idle, interval time.Duration) error {
// WSAIoctl with SIO_KEEPALIVE_VALS control code requires all fields in
// `tcp_keepalive` struct to be provided.
// Otherwise, if any of the fields were not provided, just leaving them
// zero will knock off any existing values of keep-alive.
// Unfortunately, Windows doesn't support retrieving current keep-alive
// settings in any form programmatically, which disable us to first retrieve
// the current keep-alive settings, then set it without unwanted corruption.
switch {
case idle < 0 && interval >= 0:
// Given that we can't set KeepAliveInterval alone, and this code path
// is new, it doesn't exist before, so we just return an error.
return syscall.WSAENOPROTOOPT
case idle >= 0 && interval < 0:
// Although we can't set KeepAliveTime alone either, this existing code
// path had been backing up [SetKeepAlivePeriod] which used to be set both
// KeepAliveTime and KeepAliveInterval to 15 seconds.
// Now we will use the default of KeepAliveInterval on Windows if user doesn't
// provide one.
interval = defaultKeepAliveInterval
case idle < 0 && interval < 0:
// Nothing to do, just bail out.
return nil
case idle >= 0 && interval >= 0:
// Go ahead.
}
if idle == 0 {
idle = defaultTCPKeepAliveIdle
}
if interval == 0 {
interval = defaultTCPKeepAliveInterval
}
// The kernel expects milliseconds so round to next highest
// millisecond.
tcpKeepAliveIdle := uint32(roundDurationUp(idle, time.Millisecond))
tcpKeepAliveInterval := uint32(roundDurationUp(interval, time.Millisecond))
ka := syscall.TCPKeepalive{
OnOff: 1,
Time: tcpKeepAliveIdle,
Interval: tcpKeepAliveInterval,
}
ret := uint32(0)
size := uint32(unsafe.Sizeof(ka))
err := fd.pfd.WSAIoctl(syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
runtime.KeepAlive(fd)
return os.NewSyscallError("wsaioctl", err)
}