forked from microsoft/xdp-for-windows
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xskfwd.c
325 lines (279 loc) · 10.4 KB
/
xskfwd.c
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <xdpapi.h>
#include <afxdp_helper.h>
const CHAR *UsageText =
"xskfwd.exe <IfIndex>"
"\n"
"Forwards RX traffic using an XDP program and AF_XDP sockets. This sample\n"
"application forwards traffic on the specified IfIndex originally destined to\n"
"UDP port 1234 back to the sender. Only the 0th data path queue on the interface\n"
"is used.\n"
;
const XDP_HOOK_ID XdpInspectRxL2 = {
.Layer = XDP_HOOK_L2,
.Direction = XDP_HOOK_RX,
.SubLayer = XDP_HOOK_INSPECT,
};
#define LOGERR(...) \
fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
static
VOID
TranslateRxToTx(
_Inout_ UCHAR *Frame,
_In_ UINT32 Length
)
{
UCHAR MacAddress[6];
//
// This function echo a UDP datagram back to its sender at the Ethernet
// layer by swapping source and destination MAC addresses. The IP and UDP
// layer headers are left as-is.
//
if (Length >= sizeof(MacAddress) * 2) {
RtlCopyMemory(MacAddress, Frame, sizeof(MacAddress));
RtlCopyMemory(Frame, Frame + sizeof(MacAddress), sizeof(MacAddress));
RtlCopyMemory(Frame + sizeof(MacAddress), MacAddress, sizeof(MacAddress));
}
}
INT
__cdecl
main(
INT argc,
CHAR **argv
)
{
const XDP_API_TABLE *XdpApi;
HRESULT Result;
HANDLE Socket;
HANDLE Program;
UINT32 IfIndex;
XDP_RULE Rule = {0};
UCHAR Frame[1514];
XSK_UMEM_REG UmemReg = {0};
const UINT32 RingSize = 1;
XSK_RING_INFO_SET RingInfo;
UINT32 OptionLength;
XSK_RING RxRing;
XSK_RING RxFillRing;
XSK_RING TxRing;
XSK_RING TxCompRing;
UINT32 RingIndex;
if (argc < 2) {
fprintf(stderr, UsageText);
return EXIT_FAILURE;
}
IfIndex = atoi(argv[1]);
//
// Retrieve the XDP API dispatch table.
//
Result = XdpOpenApi(XDP_API_VERSION_1, &XdpApi);
if (FAILED(Result)) {
LOGERR("XdpOpenApi failed: %x", Result);
return EXIT_FAILURE;
}
//
// Create an AF_XDP socket. The newly created socket is not connected.
//
Result = XdpApi->XskCreate(&Socket);
if (FAILED(Result)) {
LOGERR("XskCreate failed: %x", Result);
return EXIT_FAILURE;
}
//
// Register our frame buffer(s) with the AF_XDP socket. For simplicity, we
// register a buffer containing a single frame. The registered buffer is
// available mapped into AF_XDP's address space, and elements of descriptor
// rings refer to relative offets from the start of the UMEM.
//
UmemReg.TotalSize = sizeof(Frame);
UmemReg.ChunkSize = sizeof(Frame);
UmemReg.Address = Frame;
Result = XdpApi->XskSetSockopt(Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg));
if (FAILED(Result)) {
LOGERR("XSK_UMEM_REG failed: %x", Result);
return EXIT_FAILURE;
}
//
// Bind the AF_XDP socket to the specified interface and 0th data path
// queue, and indicate the intent to perform RX and TX actions.
//
Result = XdpApi->XskBind(Socket, IfIndex, 0, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
if (FAILED(Result)) {
LOGERR("XskBind failed: %x", Result);
return EXIT_FAILURE;
}
//
// Request a set of RX, RX fill, TX, and TX completion descriptor rings.
// Request a capacity of one frame in each ring for simplicity. XDP will
// create the rings and map them into the process address space as part of
// the XskActivate step further below.
//
Result = XdpApi->XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
if (FAILED(Result)) {
LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed: %x", Result);
return EXIT_FAILURE;
}
Result = XdpApi->XskSetSockopt(Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize));
if (FAILED(Result)) {
LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed: %x", Result);
return EXIT_FAILURE;
}
Result = XdpApi->XskSetSockopt(Socket, XSK_SOCKOPT_TX_RING_SIZE, &RingSize, sizeof(RingSize));
if (FAILED(Result)) {
LOGERR("XSK_SOCKOPT_TX_RING_SIZE failed: %x", Result);
return EXIT_FAILURE;
}
Result = XdpApi->XskSetSockopt(Socket, XSK_SOCKOPT_TX_COMPLETION_RING_SIZE, &RingSize, sizeof(RingSize));
if (FAILED(Result)) {
LOGERR("XSK_SOCKOPT_TX_COMPLETION_RING_SIZE failed: %x", Result);
return EXIT_FAILURE;
}
//
// Activate the AF_XDP socket. Once activated, descriptor rings are
// available and RX and TX can occur.
//
Result = XdpApi->XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
if (FAILED(Result)) {
LOGERR("XskActivate failed: %x", Result);
return EXIT_FAILURE;
}
//
// Retrieve the RX, RX fill, TX, and TX completion ring info from AF_XDP.
//
OptionLength = sizeof(RingInfo);
Result = XdpApi->XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
if (FAILED(Result)) {
LOGERR("XSK_SOCKOPT_RING_INFO failed: %x", Result);
return EXIT_FAILURE;
}
//
// Initialize the optional AF_XDP helper library with the socket ring info.
// These helpers simplify manipulation of the shared rings.
//
XskRingInitialize(&RxRing, &RingInfo.Rx);
XskRingInitialize(&RxFillRing, &RingInfo.Fill);
XskRingInitialize(&TxRing, &RingInfo.Tx);
XskRingInitialize(&TxCompRing, &RingInfo.Completion);
//
// Place an empty frame descriptor into the RX fill ring. When the AF_XDP
// socket receives a frame from XDP, it will pop the first available
// frame descriptor from the RX fill ring and copy the frame payload into
// that descriptor's buffer.
//
XskRingProducerReserve(&RxFillRing, 1, &RingIndex);
//
// The value of each RX fill and TX completion ring element is an offset
// from the start of the UMEM to the start of the frame. Since this sample
// is using a single buffer, the offset is always zero.
//
*(UINT32 *)XskRingGetElement(&RxFillRing, RingIndex) = 0;
XskRingProducerSubmit(&RxFillRing, 1);
//
// Create an XDP program using the parsed rule at the L2 inspect hook point.
// The rule intercepts all UDP frames destined to local port 1234 and
// redirects them to the AF_XDP socket.
//
Rule.Match = XDP_MATCH_UDP_DST;
Rule.Pattern.Port = 53764; // htons(1234)
Rule.Action = XDP_PROGRAM_ACTION_REDIRECT;
Rule.Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSK;
Rule.Redirect.Target = Socket;
Result = XdpApi->XdpCreateProgram(IfIndex, &XdpInspectRxL2, 0, 0, &Rule, 1, &Program);
if (FAILED(Result)) {
LOGERR("XdpCreateProgram failed: %x", Result);
return EXIT_FAILURE;
}
//
// Continuously scan the RX ring and TX completion ring for new descriptors.
// For simplicity, this loop performs actions one frame at a time. This can
// be optimized further by consuming, reserving, and submitting batches of
// frames across each XskRing* function.
//
while (TRUE) {
if (XskRingConsumerReserve(&RxRing, 1, &RingIndex) == 1) {
XSK_BUFFER_DESCRIPTOR *RxBuffer;
XSK_BUFFER_DESCRIPTOR *TxBuffer;
XSK_NOTIFY_RESULT_FLAGS NotifyResult;
//
// A new RX frame appeared on the RX ring. Forward it to the TX
// ring.
RxBuffer = XskRingGetElement(&RxRing, RingIndex);
//
// Reserve space in the TX ring. Since we're only using one frame in
// this sample, space is guaranteed to be available.
//
XskRingProducerReserve(&TxRing, 1, &RingIndex);
TxBuffer = XskRingGetElement(&TxRing, RingIndex);
//
// Swap source and destination fields within the frame payload.
//
TranslateRxToTx(
&Frame[RxBuffer->Address.BaseAddress + RxBuffer->Address.Offset],
RxBuffer->Length);
//
// Since the RX and TX buffer descriptor formats are identical,
// simply copy the descriptor across rings.
//
*TxBuffer = *RxBuffer;
//
// Advance the consumer index of the RX ring and the producer index
// of the TX ring, which allows XDP to write and read the descriptor
// elements respectively.
//
XskRingConsumerRelease(&RxRing, 1);
XskRingProducerSubmit(&TxRing, 1);
//
// Notify XDP that a new element is available on the TX ring, since
// XDP isn't continuously checking the shared ring. This can be
// optimized further using the XskRingProducerNeedPoke helper.
//
Result = XdpApi->XskNotifySocket(Socket, XSK_NOTIFY_FLAG_POKE_TX, 0, &NotifyResult);
if (FAILED(Result)) {
LOGERR("XskNotifySocket failed: %x", Result);
return EXIT_FAILURE;
}
}
if (XskRingConsumerReserve(&TxCompRing, 1, &RingIndex) == 1) {
UINT32 *Tx;
UINT32 *Rx;
//
// A TX frame address appeared on the TX completion ring. Recycle
// the frame onto the RX fill ring.
//
Tx = XskRingGetElement(&TxCompRing, RingIndex);
//
// Reserve space in the RX fill ring. Since we're only using one
// frame in this sample, space is guaranteed to be available.
//
XskRingProducerReserve(&RxFillRing, 1, &RingIndex);
Rx = XskRingGetElement(&RxFillRing, RingIndex);
//
// Since the TX completion and RX fill descriptor formats are
// identical, simply copy the descriptor across rings.
//
*Rx = *Tx;
//
// Advance the consumer index of the RX ring and the producer index
// of the TX ring, which allows XDP to write and read the descriptor
// elements respectively.
//
XskRingConsumerRelease(&TxCompRing, 1);
XskRingProducerSubmit(&RxFillRing, 1);
}
}
//
// Close the XDP program. Traffic will no longer be intercepted by XDP.
//
CloseHandle(Program);
//
// Close the AF_XDP socket. All socket resources will be cleaned up by XDP.
//
CloseHandle(Socket);
return EXIT_SUCCESS;
}