conn: implement RIO for fast Windows UDP sockets

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld
2021-02-25 15:08:08 +01:00
parent f9dac7099e
commit 3c11c0308e
9 changed files with 852 additions and 71 deletions
+2
View File
@@ -128,6 +128,8 @@ func (bind *StdNetBind) Close() error {
err2 = bind.ipv6.Close()
bind.ipv6 = nil
}
bind.blackhole4 = false
bind.blackhole6 = false
if err1 != nil {
return err1
}
File diff suppressed because it is too large Load Diff
-59
View File
@@ -1,59 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package conn
import (
"encoding/binary"
"unsafe"
"golang.org/x/sys/windows"
)
const (
sockoptIP_UNICAST_IF = 31
sockoptIPV6_UNICAST_IF = 31
)
func (bind *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
/* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */
bytes := make([]byte, 4)
binary.BigEndian.PutUint32(bytes, interfaceIndex)
interfaceIndex = *(*uint32)(unsafe.Pointer(&bytes[0]))
sysconn, err := bind.ipv4.SyscallConn()
if err != nil {
return err
}
err2 := sysconn.Control(func(fd uintptr) {
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, sockoptIP_UNICAST_IF, int(interfaceIndex))
})
if err2 != nil {
return err2
}
if err != nil {
return err
}
bind.blackhole4 = blackhole
return nil
}
func (bind *StdNetBind) BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error {
sysconn, err := bind.ipv6.SyscallConn()
if err != nil {
return err
}
err2 := sysconn.Control(func(fd uintptr) {
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, sockoptIPV6_UNICAST_IF, int(interfaceIndex))
})
if err2 != nil {
return err2
}
if err != nil {
return err
}
bind.blackhole6 = blackhole
return nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !linux
// +build !linux,!windows
/* SPDX-License-Identifier: MIT
*
+243
View File
@@ -0,0 +1,243 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2021 WireGuard LLC. All Rights Reserved.
*/
package winrio
import (
"log"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
MsgDontNotify = 1
MsgDefer = 2
MsgWaitAll = 4
MsgCommitOnly = 8
MaxCqSize = 0x8000000
invalidBufferId = 0xFFFFFFFF
invalidCq = 0
invalidRq = 0
corruptCq = 0xFFFFFFFF
)
var extensionFunctionTable struct {
cbSize uint32
rioReceive uintptr
rioReceiveEx uintptr
rioSend uintptr
rioSendEx uintptr
rioCloseCompletionQueue uintptr
rioCreateCompletionQueue uintptr
rioCreateRequestQueue uintptr
rioDequeueCompletion uintptr
rioDeregisterBuffer uintptr
rioNotify uintptr
rioRegisterBuffer uintptr
rioResizeCompletionQueue uintptr
rioResizeRequestQueue uintptr
}
type Cq uintptr
type Rq uintptr
type BufferId uintptr
type Buffer struct {
Id BufferId
Offset uint32
Length uint32
}
type Result struct {
Status int32
BytesTransferred uint32
SocketContext uint64
RequestContext uint64
}
type notificationCompletionType uint32
const (
eventCompletion notificationCompletionType = 1
iocpCompletion notificationCompletionType = 2
)
type eventNotificationCompletion struct {
completionType notificationCompletionType
event windows.Handle
notifyReset uint32
}
type iocpNotificationCompletion struct {
completionType notificationCompletionType
iocp windows.Handle
key uintptr
overlapped *windows.Overlapped
}
var initialized sync.Once
var available bool
func Initialize() bool {
initialized.Do(func() {
var (
err error
socket windows.Handle
cq Cq
)
defer func() {
if err == nil {
return
}
if maj, _, _ := windows.RtlGetNtVersionNumbers(); maj <= 7 {
return
}
log.Printf("Registered I/O is unavailable: %v", err)
}()
socket, err = Socket(windows.AF_INET, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
if err != nil {
return
}
defer windows.CloseHandle(socket)
var WSAID_MULTIPLE_RIO = &windows.GUID{0x8509e081, 0x96dd, 0x4005, [8]byte{0xb1, 0x65, 0x9e, 0x2e, 0xe8, 0xc7, 0x9e, 0x3f}}
const SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER = 0xc8000024
ob := uint32(0)
err = windows.WSAIoctl(socket, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER,
(*byte)(unsafe.Pointer(WSAID_MULTIPLE_RIO)), uint32(unsafe.Sizeof(*WSAID_MULTIPLE_RIO)),
(*byte)(unsafe.Pointer(&extensionFunctionTable)), uint32(unsafe.Sizeof(extensionFunctionTable)),
&ob, nil, 0)
if err != nil {
return
}
// While we should be able to stop here, after getting the function pointers, some anti-virus actually causes
// failures in RIOCreateRequestQueue, so keep going to be certain this is supported.
cq, err = CreatePolledCompletionQueue(2)
if err != nil {
return
}
defer CloseCompletionQueue(cq)
_, err = CreateRequestQueue(socket, 1, 1, 1, 1, cq, cq, 0)
if err != nil {
return
}
available = true
})
return available
}
func Socket(af, typ, proto int32) (windows.Handle, error) {
return windows.WSASocket(af, typ, proto, nil, 0, windows.WSA_FLAG_REGISTERED_IO)
}
func CloseCompletionQueue(cq Cq) {
_, _, _ = syscall.Syscall(extensionFunctionTable.rioCloseCompletionQueue, 1, uintptr(cq), 0, 0)
}
func CreateEventCompletionQueue(queueSize uint32, event windows.Handle, notifyReset bool) (Cq, error) {
notificationCompletion := &eventNotificationCompletion{
completionType: eventCompletion,
event: event,
}
if notifyReset {
notificationCompletion.notifyReset = 1
}
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), uintptr(unsafe.Pointer(notificationCompletion)), 0)
if ret == invalidCq {
return 0, err
}
return Cq(ret), nil
}
func CreateIOCPCompletionQueue(queueSize uint32, iocp windows.Handle, key uintptr, overlapped *windows.Overlapped) (Cq, error) {
notificationCompletion := &iocpNotificationCompletion{
completionType: iocpCompletion,
iocp: iocp,
overlapped: overlapped,
}
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), uintptr(unsafe.Pointer(notificationCompletion)), 0)
if ret == invalidCq {
return 0, err
}
return Cq(ret), nil
}
func CreatePolledCompletionQueue(queueSize uint32) (Cq, error) {
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), 0, 0)
if ret == invalidCq {
return 0, err
}
return Cq(ret), nil
}
func CreateRequestQueue(socket windows.Handle, maxOutstandingReceive, maxReceiveDataBuffers, maxOutstandingSend, maxSendDataBuffers uint32, receiveCq, sendCq Cq, socketContext uintptr) (Rq, error) {
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioCreateRequestQueue, 8, uintptr(socket), uintptr(maxOutstandingReceive), uintptr(maxReceiveDataBuffers), uintptr(maxOutstandingSend), uintptr(maxSendDataBuffers), uintptr(receiveCq), uintptr(sendCq), socketContext, 0)
if ret == invalidRq {
return 0, err
}
return Rq(ret), nil
}
func DequeueCompletion(cq Cq, results []Result) uint32 {
var array uintptr
if len(results) > 0 {
array = uintptr(unsafe.Pointer(&results[0]))
}
ret, _, _ := syscall.Syscall(extensionFunctionTable.rioDequeueCompletion, 3, uintptr(cq), array, uintptr(len(results)))
if ret == corruptCq {
panic("cq is corrupt")
}
return uint32(ret)
}
func DeregisterBuffer(id BufferId) {
_, _, _ = syscall.Syscall(extensionFunctionTable.rioDeregisterBuffer, 1, uintptr(id), 0, 0)
}
func RegisterBuffer(buffer []byte) (BufferId, error) {
var buf unsafe.Pointer
if len(buffer) > 0 {
buf = unsafe.Pointer(&buffer[0])
}
return RegisterPointer(buf, uint32(len(buffer)))
}
func RegisterPointer(ptr unsafe.Pointer, size uint32) (BufferId, error) {
ret, _, err := syscall.Syscall(extensionFunctionTable.rioRegisterBuffer, 2, uintptr(ptr), uintptr(size), 0)
if ret == invalidBufferId {
return 0, err
}
return BufferId(ret), nil
}
func SendEx(rq Rq, buf *Buffer, dataBufferCount uint32, localAddress, remoteAddress, controlContext, flags *Buffer, sflags uint32, requestContext uintptr) error {
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioSendEx, 9, uintptr(rq), uintptr(unsafe.Pointer(buf)), uintptr(dataBufferCount), uintptr(unsafe.Pointer(localAddress)), uintptr(unsafe.Pointer(remoteAddress)), uintptr(unsafe.Pointer(controlContext)), uintptr(unsafe.Pointer(flags)), uintptr(sflags), requestContext)
if ret == 0 {
return err
}
return nil
}
func ReceiveEx(rq Rq, buf *Buffer, dataBufferCount uint32, localAddress, remoteAddress, controlContext, flags *Buffer, sflags uint32, requestContext uintptr) error {
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioReceiveEx, 9, uintptr(rq), uintptr(unsafe.Pointer(buf)), uintptr(dataBufferCount), uintptr(unsafe.Pointer(localAddress)), uintptr(unsafe.Pointer(remoteAddress)), uintptr(unsafe.Pointer(controlContext)), uintptr(unsafe.Pointer(flags)), uintptr(sflags), requestContext)
if ret == 0 {
return err
}
return nil
}
func Notify(cq Cq) error {
ret, _, _ := syscall.Syscall(extensionFunctionTable.rioNotify, 1, uintptr(cq), 0, 0)
if ret != 0 {
return windows.Errno(ret)
}
return nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !android,!ios
// +build !android,!ios,!windows
/* SPDX-License-Identifier: MIT
*
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package device
const (
QueueStagedSize = 128
QueueOutboundSize = 1024
QueueInboundSize = 1024
QueueHandshakeSize = 1024
MaxSegmentSize = 2048 - 32 // largest possible UDP datagram
PreallocatedBuffersPerPool = 0 // Disable and allow for infinite memory growth
)
+3 -3
View File
@@ -3,7 +3,7 @@ module golang.zx2c4.com/wireguard
go 1.16
require (
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7
)
+6 -7
View File
@@ -1,17 +1,16 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7 h1:pk3Y+QnSKjMLfO/HIqzn/Zvv3/IHjRPhwblrmUuodzw=
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=