summaryrefslogtreecommitdiffhomepage
path: root/conn/winrio/rio_windows.go
blob: 0911998d5eff19e8bfe76b4b7552493f91bca6e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/* 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
	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)
		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.
		var iocp windows.Handle
		iocp, err = windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
		if err != nil {
			return
		}
		defer windows.CloseHandle(iocp)
		var overlapped windows.Overlapped
		cq, err = CreateIOCPCompletionQueue(2, iocp, 0, &overlapped)
		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,
		key:            key,
		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
}