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

This commit is contained in:
Shibi J M 2025-05-20 09:23:19 +05:30
parent c8bf388bad
commit 315ddc0cd8
8 changed files with 72 additions and 12 deletions

View File

@ -256,3 +256,7 @@ type FILE_COMPLETION_INFORMATION struct {
Port syscall.Handle Port syscall.Handle
Key uintptr 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

View File

@ -11,28 +11,53 @@ import (
"unsafe" "unsafe"
) )
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfow // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
type _OSVERSIONINFOW struct { type _OSVERSIONINFOEXW struct {
osVersionInfoSize uint32 osVersionInfoSize uint32
majorVersion uint32 majorVersion uint32
minorVersion uint32 minorVersion uint32
buildNumber uint32 buildNumber uint32
platformId uint32 platformId uint32
csdVersion [128]uint16 csdVersion [128]uint16
servicePackMajor uint16
servicePackMinor uint16
suiteMask uint16
productType byte
reserved byte
} }
// According to documentation, RtlGetVersion function always succeeds. // 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 // Version retrieves the major, minor, and build version numbers
// of the current Windows OS from the RtlGetVersion API. // of the current Windows OS from the RtlGetVersion API.
func Version() (major, minor, build uint32) { func Version() (major, minor, build uint32) {
info := _OSVERSIONINFOW{} info := getVersionInfo()
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
rtlGetVersion(&info)
return info.majorVersion, info.minorVersion, info.buildNumber 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 ( var (
supportTCPKeepAliveIdle bool supportTCPKeepAliveIdle bool
supportTCPKeepAliveInterval bool supportTCPKeepAliveInterval bool

View File

@ -539,7 +539,7 @@ func NtSetInformationFile(handle syscall.Handle, iosb *IO_STATUS_BLOCK, inBuffer
return return
} }
func rtlGetVersion(info *_OSVERSIONINFOW) { func rtlGetVersion(info *_OSVERSIONINFOEXW) {
syscall.Syscall(procRtlGetVersion.Addr(), 1, uintptr(unsafe.Pointer(info)), 0, 0) syscall.Syscall(procRtlGetVersion.Addr(), 1, uintptr(unsafe.Pointer(info)), 0, 0)
return return
} }

View File

@ -12,8 +12,6 @@ import (
"syscall" "syscall"
) )
const supportsSendfile = true
// sendFile copies the contents of r to c using the sendfile // sendFile copies the contents of r to c using the sendfile
// system call to minimize copies. // system call to minimize copies.
// //
@ -22,6 +20,9 @@ const supportsSendfile = true
// //
// if handled == false, sendFile performed no work. // if handled == false, sendFile performed no work.
func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { 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 var remain int64 = 0 // 0 writes the entire file
lr, ok := r.(*io.LimitedReader) lr, ok := r.(*io.LimitedReader)
if ok { if ok {

View File

@ -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
}

View File

@ -8,7 +8,9 @@ package net
import "io" import "io"
const supportsSendfile = false func supportsSendfile() bool {
return false
}
func sendFile(c *netFD, r io.Reader) (n int64, err error, handled bool) { func sendFile(c *netFD, r io.Reader) (n int64, err error, handled bool) {
return 0, nil, false return 0, nil, false

View File

@ -31,11 +31,11 @@ const (
// expectSendfile runs f, and verifies that internal/poll.SendFile successfully handles // expectSendfile runs f, and verifies that internal/poll.SendFile successfully handles
// a write to wantConn during f's execution. // 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. // expect a call to SendFile.
func expectSendfile(t *testing.T, wantConn Conn, f func()) { func expectSendfile(t *testing.T, wantConn Conn, f func()) {
t.Helper() t.Helper()
if !supportsSendfile { if !supportsSendfile() {
f() f()
return return
} }

View File

@ -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()
}