Skip to content

net: avoid using Windows' TransmitFile on non-server machines #73758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/internal/syscall/windows/types_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,7 @@ type FILE_COMPLETION_INFORMATION struct {
Port syscall.Handle
Key uintptr
}

// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
const VER_NT_WORKSTATION = 0x0000001
37 changes: 31 additions & 6 deletions src/internal/syscall/windows/version_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,53 @@ import (
"unsafe"
)

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfow
type _OSVERSIONINFOW struct {
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
type _OSVERSIONINFOEXW struct {
osVersionInfoSize uint32
majorVersion uint32
minorVersion uint32
buildNumber uint32
platformId uint32
csdVersion [128]uint16
servicePackMajor uint16
servicePackMinor uint16
suiteMask uint16
productType byte
reserved byte
}

// According to documentation, RtlGetVersion function always succeeds.
//sys rtlGetVersion(info *_OSVERSIONINFOW) = ntdll.RtlGetVersion
//sys rtlGetVersion(info *_OSVERSIONINFOEXW) = ntdll.RtlGetVersion

// Retrieves version information of the current Windows OS
// from the RtlGetVersion API.
func getVersionInfo() *_OSVERSIONINFOEXW {
info := _OSVERSIONINFOEXW{}
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
rtlGetVersion(&info)
return &info
}

// Version retrieves the major, minor, and build version numbers
// of the current Windows OS from the RtlGetVersion API.
func Version() (major, minor, build uint32) {
info := _OSVERSIONINFOW{}
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
rtlGetVersion(&info)
info := getVersionInfo()
return info.majorVersion, info.minorVersion, info.buildNumber
}

// SupportUnlimitedTransmitFile indicates whether the current
// Windows version's TransmitFile function imposes any
// concurrent operation limits.
// Workstation and client versions of Windows limit the number
// of concurrent TransmitFile operations allowed on the system
// to a maximum of two. Please see:
// https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
// https://golang.org/issue/73746
var SupportUnlimitedTransmitFile = sync.OnceValue(func() bool {
info := getVersionInfo()
return info.productType != VER_NT_WORKSTATION
})

var (
supportTCPKeepAliveIdle bool
supportTCPKeepAliveInterval bool
Expand Down
2 changes: 1 addition & 1 deletion src/internal/syscall/windows/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/net/sendfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"syscall"
)

const supportsSendfile = true

// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
Expand All @@ -22,6 +20,9 @@ const supportsSendfile = true
//
// if handled == false, sendFile performed no work.
func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
if !supportsSendfile() {
return 0, nil, false
}
var remain int64 = 0 // 0 writes the entire file
lr, ok := r.(*io.LimitedReader)
if ok {
Expand Down
12 changes: 12 additions & 0 deletions src/net/sendfile_nonwindows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2025 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.

//go:build linux || (darwin && !ios) || dragonfly || freebsd || solaris

package net

// Always true except for workstation and client versions of Windows
func supportsSendfile() bool {
return true
}
4 changes: 3 additions & 1 deletion src/net/sendfile_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ package net

import "io"

const supportsSendfile = false
func supportsSendfile() bool {
return false
}

func sendFile(c *netFD, r io.Reader) (n int64, err error, handled bool) {
return 0, nil, false
Expand Down
4 changes: 2 additions & 2 deletions src/net/sendfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ const (
// expectSendfile runs f, and verifies that internal/poll.SendFile successfully handles
// a write to wantConn during f's execution.
//
// On platforms where supportsSendfile is false, expectSendfile runs f but does not
// On platforms where supportsSendfile() is false, expectSendfile runs f but does not
// expect a call to SendFile.
func expectSendfile(t *testing.T, wantConn Conn, f func()) {
t.Helper()
if !supportsSendfile {
if !supportsSendfile() {
f()
return
}
Expand Down
16 changes: 16 additions & 0 deletions src/net/sendfile_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2025 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"

// Workstation and client versions of Windows limit the number
// of concurrent TransmitFile operations allowed on the system
// to a maximum of two. Please see:
// https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
// https://golang.org/issue/73746
func supportsSendfile() bool {
return windows.SupportUnlimitedTransmitFile()
}