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
5package windows
6
7import (
8	"errors"
9	"sync"
10	"syscall"
11	"unsafe"
12)
13
14// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfow
15type _OSVERSIONINFOW struct {
16	osVersionInfoSize uint32
17	majorVersion      uint32
18	minorVersion      uint32
19	buildNumber       uint32
20	platformId        uint32
21	csdVersion        [128]uint16
22}
23
24// According to documentation, RtlGetVersion function always succeeds.
25//sys	rtlGetVersion(info *_OSVERSIONINFOW) = ntdll.RtlGetVersion
26
27// version retrieves the major, minor, and build version numbers
28// of the current Windows OS from the RtlGetVersion API.
29func version() (major, minor, build uint32) {
30	info := _OSVERSIONINFOW{}
31	info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
32	rtlGetVersion(&info)
33	return info.majorVersion, info.minorVersion, info.buildNumber
34}
35
36var (
37	supportTCPKeepAliveIdle     bool
38	supportTCPKeepAliveInterval bool
39	supportTCPKeepAliveCount    bool
40)
41
42var initTCPKeepAlive = sync.OnceFunc(func() {
43	s, err := WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, WSA_FLAG_NO_HANDLE_INHERIT)
44	if err != nil {
45		// Fallback to checking the Windows version.
46		major, _, build := version()
47		supportTCPKeepAliveIdle = major >= 10 && build >= 16299
48		supportTCPKeepAliveInterval = major >= 10 && build >= 16299
49		supportTCPKeepAliveCount = major >= 10 && build >= 15063
50		return
51	}
52	defer syscall.Closesocket(s)
53	var optSupported = func(opt int) bool {
54		err := syscall.SetsockoptInt(s, syscall.IPPROTO_TCP, opt, 1)
55		return !errors.Is(err, syscall.WSAENOPROTOOPT)
56	}
57	supportTCPKeepAliveIdle = optSupported(TCP_KEEPIDLE)
58	supportTCPKeepAliveInterval = optSupported(TCP_KEEPINTVL)
59	supportTCPKeepAliveCount = optSupported(TCP_KEEPCNT)
60})
61
62// SupportTCPKeepAliveInterval indicates whether TCP_KEEPIDLE is supported.
63// The minimal requirement is Windows 10.0.16299.
64func SupportTCPKeepAliveIdle() bool {
65	initTCPKeepAlive()
66	return supportTCPKeepAliveIdle
67}
68
69// SupportTCPKeepAliveInterval indicates whether TCP_KEEPINTVL is supported.
70// The minimal requirement is Windows 10.0.16299.
71func SupportTCPKeepAliveInterval() bool {
72	initTCPKeepAlive()
73	return supportTCPKeepAliveInterval
74}
75
76// SupportTCPKeepAliveCount indicates whether TCP_KEEPCNT is supported.
77// supports TCP_KEEPCNT.
78// The minimal requirement is Windows 10.0.15063.
79func SupportTCPKeepAliveCount() bool {
80	initTCPKeepAlive()
81	return supportTCPKeepAliveCount
82}
83
84// SupportTCPInitialRTONoSYNRetransmissions indicates whether the current
85// Windows version supports the TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS.
86// The minimal requirement is Windows 10.0.16299.
87var SupportTCPInitialRTONoSYNRetransmissions = sync.OnceValue(func() bool {
88	major, _, build := version()
89	return major >= 10 && build >= 16299
90})
91
92// SupportUnixSocket indicates whether the current Windows version supports
93// Unix Domain Sockets.
94// The minimal requirement is Windows 10.0.17063.
95var SupportUnixSocket = sync.OnceValue(func() bool {
96	var size uint32
97	// First call to get the required buffer size in bytes.
98	// Ignore the error, it will always fail.
99	_, _ = syscall.WSAEnumProtocols(nil, nil, &size)
100	n := int32(size) / int32(unsafe.Sizeof(syscall.WSAProtocolInfo{}))
101	// Second call to get the actual protocols.
102	buf := make([]syscall.WSAProtocolInfo, n)
103	n, err := syscall.WSAEnumProtocols(nil, &buf[0], &size)
104	if err != nil {
105		return false
106	}
107	for i := int32(0); i < n; i++ {
108		if buf[i].AddressFamily == syscall.AF_UNIX {
109			return true
110		}
111	}
112	return false
113})
114