Skip to content

Commit 8cdaeec

Browse files
committed
net: avoid using Windows' TransmitFile on non-server machines
1 parent 42f9ee9 commit 8cdaeec

File tree

7 files changed

+71
-12
lines changed

7 files changed

+71
-12
lines changed

src/internal/syscall/windows/types_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,7 @@ type FILE_COMPLETION_INFORMATION struct {
256256
Port syscall.Handle
257257
Key uintptr
258258
}
259+
260+
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
261+
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
262+
const VER_NT_WORKSTATION = 0x0000001

src/internal/syscall/windows/version_windows.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,53 @@ import (
1111
"unsafe"
1212
)
1313

14-
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfow
15-
type _OSVERSIONINFOW struct {
14+
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
15+
type _OSVERSIONINFOEXW struct {
1616
osVersionInfoSize uint32
1717
majorVersion uint32
1818
minorVersion uint32
1919
buildNumber uint32
2020
platformId uint32
2121
csdVersion [128]uint16
22+
servicePackMajor uint16
23+
servicePackMinor uint16
24+
suiteMask uint16
25+
productType byte
26+
reserved byte
2227
}
2328

2429
// According to documentation, RtlGetVersion function always succeeds.
25-
//sys rtlGetVersion(info *_OSVERSIONINFOW) = ntdll.RtlGetVersion
30+
//sys rtlGetVersion(info *_OSVERSIONINFOEXW) = ntdll.RtlGetVersion
31+
32+
// Retrieves version information of the current Windows OS
33+
// from the RtlGetVersion API.
34+
func getVersionInfo() *_OSVERSIONINFOEXW {
35+
info := _OSVERSIONINFOEXW{}
36+
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
37+
rtlGetVersion(&info)
38+
return &info
39+
}
2640

2741
// Version retrieves the major, minor, and build version numbers
2842
// of the current Windows OS from the RtlGetVersion API.
2943
func Version() (major, minor, build uint32) {
30-
info := _OSVERSIONINFOW{}
31-
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
32-
rtlGetVersion(&info)
44+
info := getVersionInfo()
3345
return info.majorVersion, info.minorVersion, info.buildNumber
3446
}
3547

48+
// SupportUnlimitedTransmitFile indicates whether the current
49+
// Windows version's TransmitFile function imposes any
50+
// concurrent operation limits.
51+
// Workstation and client versions of Windows limit the number
52+
// of concurrent TransmitFile operations allowed on the system
53+
// to a maximum of two. Please see:
54+
// https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
55+
// https://golang.org/issue/73746
56+
var SupportUnlimitedTransmitFile = sync.OnceValue(func() bool {
57+
info := getVersionInfo()
58+
return info.productType != VER_NT_WORKSTATION
59+
})
60+
3661
var (
3762
supportTCPKeepAliveIdle bool
3863
supportTCPKeepAliveInterval bool

src/internal/syscall/windows/zsyscall_windows.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/net/sendfile_nonwindows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2025 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 !windows
6+
7+
package net
8+
9+
// Always true except for workstation and client versions of Windows
10+
func supportsUnlimitedSendFile() bool {
11+
return true
12+
}

src/net/sendfile_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ const (
3131
// expectSendfile runs f, and verifies that internal/poll.SendFile successfully handles
3232
// a write to wantConn during f's execution.
3333
//
34-
// On platforms where supportsSendfile is false, expectSendfile runs f but does not
35-
// expect a call to SendFile.
34+
// On platforms where supportsSendfile is false, and on platforms where SendFile's
35+
// concurrency is limited, expectSendfile runs f but does not expect a call to SendFile.
3636
func expectSendfile(t *testing.T, wantConn Conn, f func()) {
3737
t.Helper()
38-
if !supportsSendfile {
38+
if !supportsSendfile || !supportsUnlimitedSendFile() {
3939
f()
4040
return
4141
}

src/net/sendfile_windows.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 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+
package net
6+
7+
import "internal/syscall/windows"
8+
9+
// Workstation and client versions of Windows limit the number
10+
// of concurrent TransmitFile operations allowed on the system
11+
// to a maximum of two. Please see:
12+
// https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
13+
// https://golang.org/issue/73746
14+
func supportsUnlimitedSendFile() bool {
15+
return windows.SupportUnlimitedTransmitFile()
16+
}

src/net/tcpsock_posix.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ func (c *TCPConn) readFrom(r io.Reader) (int64, error) {
4848
if n, err, handled := spliceFrom(c.fd, r); handled {
4949
return n, err
5050
}
51-
if n, err, handled := sendFile(c.fd, r); handled {
52-
return n, err
51+
if supportsUnlimitedSendFile() {
52+
if n, err, handled := sendFile(c.fd, r); handled {
53+
return n, err
54+
}
5355
}
5456
return genericReadFrom(c, r)
5557
}

0 commit comments

Comments
 (0)