diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9c7cfc5f7..eeacf3871 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -729,6 +729,64 @@ jobs:
shell: PowerShell
run: tools/check-drivers.ps1 -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Verbose -AllowEbpf:($env:XDP_EBPF_BASELINE -ne '0')
+ sample_tests:
+ name: Sample Tests
+ needs: build
+ strategy:
+ fail-fast: false
+ matrix:
+ windows: [2022, 2025]
+ configuration: [Release, Debug]
+ platform: [x64]
+ runs-on:
+ - self-hosted
+ - "1ES.Pool=xdp-ci-functional${{ matrix.platform != 'x64' && format('-{0}', matrix.platform) || '' }}-gh"
+ - "1ES.ImageOverride=WS${{ matrix.windows }}${{ matrix.platform != 'x64' && format('-{0}', matrix.platform) || '' }}-Functional"
+ - "JobId=samples_${{ matrix.windows }}_${{ matrix.configuration }}_${{ matrix.platform }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}"
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ with:
+ sparse-checkout: tools
+ - name: Check Drivers
+ shell: PowerShell
+ run: tools/check-drivers.ps1 -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Verbose -AllowEbpf
+ - name: Prepare Machine
+ shell: PowerShell
+ run: tools/prepare-machine.ps1 -ForFunctionalTest -Platform ${{ matrix.platform }} -RequireNoReboot -Verbose
+ - name: Download Artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
+ with:
+ name: bin_${{ matrix.configuration }}_${{ matrix.platform }}
+ path: artifacts/bin
+ - name: Test rxfilter sample
+ shell: PowerShell
+ timeout-minutes: 15
+ run: tools/sample-rxfilter-test.ps1 -Verbose -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Duration 10
+ - name: Test xskfwd sample
+ shell: PowerShell
+ timeout-minutes: 15
+ run: tools/sample-xskfwd-test.ps1 -Verbose -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Duration 10
+ - name: Test xskrestricted sample
+ shell: PowerShell
+ timeout-minutes: 15
+ run: tools/sample-xskrestricted-test.ps1 -Verbose -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -TimeoutSeconds 10
+ - name: Convert Logs
+ if: ${{ always() }}
+ timeout-minutes: 15
+ shell: PowerShell
+ run: tools/log.ps1 -Convert -Name sample_* -Verbose -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }}
+ - name: Upload Logs
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
+ if: ${{ always() }}
+ with:
+ name: logs_samples_win${{ matrix.windows }}_${{ matrix.configuration }}_${{ matrix.platform }}
+ path: artifacts/logs
+ - name: Check Drivers
+ if: ${{ always() }}
+ shell: PowerShell
+ run: tools/check-drivers.ps1 -Config ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Verbose -AllowEbpf:($env:XDP_EBPF_BASELINE -ne '0')
+
create_artifacts:
name: Create Release Artifacts
needs: build
@@ -775,7 +833,7 @@ jobs:
all_tests_complete:
name: All Tests Complete
if: always()
- needs: [functional_tests, stress_tests, pktfuzz_tests, xskperf_tests, ring_perf_tests, rxfilter_perf_tests, xskfwdkm_test, xskmaprx_test, xskrestricted_test, downlevel_functional_tests]
+ needs: [build, build_allpackage, onebranch_build_validation, functional_tests, stress_tests, pktfuzz_tests, xskperf_tests, ring_perf_tests, rxfilter_perf_tests, xskfwdkm_test, xskmaprx_test, xskrestricted_test, downlevel_functional_tests, create_artifacts, sample_tests]
runs-on: ubuntu-latest
permissions: {}
steps:
diff --git a/docs/api/XDP_HOOK_ID.md b/docs/api/XDP_HOOK_ID.md
index 386033092..118c98aff 100644
--- a/docs/api/XDP_HOOK_ID.md
+++ b/docs/api/XDP_HOOK_ID.md
@@ -1,5 +1,9 @@
# XDP_HOOK_ID structure
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Specifies an XDP hook point.
## Syntax
diff --git a/docs/api/XDP_MATCH_PATTERN.md b/docs/api/XDP_MATCH_PATTERN.md
index 9f8ab591b..aa0915667 100644
--- a/docs/api/XDP_MATCH_PATTERN.md
+++ b/docs/api/XDP_MATCH_PATTERN.md
@@ -1,5 +1,9 @@
# XDP_MATCH_PATTERN structure
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Specifies an XDP inspection program rule match pattern.
```C
diff --git a/docs/api/XDP_MATCH_TYPE.md b/docs/api/XDP_MATCH_TYPE.md
index 50c2af135..e00dfe33e 100644
--- a/docs/api/XDP_MATCH_TYPE.md
+++ b/docs/api/XDP_MATCH_TYPE.md
@@ -1,6 +1,10 @@
# XDP_MATCH_TYPE structure
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Specifies an XDP inspection program rule match type.
## Syntax
diff --git a/docs/api/XDP_RULE.md b/docs/api/XDP_RULE.md
index 70435beca..85bd6fbe0 100644
--- a/docs/api/XDP_RULE.md
+++ b/docs/api/XDP_RULE.md
@@ -1,5 +1,9 @@
# XDP_RULE structure
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Specifies an XDP inspection program rule.
## Syntax
diff --git a/docs/api/XDP_RULE_ACTION.md b/docs/api/XDP_RULE_ACTION.md
index 0ee3e4df4..d0bfb986e 100644
--- a/docs/api/XDP_RULE_ACTION.md
+++ b/docs/api/XDP_RULE_ACTION.md
@@ -1,5 +1,9 @@
# XDP_RULE_ACTION structure
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Specifies an XDP inspection program rule action.
## Syntax
diff --git a/docs/api/XdpCreateProgram.md b/docs/api/XdpCreateProgram.md
index fefe3de1b..215194af1 100644
--- a/docs/api/XdpCreateProgram.md
+++ b/docs/api/XdpCreateProgram.md
@@ -1,5 +1,9 @@
# XdpCreateProgram function
+> **Deprecated:** The built-in rules-based program API is planned for removal.
+> All users should migrate to eBPF programs. See the
+> [eBPF Integration Guide](../ebpf.md) for migration instructions and examples.
+
Creates a new XDP program.
## Syntax
diff --git a/docs/architecture.md b/docs/architecture.md
index c4e45883b..9335911dc 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -13,7 +13,7 @@ XDP for Windows is a high-performance packet processing framework inspired by Li
- **Native XDP**: Requires NIC driver support for maximum performance and direct data path access.
- Provides programmable hook points for packet inspection, modification, and redirection.
- Implements shared memory rings for high-speed packet transfer between kernel and user mode.
-- Supports eBPF (experimental) for programmable packet processing.
+- Supports [eBPF](./ebpf.md) for programmable packet processing.
### 2. XDP API Headers (User Mode APIs)
- Header-only APIs that expose the XDP API to applications (available for `XDP_API_VERSION_3` or later).
@@ -48,7 +48,8 @@ XDP for Windows is a high-performance packet processing framework inspired by Li
- The driver is signed and uses standard Windows security mechanisms.
## Extensibility
-- XDP for Windows is designed to be extensible via eBPF (experimental) and a primitive built-in rules-based program engine.
+- XDP for Windows is designed to be extensible via [eBPF programs](./ebpf.md) through integration with [eBPF for Windows](https://github.com/microsoft/ebpf-for-windows).
+- The legacy built-in rules-based program engine is deprecated and planned for removal. See the [eBPF Integration Guide](./ebpf.md) for migration guidance.
- New hook points and interface types can be added as needed.
## Comparison to Linux XDP
@@ -60,6 +61,8 @@ XDP for Windows is a high-performance packet processing framework inspired by Li
## References
- [Usage Guide](./usage.md)
- [AF_XDP API](./afxdp.md)
+- [eBPF Integration Guide](./ebpf.md)
+- [eBPF Redirect Map (XSKMAP)](./ebpf-redirect-map.md)
- [FAQ](./faq.md)
- [Threat Model](./threat-model.md)
diff --git a/docs/ebpf-redirect-map.md b/docs/ebpf-redirect-map.md
new file mode 100644
index 000000000..dee4db0ae
--- /dev/null
+++ b/docs/ebpf-redirect-map.md
@@ -0,0 +1,326 @@
+# eBPF Redirect Map (XSKMAP)
+
+## Overview
+
+The **XSKMAP** (`BPF_MAP_TYPE_XSKMAP`) is a specialized eBPF map type that
+enables XDP eBPF programs to redirect packets to
+[AF_XDP](afxdp.md) sockets. It is the primary mechanism for delivering
+wire-rate packet data to user-mode applications when using eBPF programs with
+XDP for Windows.
+
+User mode creates AF_XDP sockets (XSKs), populates the XSKMAP with socket
+handles keyed by RX queue index, and the eBPF program calls
+`bpf_redirect_map()` to steer matching packets into the appropriate socket.
+
+```mermaid
+flowchart LR
+ subgraph User Mode
+ S1["1. Create XSK"]
+ S2["2. Populate XSKMAP
key=queue_idx
val=xsk_handle"]
+ S3["3. Load & attach
eBPF program"]
+ S4["4. Receive pkts
via UMEM rings"]
+ S1 --> S2 --> S3 --> S4
+ end
+
+ subgraph Kernel
+ XSKMAP["XSKMAP
[0] -> XSK handle
[1] -> XSK handle"]
+ EBPF["eBPF program
bpf_redirect_map()"]
+ EBPF -- lookup --> XSKMAP
+ end
+
+ S2 -- populate --> XSKMAP
+ EBPF -- redirect --> S4
+```
+
+## Map Definition
+
+Declare the XSKMAP in your eBPF program using the BTF-style map syntax:
+
+```c
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+```
+
+| Field | Value | Description |
+|-------|-------|-------------|
+| `type` | `BPF_MAP_TYPE_XSKMAP` (16) | Identifies this as an XSK redirect map. |
+| `key` | `uint32_t` | The key type. Typically the RX queue index. |
+| `value` | `void *` | Opaque XSK socket handle (populated by user mode). |
+| `max_entries` | Application-defined | Should be >= the number of RX queues you intend to use. |
+
+## `bpf_redirect_map` Helper
+
+```c
+intptr_t bpf_redirect_map(void *map, uint32_t key, uint64_t flags);
+```
+
+### Parameters
+
+| Parameter | Description |
+|-----------|-------------|
+| `map` | Pointer to a `BPF_MAP_TYPE_XSKMAP` map. |
+| `key` | Lookup key into the map. Conventionally `ctx->rx_queue_index` so the packet is delivered to the socket bound to the same RX queue. |
+| `flags` | The lower 2 bits encode a fallback `xdp_action_t` value returned when the lookup or redirect fails. Common values: `XDP_PASS` (let the packet continue up the stack) or `XDP_DROP`. |
+
+### Return Value
+
+- **`XDP_REDIRECT`** -- on success. The XDP driver redirects the packet to the
+ AF_XDP socket found in the map.
+- **Fallback action** -- on failure (extracted from the low two bits of `flags`). Failure can occur when:
+ - The key does not exist in the map (no XSK handle for that queue).
+ - The XSK socket is not in a valid state for redirect (e.g., not yet
+ activated or already closing).
+
+## Examples
+
+### Basic XSK Redirect
+
+The simplest use case: redirect every packet to the AF_XDP socket bound to the
+same RX queue.
+
+```c
+// file: xsk_redirect.c
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+//
+// XSKMAP for AF_XDP socket redirection. User mode populates this map with
+// XSK handles keyed by RX queue index.
+//
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/xsk_redirect")
+int xsk_redirect(xdp_md_t *ctx) {
+ uint32_t index = ctx->rx_queue_index;
+ return bpf_redirect_map(&xsk_map, index, XDP_PASS);
+}
+```
+
+If the map lookup fails (e.g., no XSK is bound to this queue), the packet is
+passed up the normal networking stack (`XDP_PASS`).
+
+### Configurable Fallback Action
+
+Use a separate BPF array map to let user mode control the fallback action at
+runtime:
+
+```c
+// file: xsk_redirect_fallback.c
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+//
+// A single-element array map that controls the fallback action passed to
+// bpf_redirect_map. The test populates index 0 with the desired xdp_action
+// value (XDP_PASS, XDP_DROP, or XDP_TX).
+//
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint32_t);
+ __uint(max_entries, 1);
+} fallback_map SEC(".maps");
+
+SEC("xdp/xsk_redirect_fallback")
+int xsk_redirect_fallback(xdp_md_t *ctx) {
+ uint32_t index = ctx->rx_queue_index;
+ uint32_t zero = 0;
+ uint64_t fallback = XDP_PASS;
+
+ uint32_t *fb = bpf_map_lookup_elem(&fallback_map, &zero);
+ if (fb != NULL) {
+ fallback = *fb;
+ }
+
+ return bpf_redirect_map(&xsk_map, index, fallback);
+}
+```
+
+User mode writes the desired fallback action (e.g., `XDP_DROP`) to
+`fallback_map[0]` before or during program execution.
+
+### Filtered Redirect (Redirect Only Matching Packets)
+
+Combine packet parsing with `bpf_redirect_map` to redirect only specific
+traffic and let everything else pass:
+
+```c
+// file: udp_redirect.c
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/udp_redirect")
+int udp_redirect(xdp_md_t *ctx) {
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+
+ ETHERNET_HEADER *eth = data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end)
+ return XDP_PASS;
+
+ if (eth->Type != htons(ETHERNET_TYPE_IPV4))
+ return XDP_PASS;
+
+ IPV4_HEADER *ip = (IPV4_HEADER *)(eth + 1);
+ if ((char *)ip + sizeof(*ip) > (char *)data_end)
+ return XDP_PASS;
+
+ if (ip->Protocol != 17) // Not UDP
+ return XDP_PASS;
+
+ // Redirect all UDP traffic to the AF_XDP socket.
+ return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, XDP_PASS);
+}
+```
+
+## User-Mode Workflow
+
+The complete workflow for using `bpf_redirect_map` with AF_XDP:
+
+### Step 1: Create and Bind AF_XDP Sockets
+
+Create one XSK socket per RX queue using the [AF_XDP API](afxdp.md):
+
+```c
+HANDLE xsk;
+XskCreate(&xsk);
+XskBind(xsk, ifIndex, queueId, ...);
+XskActivate(xsk, ...);
+```
+
+### Step 2: Load the eBPF Program
+
+Use the eBPF for Windows APIs (libbpf-compatible) to load the native eBPF
+driver. Only native drivers (`.sys`) are supported by the official eBPF
+runtime -- JIT execution of BPF object files (`.o`) is not officially
+supported.
+
+```c
+struct bpf_object *obj = bpf_object__open("xsk_redirect.sys");
+bpf_object__load(obj);
+```
+
+### Step 3: Populate the XSKMAP
+
+After loading, find the map and insert XSK handles keyed by queue index:
+
+```c
+struct bpf_map *map = bpf_object__find_map_by_name(obj, "xsk_map");
+int map_fd = bpf_map__fd(map);
+
+for (uint32_t q = 0; q < queue_count; q++) {
+ bpf_map_update_elem(map_fd, &q, &xsk_handles[q], BPF_ANY);
+}
+```
+
+> **Important:** The XSKMAP is **read-only from within eBPF programs**. Only
+> user mode can insert, update, or delete entries. Calling
+> `bpf_map_lookup_elem`, `bpf_map_update_elem`, or `bpf_map_delete_elem` on an
+> XSKMAP from within a BPF program will fail.
+
+### Step 4: Attach the Program
+
+```c
+int prog_fd = bpf_program__fd(
+ bpf_object__find_program_by_name(obj, "xsk_redirect"));
+bpf_xdp_attach(ifIndex, prog_fd, 0, NULL);
+```
+
+### Step 5: Receive Packets
+
+Poll the XSK completion and RX rings to receive redirected packets:
+
+```c
+XskNotifySocket(xsk, XSK_NOTIFY_FLAG_WAIT_RX, ...);
+// Process packets from the RX ring
+```
+
+## XSKMAP Properties
+
+| Property | Value |
+|----------|-------|
+| Map type ID | `BPF_MAP_TYPE_XSKMAP` (16) |
+| Key type | `uint32_t` |
+| Value type | Opaque XSK handle (`void *`) |
+| Writable from eBPF | **No** -- read-only from BPF programs. User mode manages entries. |
+| Reference counting | XSK handles are reference-counted. Adding an entry increments the ref count; removing it decrements. |
+
+### Why Read-Only from eBPF?
+
+The XSKMAP provider sets `updates_original_value = TRUE`, which means the map
+stores the actual kernel XSK object pointer, not a copy. This design enables
+zero-copy redirect but requires that only trusted user-mode code manages the
+map's contents. From within a BPF program:
+
+- `bpf_map_lookup_elem(&xsk_map, &key)` returns `NULL`.
+- `bpf_map_update_elem(&xsk_map, &key, &val, 0)` returns a non-zero error.
+- `bpf_map_delete_elem(&xsk_map, &key)` returns a non-zero error.
+
+Only `bpf_redirect_map` can successfully read from the XSKMAP at runtime.
+
+## Diagnostics
+
+### Performance Counters
+
+| Counter | Description |
+|---------|-------------|
+| `EbpfXskMapLookupFailures` | Number of `bpf_redirect_map` calls where the XSKMAP lookup did not find an entry for the given key. |
+| `EbpfXskMapRedirectFailures` | Number of `bpf_redirect_map` calls where the XSK handle was found but the socket was not in a valid state for redirect. |
+
+### ETW Events
+
+| Event | Fields | Description |
+|-------|--------|-------------|
+| `EbpfRedirectMapLookupFailure` | Key, FallbackAction | The key was not found in the XSKMAP. |
+| `EbpfRedirectMapRedirectFailure` | Key, Xsk, FallbackAction | The XSK was found but could not accept the redirect. |
+| `EbpfRedirectMapSuccess` | Key, Xsk | The packet was successfully redirected. |
+
+### Capturing Traces
+
+```powershell
+.\tools\log.ps1 -Start
+# ... run workload ...
+.\tools\log.ps1 -Stop -Convert -SymbolPath Path\To\Symbols
+```
+
+## Troubleshooting
+
+| Symptom | Likely Cause | Resolution |
+|---------|--------------|------------|
+| All packets fall through to the fallback action | XSKMAP is empty or key mismatch | Verify user mode populated the map with the correct queue indices. |
+| `EbpfXskMapRedirectFailures` counter is incrementing | XSK socket not activated or already closing | Ensure `XskActivate` is called before attaching the program, and the socket is not being torn down. |
+| Program fails to load | eBPF verifier rejects the program | Check that `xdpbpfexport.exe` was run and the `xdp/ebpfhook.h` header matches the installed XDP version. |
+| `XdpEbpfEnabled` registry key is not set | eBPF attachment is disabled by default | Set `HKLM\SYSTEM\CurrentControlSet\Services\xdp\XdpEbpfEnabled` to `1`. |
+
+## See Also
+
+- [eBPF Integration Guide](ebpf.md)
+- [AF_XDP](afxdp.md)
+- [Registry Keys](registry-keys.md)
+- [Architecture](architecture.md)
diff --git a/docs/ebpf.md b/docs/ebpf.md
new file mode 100644
index 000000000..289314309
--- /dev/null
+++ b/docs/ebpf.md
@@ -0,0 +1,457 @@
+# eBPF Integration Guide
+
+## Overview
+
+XDP for Windows supports
+[eBPF](https://ebpf.io/)-based packet processing through integration with the
+[eBPF for Windows](https://github.com/microsoft/ebpf-for-windows) project.
+eBPF programs offer fully programmable packet inspection, filtering, and
+redirection -- replacing the limited built-in rules engine with arbitrary
+logic that runs safely inside the Windows kernel.
+
+> **Note:** The built-in rules-based program API (`XDP_RULE`, `XDP_MATCH_TYPE`,
+> `XdpCreateProgram`) is deprecated and planned for removal. All users should
+> migrate to eBPF programs. See [Migrating from Built-in Rules](#migrating-from-built-in-rules).
+
+### How the Two Projects Fit Together
+
+| Project | Role |
+|---------|------|
+| **[XDP for Windows](https://github.com/microsoft/xdp-for-windows)** | Provides the high-performance data path: NIC hook points, AF_XDP sockets, shared-memory rings, and the XDP driver (`xdp.sys`). Registers an XDP program type and helper functions with the eBPF runtime. |
+| **[eBPF for Windows](https://github.com/microsoft/ebpf-for-windows)** | Provides the eBPF runtime: program verification (PREVAIL verifier), JIT compilation (uBPF), map infrastructure, and the NMR-based extension model that XDP plugs into. |
+
+An eBPF program compiled to BPF bytecode is loaded by the eBPF for Windows
+runtime, verified for safety, JIT-compiled to native code, and then attached to
+an XDP hook point. The XDP driver invokes the program on every received packet
+at wire speed.
+
+```mermaid
+graph TD
+ subgraph User Mode
+ A["AF_XDP App
(XSK socket)"] -- UMEM rings --> X[xdp.sys]
+ B["bpf_object__open/load
(eBPF loader / libbpf)"] -- IOCTL --> E["eBPF runtime
(ebpfcore.sys)
verifier + JIT"]
+ end
+
+ subgraph Kernel Mode
+ E -- NMR attach --> X
+ X --> NIC
+ end
+```
+
+## Prerequisites
+
+1. **eBPF for Windows** must be installed first. Follow the
+ [eBPF for Windows installation guide](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/InstallEbpf.md).
+
+2. **XDP for Windows** with eBPF support enabled (see [Installation](#installation)).
+
+3. **Clang/LLVM 18.1.8+** for compiling eBPF C programs to BPF bytecode. See the
+ [eBPF for Windows development prerequisites](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/GettingStarted.md).
+
+## Installation
+
+### Runtime NuGet Package (v1.3+)
+
+After installing eBPF for Windows:
+
+```powershell
+xdp-setup.ps1 -Install xdp
+xdp-setup.ps1 -Install xdpebpf
+```
+
+### MSI (<= v1.2 only)
+
+Append `ADDLOCAL=xdp_ebpf` to the MSI install command:
+
+```powershell
+msiexec.exe /i xdp-for-windows.msi ADDLOCAL=xdp_ebpf /quiet
+```
+
+### Developer Setup
+
+Install the eBPF export tool so the eBPF verifier and compiler know about
+XDP's program type and helper functions:
+
+```powershell
+xdp-setup.ps1 -Install xdpebpfexport
+```
+
+Then run the export tool **before** verifying or compiling XDP eBPF programs:
+
+```powershell
+xdpbpfexport.exe
+```
+
+This populates registry keys (HKCU and/or HKLM) that eBPF for Windows reads to
+discover the XDP program type, attach type, context descriptor, and helper
+function prototypes.
+
+### Registry Configuration
+
+| Key (under `HKLM\...\Services\xdp`) | Type | Default | Description |
+|--------------------------------------|------|---------|-------------|
+| `XdpEbpfEnabled` | `DWORD` | `0` | Set to `1` to enable eBPF program attachment. |
+| `XdpEbpfMode` | `DWORD` | N/A | `0` = force generic mode; `1` = force native mode. If unset, XDP chooses automatically. |
+
+## XDP Program Type
+
+XDP registers a single eBPF program type with the following identifiers:
+
+| Property | Value |
+|----------|-------|
+| Program type GUID | `f1832a85-85d5-45b0-98a0-7069d63013b0` |
+| Attach type GUID | `85e0d8ef-579e-4931-b072-8ee226bb2e9d` |
+| `bpf_prog_type` | `BPF_PROG_TYPE_XDP` |
+| ELF section prefix | `xdp` |
+
+### Context: `xdp_md_t`
+
+Every XDP eBPF program receives a pointer to `xdp_md_t` describing the current
+packet:
+
+```c
+typedef struct xdp_md {
+ void *data; // Pointer to start of packet data (L2 frame).
+ void *data_end; // Pointer to end of packet data.
+ uint64_t data_meta; // Packet metadata (reserved).
+ uint32_t ingress_ifindex; // Ingress network interface index.
+ uint32_t rx_queue_index; // RX queue index on the ingress interface.
+} xdp_md_t;
+```
+
+### Return Values: `xdp_action_t`
+
+The program returns one of the following actions:
+
+```c
+typedef enum _xdp_action {
+ XDP_PASS = 1, // Allow the packet to continue up the stack.
+ XDP_DROP = 2, // Drop the packet silently.
+ XDP_TX = 3, // Bounce the packet back out the same NIC.
+ XDP_REDIRECT = 4, // Redirect to another target (set by bpf_redirect_map).
+} xdp_action_t;
+```
+
+### Program Signature
+
+```c
+typedef xdp_action_t xdp_hook_t(xdp_md_t *context);
+```
+
+## Helper Functions
+
+XDP exposes program-specific helper functions in addition to the
+[general eBPF helpers](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/GettingStarted.md)
+(e.g., `bpf_map_lookup_elem`, `bpf_map_update_elem`, `bpf_printk`).
+
+### `bpf_redirect_map`
+
+Redirects the current packet to an AF_XDP socket looked up from an XSKMAP.
+
+```c
+intptr_t bpf_redirect_map(void *map, uint32_t key, uint64_t flags);
+```
+
+| Parameter | Description |
+|-----------|-------------|
+| `map` | Pointer to a `BPF_MAP_TYPE_XSKMAP` map. |
+| `key` | Lookup key (typically `ctx->rx_queue_index`). |
+| `flags` | The lower 2 bits specify a fallback `xdp_action_t` to return if the lookup or redirect fails (e.g., `XDP_PASS` or `XDP_DROP`). |
+
+**Returns:** `XDP_REDIRECT` on success, or the fallback action on failure.
+
+See [eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md) for a comprehensive
+guide.
+
+### `bpf_xdp_adjust_head`
+
+Adjusts the packet data start pointer (e.g., to add or remove encapsulation
+headers).
+
+```c
+int bpf_xdp_adjust_head(xdp_md_t *ctx, int delta);
+```
+
+> **Note:** This helper is **not yet implemented**. It currently returns `-1`.
+
+## Writing an XDP eBPF Program
+
+### Minimal Example: Pass All Packets
+
+```c
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+SEC("xdp/pass")
+int pass(xdp_md_t *ctx) {
+ return XDP_PASS;
+}
+```
+
+### Packet Inspection: Allow Only IPv6
+
+```c
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+SEC("xdp/allow_ipv6")
+int allow_ipv6(xdp_md_t *ctx) {
+ ETHERNET_HEADER *eth;
+ IPV6_HEADER *ipv6;
+ const int hdr_size = sizeof(*eth) + sizeof(*ipv6);
+
+ if ((char *)ctx->data + hdr_size > (char *)ctx->data_end) {
+ return XDP_DROP;
+ }
+
+ eth = (ETHERNET_HEADER *)ctx->data;
+ if (eth->Type != htons(ETHERNET_TYPE_IPV6)) {
+ return XDP_DROP;
+ }
+
+ ipv6 = (IPV6_HEADER *)(eth + 1);
+ if (ipv6->Version != 6) {
+ return XDP_DROP;
+ }
+
+ return XDP_PASS;
+}
+```
+
+### Selective Drop with Map-Based Configuration
+
+```c
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint32_t);
+ __uint(max_entries, 1);
+} interface_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint64_t);
+ __uint(max_entries, 1);
+} dropped_packet_map SEC(".maps");
+
+SEC("xdp/selective_drop")
+int selective_drop(xdp_md_t *ctx) {
+ uint32_t zero = 0;
+
+ uint32_t *target_ifindex = bpf_map_lookup_elem(&interface_map, &zero);
+ if (!target_ifindex) {
+ return XDP_PASS;
+ }
+
+ if (*target_ifindex == ctx->ingress_ifindex) {
+ uint64_t *count = bpf_map_lookup_elem(&dropped_packet_map, &zero);
+ if (count) {
+ *count += 1;
+ }
+ return XDP_DROP;
+ }
+
+ return XDP_PASS;
+}
+```
+
+### Redirect to AF_XDP Socket
+
+See [eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md) for complete examples.
+
+```c
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/xsk_redirect")
+int xsk_redirect(xdp_md_t *ctx) {
+ return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, XDP_PASS);
+}
+```
+
+## Compiling eBPF Programs
+
+eBPF programs are compiled from C source to native Windows kernel drivers
+(`.sys` files) using a two-step process. The official eBPF for Windows runtime
+only supports loading **native drivers** -- JIT execution of BPF object files
+(`.o`) is not officially supported.
+
+### Step 1: Compile to BPF ELF Object
+
+```powershell
+clang -g -target bpf -O2 -c my_program.c -o my_program.o
+```
+
+### Step 2: Convert to Native Driver
+
+Use the `Convert-BpfToNative.ps1` script from the eBPF for Windows package to
+convert the BPF object file into a signed Windows kernel driver:
+
+```powershell
+Convert-BpfToNative.ps1 -FileName my_program -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+```
+
+This produces a `my_program.sys` file that can be loaded by the eBPF runtime.
+
+Ensure the include paths contain the XDP and eBPF headers:
+- `xdp/ebpfhook.h` (from the XDP development NuGet package)
+- `bpf_helpers.h`, `bpf_endian.h` (from eBPF for Windows)
+
+The ELF section name must start with `xdp` (e.g., `SEC("xdp/my_prog")`).
+
+## Loading and Attaching Programs
+
+Programs are loaded and attached using the eBPF for Windows user-mode APIs
+(`libbpf`-compatible or native Windows eBPF APIs). XDP does **not** provide its
+own program loading API for eBPF -- the eBPF for Windows runtime handles
+verification and attachment.
+
+> **Important:** The official eBPF for Windows runtime only supports loading
+> **native drivers** (`.sys` files). JIT execution of BPF object files (`.o`)
+> is not officially supported. See [Compiling eBPF Programs](#compiling-ebpf-programs)
+> for the compilation workflow.
+
+```c
+// Load a native eBPF driver (.sys)
+struct bpf_object *obj = bpf_object__open("my_program.sys");
+bpf_object__load(obj);
+int prog_fd = bpf_program__fd(
+ bpf_object__find_program_by_name(obj, "xsk_redirect"));
+bpf_xdp_attach(ifIndex, prog_fd, 0, NULL);
+```
+
+For AF_XDP redirection, user mode must also populate the XSKMAP with XSK socket
+handles after loading the program. See
+[eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md) for the complete workflow.
+
+## Observability
+
+### Performance Counters
+
+XDP exposes per-interface performance counters for eBPF program execution:
+
+| Counter | Description |
+|---------|-------------|
+| `EbpfXskMapLookupFailures` | `bpf_redirect_map` calls where the XSKMAP key lookup failed. |
+| `EbpfXskMapRedirectFailures` | `bpf_redirect_map` calls where the XSK was found but the socket was in an invalid state for redirect. |
+
+### ETW Tracing
+
+eBPF-specific ETW events are emitted for redirect map operations:
+
+| Event | Description |
+|-------|-------------|
+| `EbpfRedirectMapLookupFailure` | Logged when `bpf_redirect_map` fails to find a key in the XSKMAP. Includes the key and fallback action. |
+| `EbpfRedirectMapRedirectFailure` | Logged when the XSK handle was found but `XskCanRedirect` returned false. Includes the key, XSK handle, and fallback action. |
+| `EbpfRedirectMapSuccess` | Logged on successful redirect. Includes the key and XSK handle. |
+
+Use the existing XDP tracing infrastructure to capture these events at [Logging](./usage.md#logging).
+
+## Limitations
+
+- **`bpf_xdp_adjust_head` is not implemented** -- returns `-1`.
+- **XSKMAP is read-only from eBPF programs** -- user mode must populate the map;
+ `bpf_map_lookup_elem`, `bpf_map_update_elem`, and `bpf_map_delete_elem`
+ cannot be used on XSKMAPs from within a BPF program.
+- **No mixing of eBPF and built-in rules** -- a program is either eBPF or
+ rules-based; they cannot be combined on the same queue.
+- **Not source-compatible with Linux XDP** -- while the programming model is
+ similar, the APIs and headers differ.
+
+## Migrating from Built-in Rules
+
+The built-in rules engine (`XdpCreateProgram` with `XDP_RULE` / `XDP_MATCH_TYPE`
+/ `XDP_RULE_ACTION`) is deprecated and planned for removal. All users should
+migrate to eBPF programs.
+
+### Migration Quick Reference
+
+| Built-in Rule | eBPF Equivalent |
+|---------------|-----------------|
+| `XDP_PROGRAM_ACTION_DROP` | Return `XDP_DROP` |
+| `XDP_PROGRAM_ACTION_PASS` | Return `XDP_PASS` |
+| `XDP_PROGRAM_ACTION_REDIRECT` to XSK | Call `bpf_redirect_map()` with an XSKMAP |
+| `XDP_PROGRAM_ACTION_L2FWD` | Return `XDP_TX` (swap MACs in the program) |
+| `XDP_MATCH_ALL` | Unconditional return |
+| `XDP_MATCH_UDP_DST` | Parse UDP header, compare `dst_port` |
+| `XDP_MATCH_IPV4_DST_MASK` | Parse IPv4 header, apply bitmask to `dst_addr` |
+| Port set matching | Use a `BPF_MAP_TYPE_HASH` or `BPF_MAP_TYPE_ARRAY` map for port lookups |
+
+### Example: Migrating a UDP Port Filter
+
+**Before (built-in rules):**
+
+```c
+XDP_RULE rules[1] = {};
+rules[0].Match = XDP_MATCH_UDP_DST;
+rules[0].Pattern.Port = htons(4789);
+rules[0].Action = XDP_PROGRAM_ACTION_REDIRECT;
+rules[0].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSK;
+rules[0].Redirect.Target = xskHandle;
+
+XdpCreateProgram(ifIndex, &hookId, queueId, flags, rules, 1, &program);
+```
+
+**After (eBPF):**
+
+```c
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/udp_filter")
+int udp_filter(xdp_md_t *ctx) {
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+
+ // Parse Ethernet + IPv4 + UDP headers
+ ETHERNET_HEADER *eth = data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end) return XDP_PASS;
+ if (eth->Type != htons(ETHERNET_TYPE_IPV4)) return XDP_PASS;
+
+ IPV4_HEADER *ip = (IPV4_HEADER *)(eth + 1);
+ if ((char *)ip + sizeof(*ip) > (char *)data_end) return XDP_PASS;
+ if (ip->Protocol != 17) return XDP_PASS; // UDP
+
+ UDP_HEADER *udp = (UDP_HEADER *)((char *)ip + (ip->HeaderLength * 4));
+ if ((char *)udp + sizeof(*udp) > (char *)data_end) return XDP_PASS;
+
+ if (udp->DestinationPort == htons(4789)) {
+ return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, XDP_PASS);
+ }
+
+ return XDP_PASS;
+}
+```
+
+## References
+
+- [eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md)
+- [AF_XDP](afxdp.md)
+- [eBPF for Windows - Getting Started](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/GettingStarted.md)
+- [eBPF for Windows - Installation](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/InstallEbpf.md)
+- [Registry Keys](registry-keys.md)
+- [Architecture](architecture.md)
diff --git a/docs/faq.md b/docs/faq.md
index 66294c0b4..fa079dfd4 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -17,9 +17,10 @@ We are not source compatible with Linux, partly due to differences in the underl
> Do you support eBPF programs?
-We do not officially support eBPF programs yet. We are [integrating](https://github.com/microsoft/xdp-for-windows/issues/7) with the
-[eBPF for Windows project](https://github.com/microsoft/ebpf-for-windows), which is also in a pre-release stage. In the meantime, we
-have built a barebones [program](/published/external/xdp/program.h) module.
+Yes. XDP supports eBPF programs through [integration](https://github.com/microsoft/xdp-for-windows/issues/7) with the
+[eBPF for Windows project](https://github.com/microsoft/ebpf-for-windows). See the [eBPF Integration Guide](ebpf.md) for details.
+
+The built-in rules-based program API (`XDP_RULE` / `XDP_MATCH_TYPE` / `XdpCreateProgram`) is deprecated and planned for removal. All users should migrate to eBPF programs. See the [eBPF Integration Guide](ebpf.md) and [eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md) documentation for details.
> What versions of Windows does XDP support?
diff --git a/docs/usage.md b/docs/usage.md
index 259824b0a..a4465d2f3 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -52,9 +52,16 @@ msiexec.exe /x xdp-for-windows.msi /quiet
### Enable eBPF Support
-**Note** eBPF support is experimental and is not officially supported by XDP.
+For comprehensive documentation, see the [eBPF Integration Guide](ebpf.md) and
+[eBPF Redirect Map (XSKMAP)](ebpf-redirect-map.md).
-Starting with XDP version 1.1, experimental eBPF support can be enabled by running the following command after [installing eBPF-for-Windows](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/InstallEbpf.md) and the base `xdp` component:
+> **Note:** The built-in rules-based program API (`XDP_RULE` / `XDP_MATCH_TYPE`
+> / `XdpCreateProgram`) is deprecated and planned for removal. All users should
+> migrate to eBPF programs.
+
+Starting with XDP version 1.1, eBPF support can be enabled by appending an `ADDLOCAL=xdp_ebpf` parameter to the `msiexec.exe` install commands.
+
+When using the runtime nuget package instead of the runtime MSI, run the following command after [installing eBPF-for-Windows](https://github.com/microsoft/ebpf-for-windows/blob/main/docs/InstallEbpf.md) and the base `xdp` component:
```Powershell
xdp-setup.ps1 -Install xdpebpf
diff --git a/published/external/xdp/ebpfhook.h b/published/external/xdp/ebpfhook.h
index fff88e181..08bcbda95 100644
--- a/published/external/xdp/ebpfhook.h
+++ b/published/external/xdp/ebpfhook.h
@@ -8,24 +8,33 @@
#ifndef XDPEBPFHOOK_H
#define XDPEBPFHOOK_H
+#include
+
#ifdef __cplusplus
extern "C" {
#endif
+//
+// BPF_MAP_TYPE_XSKMAP for AF_XDP socket redirection.
+//
+
+// TODO! BPF_MAP_TYPE_XSKMAP must be defined in eBPF headers.
+
+#define BPF_MAP_TYPE_XSKMAP 16
+
typedef struct xdp_md {
void *data; ///< Pointer to start of packet data.
void *data_end; ///< Pointer to end of packet data.
uint64_t data_meta; ///< Packet metadata.
uint32_t ingress_ifindex; ///< Ingress interface index.
-
- /* size: 26, cachelines: 1, members: 4 */
- /* last cacheline: 26 bytes */
+ uint32_t rx_queue_index; ///< RX queue index.
} xdp_md_t;
typedef enum _xdp_action {
XDP_PASS = 1, ///< Allow the packet to pass.
XDP_DROP, ///< Drop the packet.
- XDP_TX ///< Bounce the received packet back out the same NIC it arrived on.
+ XDP_TX, ///< Bounce the received packet back out the same NIC it arrived on.
+ XDP_REDIRECT ///< Redirect the packet to another target (e.g., AF_XDP socket).
} xdp_action_t;
/**
@@ -37,6 +46,7 @@ typedef enum _xdp_action {
* @retval XDP_PASS Allow the packet to pass.
* @retval XDP_DROP Drop the packet.
* @retval XDP_TX Bounce the received packet back out the same NIC it arrived on.
+ * @retval XDP_REDIRECT Redirect the packet to another target.
*/
typedef
xdp_action_t
@@ -44,6 +54,24 @@ xdp_hook_t(
xdp_md_t *context
);
+//
+// Program-specific helper functions.
+//
+#define XDP_EXT_HELPER_FN_BASE (EBPF_MAX_GENERAL_HELPER_FUNCTION + 1)
+
+typedef enum _xdp_helper_id {
+ BPF_FUNC_xdp_adjust_head = XDP_EXT_HELPER_FN_BASE,
+ BPF_FUNC_redirect_map,
+} xdp_helper_id_t;
+
+#define XDP_EBPF_HELPER(return_type, name, args) typedef return_type(name##_t) args
+
+XDP_EBPF_HELPER(int, bpf_xdp_adjust_head, (xdp_md_t *ctx, int delta));
+#define bpf_xdp_adjust_head ((bpf_xdp_adjust_head_t *)BPF_FUNC_xdp_adjust_head)
+
+XDP_EBPF_HELPER(intptr_t, bpf_redirect_map, (void *map, uint32_t key, uint64_t flags));
+#define bpf_redirect_map ((bpf_redirect_map_t *)BPF_FUNC_redirect_map)
+
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/samples/deprecated/readme.md b/samples/deprecated/readme.md
new file mode 100644
index 000000000..0dd46a5bb
--- /dev/null
+++ b/samples/deprecated/readme.md
@@ -0,0 +1,20 @@
+# Deprecated Samples
+
+> **Warning:** The samples in this directory use the **deprecated built-in rules
+> engine** (`XDP_RULE`, `XDP_MATCH_TYPE`, `XdpCreateProgram`). This API is
+> planned for removal. All new development should use **eBPF programs** instead.
+
+## Affected Samples
+
+| Sample | Description |
+|--------|-------------|
+| [rxfilter](rxfilter/) | RX packet filter using built-in match/action rules. |
+| [xskfwd](xskfwd/) | AF_XDP forwarding using built-in `XDP_MATCH_UDP_DST` + `XDP_PROGRAM_ACTION_REDIRECT`. |
+| [xskrestricted](xskrestricted/) | Restricted-token AF_XDP forwarding using built-in rules with handle duplication. |
+
+## Migration
+
+Replacement samples using eBPF programs are available in the parent
+[samples/](../) directory. See the [eBPF Integration Guide](../../docs/ebpf.md)
+and [Migrating from Built-in Rules](../../docs/ebpf.md#migrating-from-built-in-rules)
+for migration guidance.
diff --git a/samples/deprecated/rxfilter/rxfilter-deprecated.vcxproj b/samples/deprecated/rxfilter/rxfilter-deprecated.vcxproj
new file mode 100644
index 000000000..d0f04dfcf
--- /dev/null
+++ b/samples/deprecated/rxfilter/rxfilter-deprecated.vcxproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ {c0c730a8-2dab-4c28-8c4b-5effa6b494dd}
+ rxfilter-deprecated
+ exe
+
+
+
+
+ ntdll.lib;onecore.lib;%(AdditionalDependencies)
+
+
+
+
diff --git a/samples/deprecated/rxfilter/rxfilter.c b/samples/deprecated/rxfilter/rxfilter.c
new file mode 100644
index 000000000..ba7173b9a
--- /dev/null
+++ b/samples/deprecated/rxfilter/rxfilter.c
@@ -0,0 +1,281 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+CONST CHAR *UsageText =
+"rxfilter.exe -IfIndex -QueueId [OPTIONS] RULE_PARAMS\n"
+"\n"
+"Filters RX traffic using an XDP program. Traffic that does not match the\n"
+"filter will be allowed to pass through.\n"
+"\n"
+"RULE_PARAMS:\n"
+"\n"
+" -MatchType \n"
+"\n"
+" The frame filter type. Each of the supported types and their parameters\n"
+" are listed under FILTER_TYPES.\n"
+"\n"
+" -Action \n"
+"\n"
+" The action to perform on matching frames:\n"
+" - Pass: Allow the frame to pass through XDP\n"
+" - Drop: Drop the frame\n"
+" - L2Fwd: Forward the frame back to the L2 sender\n"
+"\n"
+"FILTER_TYPES:\n"
+"\n"
+" All\n"
+"\n"
+" Matches all frames.\n"
+"\n"
+" UdpDstPort\n"
+"\n"
+" Matches all UDP frames with the specified destination port\n"
+"\n"
+" -UdpDstPort \n"
+" The UDP destination port\n"
+"\n"
+" IcmpEchoReplyIpv4 \n"
+"\n"
+" Matches all ICMPv4 echo reply frames with the specified IPv4 destination address\n"
+"\n"
+" -IcmpDstIpv4 \n"
+" The destination IPv4 address\n"
+"\n"
+" IcmpEchoReplyIpv6 \n"
+"\n"
+" Matches all ICMPv6 echo reply frames with the specified IPv6 destination address\n"
+"\n"
+" -IcmpDstIpv6 \n"
+" The destination IPv6 address\n"
+"\n"
+"OPTIONS:\n"
+"\n"
+" -XdpMode \n"
+"\n"
+" The XDP interface provider mode:\n"
+" - System: The system determines the ideal XDP provider\n"
+" - Generic: Use the generic XDP interface provider\n"
+" - Native: Use the native XDP interface provider\n"
+" Default: System\n"
+"\n"
+"Examples:\n"
+"\n"
+" rxfilter.exe -IfIndex 6 -QueueId 0 -MatchType All -Action Drop\n"
+" rxfilter.exe -IfIndex 6 -QueueId * -MatchType UdpDstPort -UdpDstPort 53 -Action Drop\n"
+" rxfilter.exe -IfIndex 6 -QueueId * -MatchType IcmpEchoReplyIpv4 -IcmpDstIpv4 '172.169.0.22' -Action Drop\n"
+;
+
+#define LOGERR(...) \
+ fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
+
+UINT32 IfIndex;
+UINT32 QueueId;
+XDP_RULE Rule;
+XDP_CREATE_PROGRAM_FLAGS ProgramFlags;
+
+VOID
+ParseArgs(
+ INT ArgC,
+ CHAR **ArgV
+ )
+{
+ INT i = 1;
+
+ IfIndex = MAXUINT32;
+ QueueId = MAXUINT32;
+ ProgramFlags = 0;
+
+ ZeroMemory(&Rule, sizeof(Rule));
+ Rule.Match = XDP_MATCH_ALL;
+ Rule.Action = XDP_PROGRAM_ACTION_PASS;
+
+ while (i < ArgC) {
+ if (!_stricmp(ArgV[i], "-IfIndex")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing IfIndex");
+ goto Usage;
+ }
+ IfIndex = atoi(ArgV[i]);
+ } else if (!_stricmp(ArgV[i], "-QueueId")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing QueueId");
+ goto Usage;
+ }
+ if (!_stricmp(ArgV[i], "*")) {
+ ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES;
+ QueueId = 0;
+ } else {
+ QueueId = atoi(ArgV[i]);
+ }
+ } else if (!_stricmp(ArgV[i], "-XdpMode")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing XdpMode");
+ goto Usage;
+ }
+ if (!_stricmp(ArgV[i], "Generic")) {
+ ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_GENERIC;
+ } else if (!_stricmp(ArgV[i], "Native")) {
+ ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_NATIVE;
+ } else if (_stricmp(ArgV[i], "System")) {
+ LOGERR("Invalid XdpMode");
+ goto Usage;
+ }
+ } else if (!_stricmp(ArgV[i], "-MatchType")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing MatchType");
+ goto Usage;
+ }
+ if (!_stricmp(ArgV[i], "All")) {
+ Rule.Match = XDP_MATCH_ALL;
+ } else if (!_stricmp(ArgV[i], "UdpDstPort")) {
+ Rule.Match = XDP_MATCH_UDP_DST;
+ } else if (!_stricmp(ArgV[i], "IcmpEchoReplyIpv4")) {
+ Rule.Match = XDP_MATCH_ICMPV4_ECHO_REPLY_IP_DST;
+ } else if (!_stricmp(ArgV[i], "IcmpEchoReplyIpv6")) {
+ Rule.Match = XDP_MATCH_ICMPV6_ECHO_REPLY_IP_DST;
+ } else {
+ LOGERR("Invalid MatchType");
+ goto Usage;
+ }
+ } else if (!_stricmp(ArgV[i], "-Action")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing Action");
+ goto Usage;
+ }
+ if (!_stricmp(ArgV[i], "Pass")) {
+ Rule.Action = XDP_PROGRAM_ACTION_PASS;
+ } else if (!_stricmp(ArgV[i], "Drop")) {
+ Rule.Action = XDP_PROGRAM_ACTION_DROP;
+ } else if (!_stricmp(ArgV[i], "L2Fwd")) {
+ Rule.Action = XDP_PROGRAM_ACTION_L2FWD;
+ } else {
+ LOGERR("Invalid Action");
+ goto Usage;
+ }
+ } else if (!_stricmp(ArgV[i], "-UdpDstPort")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing UdpDstPort");
+ goto Usage;
+ }
+ if (Rule.Match == XDP_MATCH_UDP_DST) {
+ Rule.Pattern.Port = _byteswap_ushort((UINT16)atoi(ArgV[i]));
+ } else {
+ LOGERR("Unexpected UdpDstPort");
+ }
+ } else if (!_stricmp(ArgV[i], "-IcmpDstIpv4")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing IcmpDstIpv4");
+ goto Usage;
+ }
+ if (Rule.Match == XDP_MATCH_ICMPV4_ECHO_REPLY_IP_DST) {
+ const char *IpStr = ArgV[i];
+
+ // Parse dotted-quad into IN_ADDR (network byte order)
+ int rc = inet_pton(AF_INET, IpStr, &Rule.Pattern.IpMask.Address.Ipv4);
+ if (rc != 1) {
+ if (rc == 0) {
+ LOGERR("Invalid IPv4 address format: %s\n", IpStr);
+ } else {
+ LOGERR("InetPtonA error: %d\n", WSAGetLastError());
+ }
+ goto Usage;
+ }
+ } else {
+ LOGERR("Unexpected IcmpDstIpv4");
+ goto Usage;
+ }
+ } else if (!_stricmp(ArgV[i], "-IcmpDstIpv6")) {
+ if (++i >= ArgC) {
+ LOGERR("Missing IcmpDstIpv6");
+ goto Usage;
+ }
+ if (Rule.Match == XDP_MATCH_ICMPV6_ECHO_REPLY_IP_DST) {
+ const char *IpStr = ArgV[i];
+
+ // Parse dotted-quad into IN_ADDR (network byte order)
+ int rc = inet_pton(AF_INET6, IpStr, &Rule.Pattern.IpMask.Address.Ipv6);
+ if (rc != 1) {
+ if (rc == 0) {
+ LOGERR("Invalid IPv6 address format: %s\n", IpStr);
+ } else {
+ LOGERR("InetPtonA error: %d\n", WSAGetLastError());
+ }
+ goto Usage;
+ }
+ } else {
+ LOGERR("Unexpected IcmpDstIpv6");
+ goto Usage;
+ }
+ } else {
+ LOGERR("Unexpected parameter \"%s\"", ArgV[i]);
+ goto Usage;
+ }
+
+ ++i;
+ }
+
+ if (IfIndex == MAXUINT32) {
+ LOGERR("IfIndex is required");
+ goto Usage;
+ }
+
+ if (QueueId == MAXUINT32) {
+ LOGERR("QueueId is required");
+ goto Usage;
+ }
+
+ return;
+
+Usage:
+
+ printf(UsageText);
+ exit(1);
+}
+
+INT
+__cdecl
+main(
+ INT argc,
+ CHAR **argv
+ )
+{
+ XDP_STATUS XdpStatus;
+ HANDLE Program;
+ const XDP_HOOK_ID XdpInspectRxL2 = {
+ XDP_HOOK_L2,
+ XDP_HOOK_RX,
+ XDP_HOOK_INSPECT,
+ };
+
+ //
+ // Parse the command line arguments.
+ //
+ ParseArgs(argc, argv);
+
+ //
+ // Create an XDP program using the parsed rule at the L2 inspect hook point.
+ //
+ XdpStatus =
+ XdpCreateProgram(
+ IfIndex, &XdpInspectRxL2, QueueId, ProgramFlags, &Rule, 1, &Program);
+ if (FAILED(XdpStatus)) {
+ LOGERR("XdpCreateProgram failed: %x", XdpStatus);
+ return 1;
+ }
+
+ //
+ // Let XDP filter frames until this process is terminated.
+ //
+ Sleep(INFINITE);
+
+ return 0;
+}
diff --git a/samples/deprecated/xskfwd/xskfwd-deprecated.vcxproj b/samples/deprecated/xskfwd/xskfwd-deprecated.vcxproj
new file mode 100644
index 000000000..8d37cbbef
--- /dev/null
+++ b/samples/deprecated/xskfwd/xskfwd-deprecated.vcxproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ {238cd65b-59d5-4ec5-ba23-630f567395fa}
+ xskfwd-deprecated
+ exe
+
+
+
+
+
+ $(SolutionDir)submodules\cxplat\inc;
+ %(AdditionalIncludeDirectories)
+
+
+
+ ntdll.lib;onecore.lib;%(AdditionalDependencies)
+
+
+
+
diff --git a/samples/deprecated/xskfwd/xskfwd.c b/samples/deprecated/xskfwd/xskfwd.c
new file mode 100644
index 000000000..535632333
--- /dev/null
+++ b/samples/deprecated/xskfwd/xskfwd.c
@@ -0,0 +1,485 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+#include
+#include
+#include
+
+#ifdef _KERNEL_MODE
+#define LOGERR(...)
+#else
+#include
+#include
+#define LOGERR(...) \
+ fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
+#endif
+
+const CHAR *UsageText =
+"xskfwd.exe "
+"\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,
+};
+
+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));
+ }
+}
+
+XDP_STATUS
+XskFwd(
+ _In_ UINT32 IfIndex,
+ _In_ volatile BOOLEAN *Stop
+ )
+{
+ HRESULT Result;
+ HANDLE Socket = NULL;
+ HANDLE Program = NULL;
+ 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;
+
+ //
+ // Create an AF_XDP socket. The newly created socket is not connected.
+ //
+ Result = XskCreate(&Socket);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XskCreate failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // 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 = XskSetSockopt(Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg));
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_UMEM_REG failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // 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 = XskBind(Socket, IfIndex, 0, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XskBind failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // 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 = XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed: %x", Result);
+ goto Exit;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed: %x", Result);
+ goto Exit;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_SOCKOPT_TX_RING_SIZE failed: %x", Result);
+ goto Exit;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_COMPLETION_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_SOCKOPT_TX_COMPLETION_RING_SIZE failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // Activate the AF_XDP socket. Once activated, descriptor rings are
+ // available and RX and TX can occur.
+ //
+ Result = XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XskActivate failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // Retrieve the RX, RX fill, TX, and TX completion ring info from AF_XDP.
+ //
+ OptionLength = sizeof(RingInfo);
+ Result = XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XSK_SOCKOPT_RING_INFO failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // 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.
+ //
+ *(UINT64 *)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 = XdpCreateProgram(IfIndex, &XdpInspectRxL2, 0, 0, &Rule, 1, &Program);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XdpCreateProgram failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // 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.
+ //
+ do {
+ 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 = XskNotifySocket(Socket, XSK_NOTIFY_FLAG_POKE_TX, 0, &NotifyResult);
+ if (XDP_FAILED(Result)) {
+ LOGERR("XskNotifySocket failed: %x", Result);
+ goto Exit;
+ }
+ }
+
+ if (XskRingConsumerReserve(&TxCompRing, 1, &RingIndex) == 1) {
+ UINT64 *Tx;
+ UINT64 *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);
+ }
+ } while (!ReadBooleanNoFence(Stop));
+
+ Result = XDP_STATUS_SUCCESS;
+
+Exit:
+
+ //
+ // Close the XDP program. Traffic will no longer be intercepted by XDP.
+ //
+ if (Program != NULL) {
+ CxPlatCloseHandle(Program);
+ }
+
+ //
+ // Close the AF_XDP socket. All socket resources will be cleaned up by XDP.
+ //
+ if (Socket != NULL) {
+ CxPlatCloseHandle(Socket);
+ }
+
+ return Result;
+}
+
+#ifdef _KERNEL_MODE
+
+DRIVER_INITIALIZE DriverEntry;
+static DRIVER_UNLOAD DriverUnload;
+static KSTART_ROUTINE XskFwdWorker;
+
+static UINT32 IfIndex;
+static BOOLEAN Stop;
+static CXPLAT_THREAD WorkerThread;
+
+static
+_Use_decl_annotations_
+VOID
+XskFwdWorker(
+ VOID *Context
+ )
+{
+ UNREFERENCED_PARAMETER(Context);
+
+ XskFwd(IfIndex, &Stop);
+}
+
+static
+_Use_decl_annotations_
+VOID
+DriverUnload(
+ DRIVER_OBJECT *DriverObject
+ )
+{
+ UNREFERENCED_PARAMETER(DriverObject);
+
+ if (WorkerThread != NULL) {
+ WriteBooleanNoFence(&Stop, TRUE);
+ CxPlatThreadWaitForever(&WorkerThread);
+ CxPlatThreadDelete(&WorkerThread);
+ }
+}
+
+_Use_decl_annotations_
+NTSTATUS
+DriverEntry(
+ DRIVER_OBJECT *DriverObject,
+ UNICODE_STRING *RegistryPath
+ )
+{
+ NTSTATUS Status;
+ CXPLAT_THREAD_CONFIG ThreadConfig = {0};
+ HANDLE KeyHandle;
+ UNICODE_STRING UnicodeName;
+ OBJECT_ATTRIBUTES ObjectAttributes = {0};
+ UCHAR InformationBuffer[512] = {0};
+ KEY_VALUE_FULL_INFORMATION *Information = (KEY_VALUE_FULL_INFORMATION *) InformationBuffer;
+ ULONG ResultLength;
+ BOOLEAN RunInline = FALSE;
+
+#pragma prefast(suppress : __WARNING_BANNED_MEM_ALLOCATION_UNSAFE, "Non executable pool is enabled via -DPOOL_NX_OPTIN_AUTO=1.")
+ ExInitializeDriverRuntime(0);
+ DriverObject->DriverUnload = DriverUnload;
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ RegistryPath,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL);
+ Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
+ if (!NT_SUCCESS(Status)) {
+ goto Exit;
+ }
+
+ //
+ // Read the configured IfIndex from HKLM\SYSTEM\CurrentControlSet\Services\xskfwd\IfIndex
+ //
+ RtlInitUnicodeString(&UnicodeName, L"IfIndex");
+ Status =
+ ZwQueryValueKey(
+ KeyHandle,
+ &UnicodeName,
+ KeyValueFullInformation,
+ Information,
+ sizeof(InformationBuffer),
+ &ResultLength);
+ if (NT_SUCCESS(Status)) {
+ if (Information->Type != REG_DWORD) {
+ Status = STATUS_INVALID_PARAMETER_MIX;
+ } else {
+ IfIndex = *((DWORD UNALIGNED *)((CHAR *)Information + Information->DataOffset));
+ }
+ }
+
+ //
+ // Read the configured IfIndex from HKLM\SYSTEM\CurrentControlSet\Services\xskfwd\RunInline
+ //
+ RtlInitUnicodeString(&UnicodeName, L"RunInline");
+ Status =
+ ZwQueryValueKey(
+ KeyHandle,
+ &UnicodeName,
+ KeyValueFullInformation,
+ Information,
+ sizeof(InformationBuffer),
+ &ResultLength);
+ if (NT_SUCCESS(Status)) {
+ if (Information->Type != REG_DWORD) {
+ Status = STATUS_INVALID_PARAMETER_MIX;
+ } else {
+ RunInline = !!*((DWORD UNALIGNED *)((CHAR *)Information + Information->DataOffset));
+ }
+ }
+
+ ZwClose(KeyHandle);
+
+ if (RunInline) {
+ BOOLEAN AlwaysStop = TRUE;
+
+ //
+ // Run a single iteration and return the result.
+ //
+ Status = XskFwd(IfIndex, &AlwaysStop);
+ if (!NT_SUCCESS(Status)) {
+ goto Exit;
+ }
+ } else {
+ //
+ // Start a system worker thread to run until the driver is unloaded.
+ //
+ ThreadConfig.Callback = XskFwdWorker;
+ Status = CxPlatThreadCreate(&ThreadConfig, &WorkerThread);
+ if (!NT_SUCCESS(Status)) {
+ goto Exit;
+ }
+ }
+
+
+Exit:
+
+ if (!NT_SUCCESS(Status)) {
+ DriverUnload(DriverObject);
+ }
+
+ return Status;
+}
+
+#else // _KERNEL_MODE
+
+INT
+__cdecl
+main(
+ INT argc,
+ CHAR **argv
+ )
+{
+ UINT32 IfIndex;
+ BOOLEAN Stop = FALSE; // Run until the process is terminated.
+
+ if (argc < 2) {
+ LOGERR(UsageText);
+ return EXIT_FAILURE;
+ }
+
+ IfIndex = atoi(argv[1]);
+
+ return XskFwd(IfIndex, &Stop);
+}
+
+#endif // _KERNEL_MODE
diff --git a/samples/xskfwd/xskfwdkm.vcxproj b/samples/deprecated/xskfwd/xskfwdkm-deprecated.vcxproj
similarity index 93%
rename from samples/xskfwd/xskfwdkm.vcxproj
rename to samples/deprecated/xskfwd/xskfwdkm-deprecated.vcxproj
index 90689729d..7907fb53a 100644
--- a/samples/xskfwd/xskfwdkm.vcxproj
+++ b/samples/deprecated/xskfwd/xskfwdkm-deprecated.vcxproj
@@ -5,7 +5,7 @@
{f722da97-3252-42f1-92d3-215c99935607}
- xskfwdkm
+ xskfwdkm-deprecated
sys
diff --git a/samples/xskfwd/xskfwdkm.h b/samples/deprecated/xskfwd/xskfwdkm.h
similarity index 100%
rename from samples/xskfwd/xskfwdkm.h
rename to samples/deprecated/xskfwd/xskfwdkm.h
diff --git a/samples/deprecated/xskrestricted/xskrestricted-deprecated.vcxproj b/samples/deprecated/xskrestricted/xskrestricted-deprecated.vcxproj
new file mode 100644
index 000000000..03e1fd24b
--- /dev/null
+++ b/samples/deprecated/xskrestricted/xskrestricted-deprecated.vcxproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
+ xskrestricted-deprecated
+ exe
+
+
+
+
+ ntdll.lib;onecore.lib;%(AdditionalDependencies)
+
+
+
+
diff --git a/samples/deprecated/xskrestricted/xskrestricted.c b/samples/deprecated/xskrestricted/xskrestricted.c
new file mode 100644
index 000000000..3d9740397
--- /dev/null
+++ b/samples/deprecated/xskrestricted/xskrestricted.c
@@ -0,0 +1,638 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// This sample demonstrates how to create an AF_XDP socket and XDP program in
+// a privileged parent process, then duplicate those handles into a child
+// process running with a restricted token. The child process uses the
+// duplicated handles to perform RX-to-TX forwarding without needing elevated
+// privileges itself.
+//
+// Approach:
+// 1. Parent creates AF_XDP socket and XDP program (requires admin).
+// 2. Parent creates a restricted token (Administrators SID disabled).
+// 3. Parent spawns a child process (suspended) with the restricted token.
+// 4. Parent duplicates the socket and program handles into the child.
+// 5. Parent writes handle values to a named pipe the child reads on startup.
+// 6. Parent resumes the child, which configures rings and forwards packets.
+//
+// Usage:
+// xskrestricted.exe [-TimeoutSeconds ]
+//
+
+#include
+#include
+#include
+#include
+#include
+
+#define LOGERR(...) \
+ fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
+
+#define LOGINFO(...) \
+ fprintf(stdout, "INFO: "); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n")
+
+static const XDP_HOOK_ID XdpInspectRxL2 = {
+ .Layer = XDP_HOOK_L2,
+ .Direction = XDP_HOOK_RX,
+ .SubLayer = XDP_HOOK_INSPECT,
+};
+
+//
+// A simple structure passed from the parent to the child via a named pipe.
+// Contains the duplicated handle values in the child's handle table.
+//
+typedef struct _XSKRESTRICTED_CHILD_PARAMS {
+ UINT64 SocketHandle;
+ UINT64 ProgramHandle;
+ UINT32 IfIndex;
+ UINT32 TimeoutSeconds;
+} XSKRESTRICTED_CHILD_PARAMS;
+
+//
+// Child process: read duplicated handle values from the named pipe, configure
+// the socket, and run the RX-to-TX forwarding loop.
+//
+static
+INT
+RunChild(
+ _In_ const WCHAR *PipeName
+ )
+{
+ HRESULT Result;
+ HANDLE PipeHandle = INVALID_HANDLE_VALUE;
+ XSKRESTRICTED_CHILD_PARAMS Params;
+ DWORD BytesRead;
+ HANDLE Socket;
+ HANDLE Program;
+ 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;
+ HANDLE CreatedXsk;
+ HANDLE CreatedProgram;
+ XDP_RULE Rule = {0};
+
+ //
+ // Connect to the named pipe created by the parent to receive the
+ // duplicated handle values.
+ //
+ PipeHandle =
+ CreateFileW(
+ PipeName,
+ GENERIC_READ,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (PipeHandle == INVALID_HANDLE_VALUE) {
+ LOGERR("[Child] Failed to open pipe: %u", GetLastError());
+ return 1;
+ }
+
+ if (!ReadFile(PipeHandle, &Params, sizeof(Params), &BytesRead, NULL) ||
+ BytesRead != sizeof(Params)) {
+ LOGERR("[Child] Failed to read params from pipe: %u", GetLastError());
+ CloseHandle(PipeHandle);
+ return 1;
+ }
+ CloseHandle(PipeHandle);
+
+ Socket = (HANDLE)(ULONG_PTR)Params.SocketHandle;
+ Program = (HANDLE)(ULONG_PTR)Params.ProgramHandle;
+
+ LOGINFO(
+ "[Child] Running with duplicated handles: Socket=%p Program=%p IfIndex=%u",
+ Socket, Program, Params.IfIndex);
+
+ //
+ // For sanity, verify this restricted process can't create sockets or
+ // programs.
+ //
+
+ if (XskCreate(&CreatedXsk) != HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) {
+ LOGERR("[Child] XskCreate was not denied access");
+ return 1;
+ }
+
+ Rule.Match = XDP_MATCH_ALL;
+ Rule.Action = XDP_PROGRAM_ACTION_PASS;
+
+ if (XdpCreateProgram(
+ Params.IfIndex, &XdpInspectRxL2, 0, XDP_CREATE_PROGRAM_FLAG_NONE, &Rule, 1,
+ &CreatedProgram) != HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) {
+ LOGERR("[Child] XdpCreateProgram was not denied access");
+ return 1;
+ }
+
+ //
+ // Register our frame buffer with the AF_XDP socket. The parent created
+ // and bound the socket, but UMEM registration, ring setup, and activation
+ // are done here in the child's address space.
+ //
+ UmemReg.TotalSize = sizeof(Frame);
+ UmemReg.ChunkSize = sizeof(Frame);
+ UmemReg.Address = Frame;
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg));
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_UMEM_REG failed: %x", Result);
+ return 1;
+ }
+
+ //
+ // Set ring sizes.
+ //
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_SOCKOPT_RX_RING_SIZE failed: %x", Result);
+ return 1;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_SOCKOPT_RX_FILL_RING_SIZE failed: %x", Result);
+ return 1;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_SOCKOPT_TX_RING_SIZE failed: %x", Result);
+ return 1;
+ }
+
+ Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_COMPLETION_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_SOCKOPT_TX_COMPLETION_RING_SIZE failed: %x", Result);
+ return 1;
+ }
+
+ //
+ // Activate the socket so rings become available.
+ //
+ Result = XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XskActivate failed: %x", Result);
+ return 1;
+ }
+
+ //
+ // Retrieve ring info and initialize ring helpers.
+ //
+ OptionLength = sizeof(RingInfo);
+ Result = XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XSK_SOCKOPT_RING_INFO failed: %x", Result);
+ return 1;
+ }
+
+ 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.
+ //
+ XskRingProducerReserve(&RxFillRing, 1, &RingIndex);
+ *(UINT64 *)XskRingGetElement(&RxFillRing, RingIndex) = 0;
+ XskRingProducerSubmit(&RxFillRing, 1);
+
+ LOGINFO("[Child] Forwarding packets (Ctrl+C to stop)...");
+
+ //
+ // RX-to-TX forwarding loop: receive frames, swap MAC addresses, and
+ // transmit them back. If a timeout was specified, exit after the deadline.
+ //
+ {
+ ULONGLONG Deadline = 0;
+ if (Params.TimeoutSeconds > 0) {
+ Deadline = GetTickCount64() + ((ULONGLONG)Params.TimeoutSeconds * 1000);
+ LOGINFO("[Child] Will exit after %u seconds", Params.TimeoutSeconds);
+ }
+
+ for (;;) {
+ if (Deadline != 0 && GetTickCount64() >= Deadline) {
+ LOGINFO("[Child] Timeout reached, exiting");
+ break;
+ }
+ if (XskRingConsumerReserve(&RxRing, 1, &RingIndex) == 1) {
+ XSK_BUFFER_DESCRIPTOR *RxBuffer;
+ XSK_BUFFER_DESCRIPTOR *TxBuffer;
+ XSK_NOTIFY_RESULT_FLAGS NotifyResult;
+
+ RxBuffer = XskRingGetElement(&RxRing, RingIndex);
+
+ XskRingProducerReserve(&TxRing, 1, &RingIndex);
+ TxBuffer = XskRingGetElement(&TxRing, RingIndex);
+
+ //
+ // Swap source and destination MAC addresses to echo the frame
+ // back to the sender.
+ //
+ {
+ UCHAR *FrameData =
+ &Frame[RxBuffer->Address.BaseAddress + RxBuffer->Address.Offset];
+ UINT32 Length = RxBuffer->Length;
+ UCHAR MacAddress[6];
+
+ if (Length >= sizeof(MacAddress) * 2) {
+ RtlCopyMemory(MacAddress, FrameData, sizeof(MacAddress));
+ RtlCopyMemory(FrameData, FrameData + sizeof(MacAddress), sizeof(MacAddress));
+ RtlCopyMemory(FrameData + sizeof(MacAddress), MacAddress, sizeof(MacAddress));
+ }
+ }
+
+ *TxBuffer = *RxBuffer;
+
+ XskRingConsumerRelease(&RxRing, 1);
+ XskRingProducerSubmit(&TxRing, 1);
+
+ Result = XskNotifySocket(Socket, XSK_NOTIFY_FLAG_POKE_TX, 0, &NotifyResult);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Child] XskNotifySocket failed: %x", Result);
+ return 1;
+ }
+ }
+
+ if (XskRingConsumerReserve(&TxCompRing, 1, &RingIndex) == 1) {
+ UINT64 *Tx = XskRingGetElement(&TxCompRing, RingIndex);
+ UINT64 *Rx;
+
+ XskRingProducerReserve(&RxFillRing, 1, &RingIndex);
+ Rx = XskRingGetElement(&RxFillRing, RingIndex);
+ *Rx = *Tx;
+
+ XskRingConsumerRelease(&TxCompRing, 1);
+ XskRingProducerSubmit(&RxFillRing, 1);
+ }
+ }
+ }
+
+ LOGINFO("[Child] Exiting successfully");
+ CloseHandle(Socket);
+ CloseHandle(Program);
+
+ return 0;
+}
+
+//
+// Parent process: create the AF_XDP socket and XDP program, create a
+// restricted child process, duplicate the handles into it, and communicate
+// the handle values via a named pipe.
+//
+static
+INT
+RunParent(
+ _In_ UINT32 IfIndex,
+ _In_ UINT32 TimeoutSeconds
+ )
+{
+ HRESULT Result;
+ HANDLE Socket = NULL;
+ HANDLE Program = NULL;
+ XDP_RULE Rule = {0};
+ HANDLE RestrictedToken = NULL;
+ HANDLE CurrentToken = NULL;
+ SID_AND_ATTRIBUTES SidsToDisable[1];
+ DWORD SidSize;
+ PSID AdminSid = NULL;
+ STARTUPINFOW StartupInfo;
+ PROCESS_INFORMATION ProcessInfo;
+ WCHAR CommandLine[512];
+ HANDLE ChildSocket = NULL;
+ HANDLE ChildProgram = NULL;
+ INT ExitCode = 1;
+ WCHAR ModulePathW[MAX_PATH];
+ HANDLE PipeHandle = INVALID_HANDLE_VALUE;
+ WCHAR PipeName[128];
+ XSKRESTRICTED_CHILD_PARAMS ChildParams;
+ DWORD BytesWritten;
+ SECURITY_ATTRIBUTES PipeSa;
+ BOOL ProcessCreated = FALSE;
+
+ ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
+
+ LOGINFO("[Parent] Creating AF_XDP socket and XDP program for IfIndex=%u", IfIndex);
+
+ //
+ // Create a named pipe with a unique name based on the current process ID.
+ // The child will read its handle values from this pipe on startup.
+ //
+ _snwprintf_s(
+ PipeName, ARRAYSIZE(PipeName), _TRUNCATE,
+ L"\\\\.\\pipe\\xskrestricted_%u", GetCurrentProcessId());
+
+ ZeroMemory(&PipeSa, sizeof(PipeSa));
+ PipeSa.nLength = sizeof(PipeSa);
+ PipeSa.bInheritHandle = FALSE;
+
+ PipeHandle =
+ CreateNamedPipeW(
+ PipeName,
+ PIPE_ACCESS_OUTBOUND,
+ PIPE_TYPE_BYTE | PIPE_WAIT,
+ 1, // Max instances
+ sizeof(XSKRESTRICTED_CHILD_PARAMS),
+ 0, // In buffer size
+ 0, // Default timeout
+ &PipeSa);
+ if (PipeHandle == INVALID_HANDLE_VALUE) {
+ LOGERR("[Parent] CreateNamedPipeW failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ //
+ // Create an AF_XDP socket.
+ //
+ Result = XskCreate(&Socket);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Parent] XskCreate failed: %x", Result);
+ goto Exit;
+ }
+
+ //
+ // Bind the socket to the specified interface and 0th queue.
+ //
+ Result = XskBind(Socket, IfIndex, 0, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Parent] XskBind failed: %x", Result);
+ goto Exit;
+ }
+
+ LOGINFO("[Parent] Socket created and bound (handle=%p)", Socket);
+
+ //
+ // Create an XDP program that redirects UDP port 1234 traffic to the socket.
+ //
+ Rule.Match = XDP_MATCH_UDP_DST;
+ Rule.Pattern.Port = _byteswap_ushort(1234);
+ Rule.Action = XDP_PROGRAM_ACTION_REDIRECT;
+ Rule.Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSK;
+ Rule.Redirect.Target = Socket;
+
+ Result = XdpCreateProgram(IfIndex, &XdpInspectRxL2, 0, 0, &Rule, 1, &Program);
+ if (XDP_FAILED(Result)) {
+ LOGERR("[Parent] XdpCreateProgram failed: %x", Result);
+ goto Exit;
+ }
+
+ LOGINFO("[Parent] XDP program created (handle=%p)", Program);
+
+ //
+ // Create a restricted token by disabling the Administrators group SID.
+ // This demonstrates privilege reduction: the child process cannot create
+ // new XDP objects but can use pre-existing duplicated handles.
+ //
+ SidSize = SECURITY_MAX_SID_SIZE;
+ AdminSid = LocalAlloc(LMEM_FIXED, SidSize);
+ if (AdminSid == NULL) {
+ LOGERR("[Parent] LocalAlloc for SID failed");
+ goto Exit;
+ }
+
+ if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, AdminSid, &SidSize)) {
+ LOGERR("[Parent] CreateWellKnownSid failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &CurrentToken)) {
+ LOGERR("[Parent] OpenProcessToken failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ SidsToDisable[0].Sid = AdminSid;
+ SidsToDisable[0].Attributes = 0;
+
+ if (!CreateRestrictedToken(
+ CurrentToken,
+ 0, // Flags
+ 1, // DisableSidCount
+ SidsToDisable, // SidsToDisable
+ 0, // DeletePrivilegeCount
+ NULL, // PrivilegesToDelete
+ 0, // RestrictedSidCount
+ NULL, // SidsToRestrict
+ &RestrictedToken)) {
+ LOGERR("[Parent] CreateRestrictedToken failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ LOGINFO("[Parent] Restricted token created (Administrators SID disabled)");
+
+ //
+ // Create the child process in a suspended state. We pass the pipe name
+ // on the command line so the child can read its handle values.
+ //
+ ZeroMemory(&StartupInfo, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+
+ if (GetModuleFileNameW(NULL, ModulePathW, ARRAYSIZE(ModulePathW)) == 0) {
+ LOGERR("[Parent] GetModuleFileNameW failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ _snwprintf_s(
+ CommandLine, ARRAYSIZE(CommandLine), _TRUNCATE,
+ L"\"%s\" -child \"%s\"",
+ ModulePathW, PipeName);
+
+ if (!CreateProcessAsUserW(
+ RestrictedToken,
+ NULL, // Application name (use command line)
+ CommandLine, // Command line
+ NULL, // Process attributes
+ NULL, // Thread attributes
+ FALSE, // Inherit handles
+ CREATE_SUSPENDED, // Creation flags
+ NULL, // Environment
+ NULL, // Current directory
+ &StartupInfo,
+ &ProcessInfo)) {
+ LOGERR("[Parent] CreateProcessAsUserW failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ ProcessCreated = TRUE;
+
+ LOGINFO("[Parent] Child process created (PID=%u), suspended", ProcessInfo.dwProcessId);
+
+ //
+ // Duplicate the AF_XDP socket handle into the child process.
+ //
+ if (!DuplicateHandle(
+ GetCurrentProcess(), // Source process
+ Socket, // Source handle
+ ProcessInfo.hProcess, // Target process
+ &ChildSocket, // Target handle (value in child's table)
+ 0, // Desired access (ignored with DUPLICATE_SAME_ACCESS)
+ FALSE, // Inherit handle
+ DUPLICATE_SAME_ACCESS)) {
+ LOGERR("[Parent] DuplicateHandle(Socket) failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ //
+ // Duplicate the XDP program handle into the child process.
+ //
+ if (!DuplicateHandle(
+ GetCurrentProcess(),
+ Program,
+ ProcessInfo.hProcess,
+ &ChildProgram,
+ 0,
+ FALSE,
+ DUPLICATE_SAME_ACCESS)) {
+ LOGERR("[Parent] DuplicateHandle(Program) failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ LOGINFO(
+ "[Parent] Handles duplicated into child: Socket=%p Program=%p",
+ ChildSocket, ChildProgram);
+
+ //
+ // Resume the child process. It will connect to the named pipe and read
+ // its parameters.
+ //
+ ResumeThread(ProcessInfo.hThread);
+
+ //
+ // Wait for the child to connect, then send the handle values.
+ //
+ if (!ConnectNamedPipe(PipeHandle, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
+ LOGERR("[Parent] ConnectNamedPipe failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ ChildParams.SocketHandle = (UINT64)(ULONG_PTR)ChildSocket;
+ ChildParams.ProgramHandle = (UINT64)(ULONG_PTR)ChildProgram;
+ ChildParams.IfIndex = IfIndex;
+ ChildParams.TimeoutSeconds = TimeoutSeconds;
+
+ if (!WriteFile(PipeHandle, &ChildParams, sizeof(ChildParams), &BytesWritten, NULL) ||
+ BytesWritten != sizeof(ChildParams)) {
+ LOGERR("[Parent] WriteFile to pipe failed: %u", GetLastError());
+ goto Exit;
+ }
+
+ //
+ // Close the pipe; the child has its data.
+ //
+ CloseHandle(PipeHandle);
+ PipeHandle = INVALID_HANDLE_VALUE;
+
+ LOGINFO("[Parent] Parameters sent to child, waiting for child to exit...");
+
+ //
+ // Wait for the child process to exit.
+ //
+ WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
+
+ {
+ DWORD ChildExitCode = 1;
+ GetExitCodeProcess(ProcessInfo.hProcess, &ChildExitCode);
+ LOGINFO("[Parent] Child exited with code %u", ChildExitCode);
+ ExitCode = (INT)ChildExitCode;
+ }
+
+Exit:
+
+ if (ProcessCreated) {
+ CloseHandle(ProcessInfo.hProcess);
+ CloseHandle(ProcessInfo.hThread);
+ }
+ if (PipeHandle != INVALID_HANDLE_VALUE) {
+ _Analysis_assume_(PipeHandle != NULL);
+ CloseHandle(PipeHandle);
+ }
+ if (Program != NULL) {
+ CloseHandle(Program);
+ }
+ if (Socket != NULL) {
+ CloseHandle(Socket);
+ }
+ if (RestrictedToken != NULL) {
+ CloseHandle(RestrictedToken);
+ }
+ if (CurrentToken != NULL) {
+ CloseHandle(CurrentToken);
+ }
+ if (AdminSid != NULL) {
+ LocalFree(AdminSid);
+ }
+
+ return ExitCode;
+}
+
+INT
+__cdecl
+main(
+ INT argc,
+ CHAR **argv
+ )
+{
+ if (argc >= 3 && _stricmp(argv[1], "-child") == 0) {
+ //
+ // Child mode: receive the pipe name, read handle values from the pipe.
+ //
+ WCHAR PipeName[128];
+
+ if (MultiByteToWideChar(
+ CP_ACP, 0, argv[2], -1, PipeName, ARRAYSIZE(PipeName)) == 0) {
+ LOGERR("Invalid pipe name");
+ return 1;
+ }
+
+ return RunChild(PipeName);
+ }
+
+ if (argc >= 2 && argv[1][0] != '-') {
+ //
+ // Parent mode: create XDP objects and spawn restricted child.
+ //
+ UINT32 IfIndex = (UINT32)atoi(argv[1]);
+ UINT32 TimeoutSeconds = 0;
+
+ if (IfIndex == 0) {
+ LOGERR("Invalid IfIndex");
+ return 1;
+ }
+
+ //
+ // Check for optional -TimeoutSeconds argument.
+ //
+ for (INT i = 2; i < argc - 1; i++) {
+ if (_stricmp(argv[i], "-TimeoutSeconds") == 0) {
+ TimeoutSeconds = (UINT32)atoi(argv[i + 1]);
+ break;
+ }
+ }
+
+ return RunParent(IfIndex, TimeoutSeconds);
+ }
+
+ fprintf(
+ stderr,
+ "Usage:\n"
+ " xskrestricted.exe [-TimeoutSeconds ]\n"
+ "\n"
+ "Creates an AF_XDP socket and XDP program, then spawns a child process\n"
+ "with a restricted token and duplicates the handles into it. The child\n"
+ "forwards UDP port 1234 traffic back to the sender.\n"
+ "\n"
+ "Options:\n"
+ " -TimeoutSeconds Exit after the specified number of seconds.\n"
+ " If 0 or omitted, run indefinitely.\n");
+
+ return 1;
+}
diff --git a/samples/rxfilter/bpf/rxfilter_drop_all.c b/samples/rxfilter/bpf/rxfilter_drop_all.c
new file mode 100644
index 000000000..a5bf7139c
--- /dev/null
+++ b/samples/rxfilter/bpf/rxfilter_drop_all.c
@@ -0,0 +1,23 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// eBPF XDP program: drop all packets.
+//
+// Compile:
+// clang -g -target bpf -O2 -c rxfilter_drop_all.c -o rxfilter_drop_all.o
+// Convert-BpfToNative.ps1 -FileName rxfilter_drop_all -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+SEC("xdp/drop_all")
+int
+drop_all(xdp_md_t *ctx)
+{
+ return XDP_DROP;
+}
diff --git a/samples/rxfilter/bpf/rxfilter_l2fwd.c b/samples/rxfilter/bpf/rxfilter_l2fwd.c
new file mode 100644
index 000000000..c094fa52e
--- /dev/null
+++ b/samples/rxfilter/bpf/rxfilter_l2fwd.c
@@ -0,0 +1,43 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// eBPF XDP program: L2 forward (bounce) all packets back to the sender.
+//
+// This swaps the source and destination MAC addresses, then returns XDP_TX
+// so the NIC transmits the frame back out.
+//
+// Compile:
+// clang -g -target bpf -O2 -I -c rxfilter_l2fwd.c -o rxfilter_l2fwd.o
+// Convert-BpfToNative.ps1 -FileName rxfilter_l2fwd -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "xdp/ebpfhook.h"
+
+SEC("xdp/l2fwd")
+int
+l2fwd(xdp_md_t *ctx)
+{
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+
+ ETHERNET_HEADER *eth = (ETHERNET_HEADER *)data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ //
+ // Swap source and destination MAC addresses.
+ //
+ uint8_t tmp[6];
+ __builtin_memcpy(tmp, eth->Destination, 6);
+ __builtin_memcpy(eth->Destination, eth->Source, 6);
+ __builtin_memcpy(eth->Source, tmp, 6);
+
+ return XDP_TX;
+}
diff --git a/samples/rxfilter/bpf/rxfilter_udp.c b/samples/rxfilter/bpf/rxfilter_udp.c
new file mode 100644
index 000000000..75cf1fc6c
--- /dev/null
+++ b/samples/rxfilter/bpf/rxfilter_udp.c
@@ -0,0 +1,113 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// eBPF XDP program: drop or pass UDP packets by destination port.
+//
+// User mode configures the filter by writing to the BPF maps:
+// - port_map[0] = UDP destination port (network byte order) to match.
+// - action_map[0] = xdp_action_t to apply on match (XDP_DROP, XDP_PASS, XDP_TX).
+//
+// Packets that do not match are passed through.
+//
+// Compile:
+// clang -g -target bpf -O2 -I -c rxfilter_udp.c -o rxfilter_udp.o
+// Convert-BpfToNative.ps1 -FileName rxfilter_udp -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+//
+// Map holding the UDP destination port to match (network byte order).
+//
+struct
+{
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint16_t);
+ __uint(max_entries, 1);
+} port_map SEC(".maps");
+
+//
+// Map holding the XDP action to take on a matching packet.
+//
+struct
+{
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint32_t);
+ __uint(max_entries, 1);
+} action_map SEC(".maps");
+
+SEC("xdp/udp_filter")
+int
+udp_filter(xdp_md_t *ctx)
+{
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+ uint32_t zero = 0;
+
+ ETHERNET_HEADER *eth = (ETHERNET_HEADER *)data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ //
+ // Support both IPv4 and IPv6 by checking the EtherType and skipping
+ // the appropriate IP header to reach the UDP header.
+ //
+ void *transport;
+ uint8_t protocol;
+
+ if (eth->Type == bpf_htons(ETHERNET_TYPE_IPV4)) {
+ IPV4_HEADER *ip = (IPV4_HEADER *)(eth + 1);
+ if ((char *)ip + sizeof(*ip) > (char *)data_end) {
+ return XDP_PASS;
+ }
+ protocol = ip->Protocol;
+ transport = (char *)ip + (ip->HeaderLength * 4);
+ } else if (eth->Type == bpf_htons(ETHERNET_TYPE_IPV6)) {
+ IPV6_HEADER *ip6 = (IPV6_HEADER *)(eth + 1);
+ if ((char *)ip6 + sizeof(*ip6) > (char *)data_end) {
+ return XDP_PASS;
+ }
+ protocol = ip6->NextHeader;
+ transport = (char *)(ip6 + 1);
+ } else {
+ return XDP_PASS;
+ }
+
+ if (protocol != 17) { // Not UDP
+ return XDP_PASS;
+ }
+
+ UDP_HEADER *udp = (UDP_HEADER *)transport;
+ if ((char *)udp + sizeof(*udp) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ //
+ // Look up the configured port and action from BPF maps.
+ //
+ uint16_t *target_port = bpf_map_lookup_elem(&port_map, &zero);
+ if (!target_port) {
+ return XDP_PASS;
+ }
+
+ if (udp->DestinationPort != *target_port) {
+ return XDP_PASS;
+ }
+
+ uint32_t *action = bpf_map_lookup_elem(&action_map, &zero);
+ if (!action) {
+ return XDP_DROP;
+ }
+
+ return *action;
+}
diff --git a/samples/rxfilter/rxfilter.c b/samples/rxfilter/rxfilter.c
index ba7173b9a..0c0a836ed 100644
--- a/samples/rxfilter/rxfilter.c
+++ b/samples/rxfilter/rxfilter.c
@@ -4,84 +4,56 @@
//
#include
-#include
-#include
-#include
+#pragma warning(push)
+#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
+#include
+#include
+#pragma warning(pop)
#include
#include
+#include
+
+#define LOGERR(...) \
+ fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
CONST CHAR *UsageText =
-"rxfilter.exe -IfIndex -QueueId [OPTIONS] RULE_PARAMS\n"
-"\n"
-"Filters RX traffic using an XDP program. Traffic that does not match the\n"
-"filter will be allowed to pass through.\n"
-"\n"
-"RULE_PARAMS:\n"
-"\n"
-" -MatchType \n"
-"\n"
-" The frame filter type. Each of the supported types and their parameters\n"
-" are listed under FILTER_TYPES.\n"
-"\n"
-" -Action \n"
-"\n"
-" The action to perform on matching frames:\n"
-" - Pass: Allow the frame to pass through XDP\n"
-" - Drop: Drop the frame\n"
-" - L2Fwd: Forward the frame back to the L2 sender\n"
-"\n"
-"FILTER_TYPES:\n"
+"rxfilter.exe -IfIndex -BpfProgram -ProgramName [OPTIONS]\n"
"\n"
-" All\n"
+"Filters RX traffic using an eBPF XDP program. The specified compiled eBPF\n"
+"program (.sys) is loaded and attached to the given network interface.\n"
"\n"
-" Matches all frames.\n"
-"\n"
-" UdpDstPort\n"
-"\n"
-" Matches all UDP frames with the specified destination port\n"
-"\n"
-" -UdpDstPort \n"
-" The UDP destination port\n"
-"\n"
-" IcmpEchoReplyIpv4 \n"
-"\n"
-" Matches all ICMPv4 echo reply frames with the specified IPv4 destination address\n"
-"\n"
-" -IcmpDstIpv4 \n"
-" The destination IPv4 address\n"
-"\n"
-" IcmpEchoReplyIpv6 \n"
+"OPTIONS:\n"
"\n"
-" Matches all ICMPv6 echo reply frames with the specified IPv6 destination address\n"
+" -IfIndex \n"
+" The network interface index to attach the program to.\n"
"\n"
-" -IcmpDstIpv6 \n"
-" The destination IPv6 address\n"
+" -BpfProgram \n"
+" Path to the compiled eBPF program (.sys native driver).\n"
"\n"
-"OPTIONS:\n"
+" -ProgramName \n"
+" The function name of the eBPF program entry point.\n"
"\n"
-" -XdpMode \n"
+" -UdpDstPort \n"
+" For udp_filter: the UDP destination port to filter.\n"
"\n"
-" The XDP interface provider mode:\n"
-" - System: The system determines the ideal XDP provider\n"
-" - Generic: Use the generic XDP interface provider\n"
-" - Native: Use the native XDP interface provider\n"
-" Default: System\n"
+" -Action \n"
+" For udp_filter: the action on match (Drop or Pass). Default: Drop.\n"
"\n"
"Examples:\n"
"\n"
-" rxfilter.exe -IfIndex 6 -QueueId 0 -MatchType All -Action Drop\n"
-" rxfilter.exe -IfIndex 6 -QueueId * -MatchType UdpDstPort -UdpDstPort 53 -Action Drop\n"
-" rxfilter.exe -IfIndex 6 -QueueId * -MatchType IcmpEchoReplyIpv4 -IcmpDstIpv4 '172.169.0.22' -Action Drop\n"
+" rxfilter.exe -IfIndex 6 -BpfProgram rxfilter_drop_all.sys -ProgramName drop_all\n"
+" rxfilter.exe -IfIndex 6 -BpfProgram rxfilter_udp.sys -ProgramName udp_filter -UdpDstPort 53 -Action Drop\n"
+" rxfilter.exe -IfIndex 6 -BpfProgram rxfilter_l2fwd.sys -ProgramName l2fwd\n"
;
-#define LOGERR(...) \
- fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
-
-UINT32 IfIndex;
-UINT32 QueueId;
-XDP_RULE Rule;
-XDP_CREATE_PROGRAM_FLAGS ProgramFlags;
+static UINT32 IfIndex;
+static const CHAR *BpfProgramPath;
+static const CHAR *ProgramName;
+static UINT16 UdpDstPort;
+static BOOLEAN UdpDstPortSet;
+static UINT32 MatchAction; // xdp_action_t: 2=DROP, 1=PASS
+static
VOID
ParseArgs(
INT ArgC,
@@ -90,13 +62,12 @@ ParseArgs(
{
INT i = 1;
- IfIndex = MAXUINT32;
- QueueId = MAXUINT32;
- ProgramFlags = 0;
-
- ZeroMemory(&Rule, sizeof(Rule));
- Rule.Match = XDP_MATCH_ALL;
- Rule.Action = XDP_PROGRAM_ACTION_PASS;
+ IfIndex = 0;
+ BpfProgramPath = NULL;
+ ProgramName = NULL;
+ UdpDstPort = 0;
+ UdpDstPortSet = FALSE;
+ MatchAction = 2; // XDP_DROP
while (i < ArgC) {
if (!_stricmp(ArgV[i], "-IfIndex")) {
@@ -105,114 +76,36 @@ ParseArgs(
goto Usage;
}
IfIndex = atoi(ArgV[i]);
- } else if (!_stricmp(ArgV[i], "-QueueId")) {
- if (++i >= ArgC) {
- LOGERR("Missing QueueId");
- goto Usage;
- }
- if (!_stricmp(ArgV[i], "*")) {
- ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES;
- QueueId = 0;
- } else {
- QueueId = atoi(ArgV[i]);
- }
- } else if (!_stricmp(ArgV[i], "-XdpMode")) {
- if (++i >= ArgC) {
- LOGERR("Missing XdpMode");
- goto Usage;
- }
- if (!_stricmp(ArgV[i], "Generic")) {
- ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_GENERIC;
- } else if (!_stricmp(ArgV[i], "Native")) {
- ProgramFlags |= XDP_CREATE_PROGRAM_FLAG_NATIVE;
- } else if (_stricmp(ArgV[i], "System")) {
- LOGERR("Invalid XdpMode");
- goto Usage;
- }
- } else if (!_stricmp(ArgV[i], "-MatchType")) {
+ } else if (!_stricmp(ArgV[i], "-BpfProgram")) {
if (++i >= ArgC) {
- LOGERR("Missing MatchType");
- goto Usage;
- }
- if (!_stricmp(ArgV[i], "All")) {
- Rule.Match = XDP_MATCH_ALL;
- } else if (!_stricmp(ArgV[i], "UdpDstPort")) {
- Rule.Match = XDP_MATCH_UDP_DST;
- } else if (!_stricmp(ArgV[i], "IcmpEchoReplyIpv4")) {
- Rule.Match = XDP_MATCH_ICMPV4_ECHO_REPLY_IP_DST;
- } else if (!_stricmp(ArgV[i], "IcmpEchoReplyIpv6")) {
- Rule.Match = XDP_MATCH_ICMPV6_ECHO_REPLY_IP_DST;
- } else {
- LOGERR("Invalid MatchType");
+ LOGERR("Missing BpfProgram path");
goto Usage;
}
- } else if (!_stricmp(ArgV[i], "-Action")) {
+ BpfProgramPath = ArgV[i];
+ } else if (!_stricmp(ArgV[i], "-ProgramName")) {
if (++i >= ArgC) {
- LOGERR("Missing Action");
- goto Usage;
- }
- if (!_stricmp(ArgV[i], "Pass")) {
- Rule.Action = XDP_PROGRAM_ACTION_PASS;
- } else if (!_stricmp(ArgV[i], "Drop")) {
- Rule.Action = XDP_PROGRAM_ACTION_DROP;
- } else if (!_stricmp(ArgV[i], "L2Fwd")) {
- Rule.Action = XDP_PROGRAM_ACTION_L2FWD;
- } else {
- LOGERR("Invalid Action");
+ LOGERR("Missing ProgramName");
goto Usage;
}
+ ProgramName = ArgV[i];
} else if (!_stricmp(ArgV[i], "-UdpDstPort")) {
if (++i >= ArgC) {
LOGERR("Missing UdpDstPort");
goto Usage;
}
- if (Rule.Match == XDP_MATCH_UDP_DST) {
- Rule.Pattern.Port = _byteswap_ushort((UINT16)atoi(ArgV[i]));
- } else {
- LOGERR("Unexpected UdpDstPort");
- }
- } else if (!_stricmp(ArgV[i], "-IcmpDstIpv4")) {
- if (++i >= ArgC) {
- LOGERR("Missing IcmpDstIpv4");
- goto Usage;
- }
- if (Rule.Match == XDP_MATCH_ICMPV4_ECHO_REPLY_IP_DST) {
- const char *IpStr = ArgV[i];
-
- // Parse dotted-quad into IN_ADDR (network byte order)
- int rc = inet_pton(AF_INET, IpStr, &Rule.Pattern.IpMask.Address.Ipv4);
- if (rc != 1) {
- if (rc == 0) {
- LOGERR("Invalid IPv4 address format: %s\n", IpStr);
- } else {
- LOGERR("InetPtonA error: %d\n", WSAGetLastError());
- }
- goto Usage;
- }
- } else {
- LOGERR("Unexpected IcmpDstIpv4");
- goto Usage;
- }
- } else if (!_stricmp(ArgV[i], "-IcmpDstIpv6")) {
+ UdpDstPort = (UINT16)atoi(ArgV[i]);
+ UdpDstPortSet = TRUE;
+ } else if (!_stricmp(ArgV[i], "-Action")) {
if (++i >= ArgC) {
- LOGERR("Missing IcmpDstIpv6");
+ LOGERR("Missing Action");
goto Usage;
}
- if (Rule.Match == XDP_MATCH_ICMPV6_ECHO_REPLY_IP_DST) {
- const char *IpStr = ArgV[i];
-
- // Parse dotted-quad into IN_ADDR (network byte order)
- int rc = inet_pton(AF_INET6, IpStr, &Rule.Pattern.IpMask.Address.Ipv6);
- if (rc != 1) {
- if (rc == 0) {
- LOGERR("Invalid IPv6 address format: %s\n", IpStr);
- } else {
- LOGERR("InetPtonA error: %d\n", WSAGetLastError());
- }
- goto Usage;
- }
+ if (!_stricmp(ArgV[i], "Drop")) {
+ MatchAction = 2; // XDP_DROP
+ } else if (!_stricmp(ArgV[i], "Pass")) {
+ MatchAction = 1; // XDP_PASS
} else {
- LOGERR("Unexpected IcmpDstIpv6");
+ LOGERR("Invalid Action (use Drop or Pass)");
goto Usage;
}
} else {
@@ -223,21 +116,25 @@ ParseArgs(
++i;
}
- if (IfIndex == MAXUINT32) {
+ if (IfIndex == 0) {
LOGERR("IfIndex is required");
goto Usage;
}
- if (QueueId == MAXUINT32) {
- LOGERR("QueueId is required");
+ if (BpfProgramPath == NULL) {
+ LOGERR("BpfProgram is required");
+ goto Usage;
+ }
+
+ if (ProgramName == NULL) {
+ LOGERR("ProgramName is required");
goto Usage;
}
return;
Usage:
-
- printf(UsageText);
+ printf("%s", UsageText);
exit(1);
}
@@ -248,34 +145,96 @@ main(
CHAR **argv
)
{
- XDP_STATUS XdpStatus;
- HANDLE Program;
- const XDP_HOOK_ID XdpInspectRxL2 = {
- XDP_HOOK_L2,
- XDP_HOOK_RX,
- XDP_HOOK_INSPECT,
- };
+ struct bpf_object *BpfObject = NULL;
+ struct bpf_program *BpfProgram = NULL;
+ int ProgramFd;
+ int Result;
- //
- // Parse the command line arguments.
- //
ParseArgs(argc, argv);
+ BpfObject = bpf_object__open(BpfProgramPath);
+ if (BpfObject == NULL) {
+ LOGERR("bpf_object__open(%s) failed", BpfProgramPath);
+ return 1;
+ }
+
+ Result = bpf_object__load(BpfObject);
+ if (Result != 0) {
+ LOGERR("bpf_object__load failed: %d", Result);
+ goto Exit;
+ }
+
+ BpfProgram = bpf_object__find_program_by_name(BpfObject, ProgramName);
+ if (BpfProgram == NULL) {
+ LOGERR("Program '%s' not found in %s", ProgramName, BpfProgramPath);
+ goto Exit;
+ }
+
+ ProgramFd = bpf_program__fd(BpfProgram);
+ if (ProgramFd < 0) {
+ LOGERR("bpf_program__fd failed");
+ goto Exit;
+ }
+
//
- // Create an XDP program using the parsed rule at the L2 inspect hook point.
+ // If map-based configuration is needed, populate the maps before
+ // attaching the program.
//
- XdpStatus =
- XdpCreateProgram(
- IfIndex, &XdpInspectRxL2, QueueId, ProgramFlags, &Rule, 1, &Program);
- if (FAILED(XdpStatus)) {
- LOGERR("XdpCreateProgram failed: %x", XdpStatus);
- return 1;
+ if (UdpDstPortSet) {
+ fd_t PortMapFd = bpf_object__find_map_fd_by_name(BpfObject, "port_map");
+ fd_t ActionMapFd = bpf_object__find_map_fd_by_name(BpfObject, "action_map");
+
+ if (PortMapFd < 0) {
+ LOGERR("port_map not found in eBPF program");
+ goto Exit;
+ }
+ if (ActionMapFd < 0) {
+ LOGERR("action_map not found in eBPF program");
+ goto Exit;
+ }
+
+ UINT32 Key = 0;
+ UINT16 Port = htons(UdpDstPort);
+
+ Result = bpf_map_update_elem(PortMapFd, &Key, &Port, 0);
+ if (Result != 0) {
+ LOGERR("Failed to set port_map: %d", Result);
+ goto Exit;
+ }
+
+ Result = bpf_map_update_elem(ActionMapFd, &Key, &MatchAction, 0);
+ if (Result != 0) {
+ LOGERR("Failed to set action_map: %d", Result);
+ goto Exit;
+ }
+
+ printf("Configured UDP filter: port=%u action=%s\n",
+ UdpDstPort, MatchAction == 2 ? "DROP" : "PASS");
}
+ Result = bpf_xdp_attach(IfIndex, ProgramFd, 0, NULL);
+ if (Result != 0) {
+ LOGERR("bpf_xdp_attach(IfIndex=%u) failed: %d", IfIndex, Result);
+ goto Exit;
+ }
+
+ printf("eBPF program '%s' attached to IfIndex %u. Press Ctrl+C to stop.\n",
+ ProgramName, IfIndex);
+
//
- // Let XDP filter frames until this process is terminated.
+ // Let the eBPF program filter frames until this process is terminated.
//
Sleep(INFINITE);
- return 0;
+ //
+ // Detach on exit (reached if INFINITE sleep is interrupted).
+ //
+ bpf_xdp_detach(IfIndex, 0, NULL);
+
+Exit:
+ if (BpfObject != NULL) {
+ bpf_object__close(BpfObject);
+ }
+
+ return Result == 0 ? 0 : 1;
}
diff --git a/samples/rxfilter/rxfilter.vcxproj b/samples/rxfilter/rxfilter.vcxproj
index a23a64d77..f388de4c2 100644
--- a/samples/rxfilter/rxfilter.vcxproj
+++ b/samples/rxfilter/rxfilter.vcxproj
@@ -1,12 +1,13 @@
-
+
- {c0c730a8-2dab-4c28-8c4b-5effa6b494dd}
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}
rxfilter
exe
+ true
diff --git a/samples/xskfwd/bpf/xskfwd_redirect.c b/samples/xskfwd/bpf/xskfwd_redirect.c
new file mode 100644
index 000000000..a27151680
--- /dev/null
+++ b/samples/xskfwd/bpf/xskfwd_redirect.c
@@ -0,0 +1,72 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// eBPF XDP program: redirect UDP port 1234 traffic to an AF_XDP socket via
+// XSKMAP. Non-matching traffic is passed through.
+//
+// User mode populates xsk_map[queue_index] with the XSK socket handle.
+//
+// Compile:
+// clang -g -target bpf -O2 -I -c xskfwd_redirect.c -o xskfwd_redirect.o
+// Convert-BpfToNative.ps1 -FileName xskfwd_redirect -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+struct
+{
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/xskfwd_redirect")
+int
+xskfwd_redirect(xdp_md_t *ctx)
+{
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+
+ ETHERNET_HEADER *eth = (ETHERNET_HEADER *)data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ //
+ // Only process IPv4 packets.
+ //
+ if (eth->Type != bpf_htons(ETHERNET_TYPE_IPV4)) {
+ return XDP_PASS;
+ }
+
+ IPV4_HEADER *ip = (IPV4_HEADER *)(eth + 1);
+ if ((char *)ip + sizeof(*ip) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ if (ip->Protocol != 17) { // Not UDP
+ return XDP_PASS;
+ }
+
+ UDP_HEADER *udp = (UDP_HEADER *)((char *)ip + (ip->HeaderLength * 4));
+ if ((char *)udp + sizeof(*udp) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ //
+ // Redirect UDP port 1234 traffic to the AF_XDP socket.
+ //
+ if (udp->DestinationPort == bpf_htons(1234)) {
+ return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, XDP_PASS);
+ }
+
+ return XDP_PASS;
+}
diff --git a/samples/xskfwd/xskfwd.c b/samples/xskfwd/xskfwd.c
index 535632333..2440866a3 100644
--- a/samples/xskfwd/xskfwd.c
+++ b/samples/xskfwd/xskfwd.c
@@ -5,31 +5,31 @@
#include
#include
-#include
-
-#ifdef _KERNEL_MODE
-#define LOGERR(...)
-#else
+#pragma warning(push)
+#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
+#include
+#include
+#pragma warning(pop)
#include
#include
+
#define LOGERR(...) \
fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
-#endif
-const CHAR *UsageText =
-"xskfwd.exe "
+CONST CHAR *UsageText =
+"xskfwd.exe [-BpfProgram ] [-ProgramName ]\n"
"\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"
+"Forwards RX traffic using an eBPF XDP program and AF_XDP sockets. Traffic\n"
+"destined to UDP port 1234 on the specified interface is redirected to an\n"
+"AF_XDP socket, MAC addresses are swapped, and the frame is transmitted back.\n"
+"\n"
+"If -BpfProgram is not specified, loads xskfwd_redirect.sys from the current\n"
+"directory. If -ProgramName is not specified, defaults to xskfwd_redirect.\n"
;
-const XDP_HOOK_ID XdpInspectRxL2 = {
- .Layer = XDP_HOOK_L2,
- .Direction = XDP_HOOK_RX,
- .SubLayer = XDP_HOOK_INSPECT,
-};
+static const CHAR *DefaultProgramName = "xskfwd_redirect";
+
+static const CHAR *DefaultBpfProgram = "xskfwd_redirect.sys";
static
VOID
@@ -41,11 +41,8 @@ TranslateRxToTx(
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.
+ // Swap source and destination MAC addresses to echo the frame back.
//
-
if (Length >= sizeof(MacAddress) * 2) {
RtlCopyMemory(MacAddress, Frame, sizeof(MacAddress));
RtlCopyMemory(Frame, Frame + sizeof(MacAddress), sizeof(MacAddress));
@@ -53,16 +50,22 @@ TranslateRxToTx(
}
}
-XDP_STATUS
-XskFwd(
- _In_ UINT32 IfIndex,
- _In_ volatile BOOLEAN *Stop
+INT
+__cdecl
+main(
+ INT argc,
+ CHAR **argv
)
{
- HRESULT Result;
+ HRESULT HResult;
+ int Result = 1;
+ UINT32 IfIndex;
+ const CHAR *BpfProgramPath = DefaultBpfProgram;
+ struct bpf_object *BpfObject = NULL;
+ struct bpf_program *BpfProgram = NULL;
+ int ProgramFd;
+ fd_t XskMapFd;
HANDLE Socket = NULL;
- HANDLE Program = NULL;
- XDP_RULE Rule = {0};
UCHAR Frame[1514];
XSK_UMEM_REG UmemReg = {0};
const UINT32 RingSize = 1;
@@ -73,413 +76,223 @@ XskFwd(
XSK_RING TxRing;
XSK_RING TxCompRing;
UINT32 RingIndex;
+ UINT32 QueueId = 0;
//
- // Create an AF_XDP socket. The newly created socket is not connected.
+ // Parse arguments.
//
- Result = XskCreate(&Socket);
- if (XDP_FAILED(Result)) {
- LOGERR("XskCreate failed: %x", Result);
+ if (argc < 2 || argv[1][0] == '-') {
+ printf("%s", UsageText);
+ return 1;
+ }
+
+ IfIndex = atoi(argv[1]);
+ if (IfIndex == 0) {
+ LOGERR("Invalid IfIndex");
+ return 1;
+ }
+
+ const CHAR *ProgramName = DefaultProgramName;
+
+ for (INT i = 2; i < argc; i++) {
+ if (!_stricmp(argv[i], "-BpfProgram")) {
+ if (++i >= argc) {
+ LOGERR("Missing BpfProgram path");
+ return 1;
+ }
+ BpfProgramPath = argv[i];
+ } else if (!_stricmp(argv[i], "-ProgramName")) {
+ if (++i >= argc) {
+ LOGERR("Missing ProgramName");
+ return 1;
+ }
+ ProgramName = argv[i];
+ }
+ }
+
+ HResult = XskCreate(&Socket);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XskCreate failed: %x", HResult);
goto Exit;
}
//
- // 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.
+ // Register a single-frame UMEM buffer.
//
UmemReg.TotalSize = sizeof(Frame);
UmemReg.ChunkSize = sizeof(Frame);
UmemReg.Address = Frame;
- Result = XskSetSockopt(Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg));
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_UMEM_REG failed: %x", Result);
+ HResult = XskSetSockopt(Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg));
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_UMEM_REG failed: %x", HResult);
goto Exit;
}
//
- // Bind the AF_XDP socket to the specified interface and 0th data path
- // queue, and indicate the intent to perform RX and TX actions.
+ // Bind the socket to the interface and queue 0 for RX and TX.
//
- Result = XskBind(Socket, IfIndex, 0, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
- if (XDP_FAILED(Result)) {
- LOGERR("XskBind failed: %x", Result);
+ HResult = XskBind(Socket, IfIndex, QueueId, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XskBind failed: %x", HResult);
goto Exit;
}
//
- // 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.
+ // Configure descriptor rings with capacity of one frame each.
//
-
- Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed: %x", Result);
+ HResult = XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed: %x", HResult);
goto Exit;
}
- Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize));
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed: %x", Result);
+ HResult = XskSetSockopt(Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed: %x", HResult);
goto Exit;
}
- Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_RING_SIZE, &RingSize, sizeof(RingSize));
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_SOCKOPT_TX_RING_SIZE failed: %x", Result);
+ HResult = XskSetSockopt(Socket, XSK_SOCKOPT_TX_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_SOCKOPT_TX_RING_SIZE failed: %x", HResult);
goto Exit;
}
- Result = XskSetSockopt(Socket, XSK_SOCKOPT_TX_COMPLETION_RING_SIZE, &RingSize, sizeof(RingSize));
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_SOCKOPT_TX_COMPLETION_RING_SIZE failed: %x", Result);
+ HResult = XskSetSockopt(Socket, XSK_SOCKOPT_TX_COMPLETION_RING_SIZE, &RingSize, sizeof(RingSize));
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_SOCKOPT_TX_COMPLETION_RING_SIZE failed: %x", HResult);
goto Exit;
}
- //
- // Activate the AF_XDP socket. Once activated, descriptor rings are
- // available and RX and TX can occur.
- //
- Result = XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
- if (XDP_FAILED(Result)) {
- LOGERR("XskActivate failed: %x", Result);
+ HResult = XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XskActivate failed: %x", HResult);
goto Exit;
}
- //
- // Retrieve the RX, RX fill, TX, and TX completion ring info from AF_XDP.
- //
OptionLength = sizeof(RingInfo);
- Result = XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
- if (XDP_FAILED(Result)) {
- LOGERR("XSK_SOCKOPT_RING_INFO failed: %x", Result);
+ HResult = XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XSK_SOCKOPT_RING_INFO failed: %x", HResult);
goto Exit;
}
- //
- // 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.
+ // Place an empty frame descriptor into the RX fill ring.
//
XskRingProducerReserve(&RxFillRing, 1, &RingIndex);
+ *(UINT64 *)XskRingGetElement(&RxFillRing, RingIndex) = 0;
+ XskRingProducerSubmit(&RxFillRing, 1);
+
+ BpfObject = bpf_object__open(BpfProgramPath);
+ if (BpfObject == NULL) {
+ LOGERR("bpf_object__open(%s) failed", BpfProgramPath);
+ goto Exit;
+ }
+
+ Result = bpf_object__load(BpfObject);
+ if (Result != 0) {
+ LOGERR("bpf_object__load failed: %d", Result);
+ goto Exit;
+ }
//
- // 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.
+ // Find the redirect program and the XSKMAP.
//
- *(UINT64 *)XskRingGetElement(&RxFillRing, RingIndex) = 0;
+ BpfProgram = bpf_object__find_program_by_name(BpfObject, ProgramName);
+ if (BpfProgram == NULL) {
+ LOGERR("Program '%s' not found in %s", ProgramName, BpfProgramPath);
+ goto Exit;
+ }
- XskRingProducerSubmit(&RxFillRing, 1);
+ ProgramFd = bpf_program__fd(BpfProgram);
+ if (ProgramFd < 0) {
+ LOGERR("bpf_program__fd failed");
+ goto Exit;
+ }
+
+ XskMapFd = bpf_object__find_map_fd_by_name(BpfObject, "xsk_map");
+ if (XskMapFd < 0) {
+ LOGERR("xsk_map not found in eBPF program");
+ goto Exit;
+ }
//
- // 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.
+ // Populate the XSKMAP: key = queue index, value = XSK socket handle.
//
+ Result = bpf_map_update_elem(XskMapFd, &QueueId, &Socket, 0);
+ if (Result != 0) {
+ LOGERR("bpf_map_update_elem(xsk_map) failed: %d", Result);
+ goto Exit;
+ }
- 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 = XdpCreateProgram(IfIndex, &XdpInspectRxL2, 0, 0, &Rule, 1, &Program);
- if (XDP_FAILED(Result)) {
- LOGERR("XdpCreateProgram failed: %x", Result);
+ Result = bpf_xdp_attach(IfIndex, ProgramFd, 0, NULL);
+ if (Result != 0) {
+ LOGERR("bpf_xdp_attach(IfIndex=%u) failed: %d", IfIndex, Result);
goto Exit;
}
+ printf("eBPF XSK forwarder attached to IfIndex %u (UDP port 1234). Ctrl+C to stop.\n", IfIndex);
+
//
- // 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.
+ // RX-to-TX forwarding loop: receive frames, swap MACs, and transmit.
//
- do {
+ for (;;) {
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 = XskNotifySocket(Socket, XSK_NOTIFY_FLAG_POKE_TX, 0, &NotifyResult);
- if (XDP_FAILED(Result)) {
- LOGERR("XskNotifySocket failed: %x", Result);
+ HResult = XskNotifySocket(Socket, XSK_NOTIFY_FLAG_POKE_TX, 0, &NotifyResult);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("XskNotifySocket failed: %x", HResult);
goto Exit;
}
}
if (XskRingConsumerReserve(&TxCompRing, 1, &RingIndex) == 1) {
- UINT64 *Tx;
+ UINT64 *Tx = XskRingGetElement(&TxCompRing, RingIndex);
UINT64 *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);
}
- } while (!ReadBooleanNoFence(Stop));
-
- Result = XDP_STATUS_SUCCESS;
-
-Exit:
-
- //
- // Close the XDP program. Traffic will no longer be intercepted by XDP.
- //
- if (Program != NULL) {
- CxPlatCloseHandle(Program);
}
- //
- // Close the AF_XDP socket. All socket resources will be cleaned up by XDP.
- //
- if (Socket != NULL) {
- CxPlatCloseHandle(Socket);
- }
-
- return Result;
-}
-
-#ifdef _KERNEL_MODE
-
-DRIVER_INITIALIZE DriverEntry;
-static DRIVER_UNLOAD DriverUnload;
-static KSTART_ROUTINE XskFwdWorker;
-
-static UINT32 IfIndex;
-static BOOLEAN Stop;
-static CXPLAT_THREAD WorkerThread;
-
-static
-_Use_decl_annotations_
-VOID
-XskFwdWorker(
- VOID *Context
- )
-{
- UNREFERENCED_PARAMETER(Context);
-
- XskFwd(IfIndex, &Stop);
-}
-
-static
-_Use_decl_annotations_
-VOID
-DriverUnload(
- DRIVER_OBJECT *DriverObject
- )
-{
- UNREFERENCED_PARAMETER(DriverObject);
-
- if (WorkerThread != NULL) {
- WriteBooleanNoFence(&Stop, TRUE);
- CxPlatThreadWaitForever(&WorkerThread);
- CxPlatThreadDelete(&WorkerThread);
- }
-}
-
-_Use_decl_annotations_
-NTSTATUS
-DriverEntry(
- DRIVER_OBJECT *DriverObject,
- UNICODE_STRING *RegistryPath
- )
-{
- NTSTATUS Status;
- CXPLAT_THREAD_CONFIG ThreadConfig = {0};
- HANDLE KeyHandle;
- UNICODE_STRING UnicodeName;
- OBJECT_ATTRIBUTES ObjectAttributes = {0};
- UCHAR InformationBuffer[512] = {0};
- KEY_VALUE_FULL_INFORMATION *Information = (KEY_VALUE_FULL_INFORMATION *) InformationBuffer;
- ULONG ResultLength;
- BOOLEAN RunInline = FALSE;
-
-#pragma prefast(suppress : __WARNING_BANNED_MEM_ALLOCATION_UNSAFE, "Non executable pool is enabled via -DPOOL_NX_OPTIN_AUTO=1.")
- ExInitializeDriverRuntime(0);
- DriverObject->DriverUnload = DriverUnload;
-
- InitializeObjectAttributes(
- &ObjectAttributes,
- RegistryPath,
- OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
- NULL,
- NULL);
- Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
- if (!NT_SUCCESS(Status)) {
- goto Exit;
- }
-
- //
- // Read the configured IfIndex from HKLM\SYSTEM\CurrentControlSet\Services\xskfwd\IfIndex
- //
- RtlInitUnicodeString(&UnicodeName, L"IfIndex");
- Status =
- ZwQueryValueKey(
- KeyHandle,
- &UnicodeName,
- KeyValueFullInformation,
- Information,
- sizeof(InformationBuffer),
- &ResultLength);
- if (NT_SUCCESS(Status)) {
- if (Information->Type != REG_DWORD) {
- Status = STATUS_INVALID_PARAMETER_MIX;
- } else {
- IfIndex = *((DWORD UNALIGNED *)((CHAR *)Information + Information->DataOffset));
- }
- }
-
- //
- // Read the configured IfIndex from HKLM\SYSTEM\CurrentControlSet\Services\xskfwd\RunInline
- //
- RtlInitUnicodeString(&UnicodeName, L"RunInline");
- Status =
- ZwQueryValueKey(
- KeyHandle,
- &UnicodeName,
- KeyValueFullInformation,
- Information,
- sizeof(InformationBuffer),
- &ResultLength);
- if (NT_SUCCESS(Status)) {
- if (Information->Type != REG_DWORD) {
- Status = STATUS_INVALID_PARAMETER_MIX;
- } else {
- RunInline = !!*((DWORD UNALIGNED *)((CHAR *)Information + Information->DataOffset));
- }
- }
-
- ZwClose(KeyHandle);
-
- if (RunInline) {
- BOOLEAN AlwaysStop = TRUE;
-
- //
- // Run a single iteration and return the result.
- //
- Status = XskFwd(IfIndex, &AlwaysStop);
- if (!NT_SUCCESS(Status)) {
- goto Exit;
- }
- } else {
- //
- // Start a system worker thread to run until the driver is unloaded.
- //
- ThreadConfig.Callback = XskFwdWorker;
- Status = CxPlatThreadCreate(&ThreadConfig, &WorkerThread);
- if (!NT_SUCCESS(Status)) {
- goto Exit;
- }
- }
-
-
Exit:
+ bpf_xdp_detach(IfIndex, 0, NULL);
- if (!NT_SUCCESS(Status)) {
- DriverUnload(DriverObject);
+ if (BpfObject != NULL) {
+ bpf_object__close(BpfObject);
}
- return Status;
-}
-
-#else // _KERNEL_MODE
-
-INT
-__cdecl
-main(
- INT argc,
- CHAR **argv
- )
-{
- UINT32 IfIndex;
- BOOLEAN Stop = FALSE; // Run until the process is terminated.
-
- if (argc < 2) {
- LOGERR(UsageText);
- return EXIT_FAILURE;
+ if (Socket != NULL) {
+ CloseHandle(Socket);
}
- IfIndex = atoi(argv[1]);
-
- return XskFwd(IfIndex, &Stop);
+ return Result == 0 ? 0 : 1;
}
-
-#endif // _KERNEL_MODE
diff --git a/samples/xskfwd/xskfwd.vcxproj b/samples/xskfwd/xskfwd.vcxproj
index 3bc861972..2ba327529 100644
--- a/samples/xskfwd/xskfwd.vcxproj
+++ b/samples/xskfwd/xskfwd.vcxproj
@@ -1,12 +1,13 @@
-
+
- {238cd65b-59d5-4ec5-ba23-630f567395fa}
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}
xskfwd
exe
+ true
diff --git a/samples/xskrestricted/bpf/xskrestricted_redirect.c b/samples/xskrestricted/bpf/xskrestricted_redirect.c
new file mode 100644
index 000000000..26b1a6a0a
--- /dev/null
+++ b/samples/xskrestricted/bpf/xskrestricted_redirect.c
@@ -0,0 +1,64 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// eBPF XDP program: redirect UDP port 1234 traffic to an AF_XDP socket.
+// Used by the xskrestricted sample for restricted-token forwarding.
+//
+// Compile:
+// clang -g -target bpf -O2 -I -c xskrestricted_redirect.c -o xskrestricted_redirect.o
+// Convert-BpfToNative.ps1 -FileName xskrestricted_redirect -IncludeDir -Platform x64 -Configuration Release -KernelMode $true
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "net/if_ether.h"
+#include "net/ip.h"
+#include "xdp/ebpfhook.h"
+
+struct
+{
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+SEC("xdp/xskrestricted_redirect")
+int
+xskrestricted_redirect(xdp_md_t *ctx)
+{
+ void *data = ctx->data;
+ void *data_end = ctx->data_end;
+
+ ETHERNET_HEADER *eth = (ETHERNET_HEADER *)data;
+ if ((char *)eth + sizeof(*eth) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ if (eth->Type != bpf_htons(ETHERNET_TYPE_IPV4)) {
+ return XDP_PASS;
+ }
+
+ IPV4_HEADER *ip = (IPV4_HEADER *)(eth + 1);
+ if ((char *)ip + sizeof(*ip) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ if (ip->Protocol != 17) {
+ return XDP_PASS;
+ }
+
+ UDP_HEADER *udp = (UDP_HEADER *)((char *)ip + (ip->HeaderLength * 4));
+ if ((char *)udp + sizeof(*udp) > (char *)data_end) {
+ return XDP_PASS;
+ }
+
+ if (udp->DestinationPort == bpf_htons(1234)) {
+ return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, XDP_PASS);
+ }
+
+ return XDP_PASS;
+}
diff --git a/samples/xskrestricted/xskrestricted.c b/samples/xskrestricted/xskrestricted.c
index 3d9740397..50a1c5ba2 100644
--- a/samples/xskrestricted/xskrestricted.c
+++ b/samples/xskrestricted/xskrestricted.c
@@ -4,49 +4,62 @@
//
//
-// This sample demonstrates how to create an AF_XDP socket and XDP program in
-// a privileged parent process, then duplicate those handles into a child
-// process running with a restricted token. The child process uses the
-// duplicated handles to perform RX-to-TX forwarding without needing elevated
+// This sample demonstrates how to create an AF_XDP socket and attach an eBPF
+// XDP program in a privileged parent process, then duplicate the socket handle
+// into a child process running with a restricted token. The child process uses
+// the duplicated handle to perform RX-to-TX forwarding without needing elevated
// privileges itself.
//
// Approach:
-// 1. Parent creates AF_XDP socket and XDP program (requires admin).
-// 2. Parent creates a restricted token (Administrators SID disabled).
-// 3. Parent spawns a child process (suspended) with the restricted token.
-// 4. Parent duplicates the socket and program handles into the child.
-// 5. Parent writes handle values to a named pipe the child reads on startup.
-// 6. Parent resumes the child, which configures rings and forwards packets.
-//
-// Usage:
-// xskrestricted.exe [-TimeoutSeconds ]
+// 1. Parent loads eBPF program, creates AF_XDP socket, populates XSKMAP.
+// 2. Parent attaches eBPF program to the interface.
+// 3. Parent creates a restricted token (Administrators SID disabled).
+// 4. Parent spawns a child process (suspended) with the restricted token.
+// 5. Parent duplicates the socket handle into the child.
+// 6. Parent writes handle value to a named pipe the child reads on startup.
+// 7. Parent resumes the child, which configures rings and forwards packets.
//
#include
#include
+#pragma warning(push)
+#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
+#include
+#include
+#pragma warning(pop)
#include
#include
#include
+CONST CHAR *UsageText =
+"Usage:\n"
+" xskrestricted.exe [-BpfProgram ] [-ProgramName ]\n"
+" [-TimeoutSeconds ]\n"
+"\n"
+"Loads an eBPF XDP program, creates an AF_XDP socket, then spawns a child\n"
+"process with a restricted token and duplicates the socket handle into it.\n"
+"The child forwards UDP port 1234 traffic back to the sender.\n"
+"\n"
+"Options:\n"
+" -BpfProgram Path to compiled eBPF program.\n"
+" Default: xskrestricted_redirect.sys\n"
+" -ProgramName eBPF program entry point name.\n"
+" Default: xskrestricted_redirect\n"
+" -TimeoutSeconds Exit after the specified number of seconds.\n"
+" If 0 or omitted, run indefinitely.\n";
+
#define LOGERR(...) \
fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n")
#define LOGINFO(...) \
fprintf(stdout, "INFO: "); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n")
-static const XDP_HOOK_ID XdpInspectRxL2 = {
- .Layer = XDP_HOOK_L2,
- .Direction = XDP_HOOK_RX,
- .SubLayer = XDP_HOOK_INSPECT,
-};
//
// A simple structure passed from the parent to the child via a named pipe.
-// Contains the duplicated handle values in the child's handle table.
//
typedef struct _XSKRESTRICTED_CHILD_PARAMS {
UINT64 SocketHandle;
- UINT64 ProgramHandle;
UINT32 IfIndex;
UINT32 TimeoutSeconds;
} XSKRESTRICTED_CHILD_PARAMS;
@@ -66,7 +79,6 @@ RunChild(
XSKRESTRICTED_CHILD_PARAMS Params;
DWORD BytesRead;
HANDLE Socket;
- HANDLE Program;
UCHAR Frame[1514];
XSK_UMEM_REG UmemReg = {0};
const UINT32 RingSize = 1;
@@ -78,13 +90,7 @@ RunChild(
XSK_RING TxCompRing;
UINT32 RingIndex;
HANDLE CreatedXsk;
- HANDLE CreatedProgram;
- XDP_RULE Rule = {0};
- //
- // Connect to the named pipe created by the parent to receive the
- // duplicated handle values.
- //
PipeHandle =
CreateFileW(
PipeName,
@@ -108,36 +114,21 @@ RunChild(
CloseHandle(PipeHandle);
Socket = (HANDLE)(ULONG_PTR)Params.SocketHandle;
- Program = (HANDLE)(ULONG_PTR)Params.ProgramHandle;
LOGINFO(
- "[Child] Running with duplicated handles: Socket=%p Program=%p IfIndex=%u",
- Socket, Program, Params.IfIndex);
+ "[Child] Running with duplicated socket handle=%p IfIndex=%u",
+ Socket, Params.IfIndex);
//
- // For sanity, verify this restricted process can't create sockets or
- // programs.
+ // Verify this restricted process can't create sockets.
//
-
if (XskCreate(&CreatedXsk) != HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) {
LOGERR("[Child] XskCreate was not denied access");
return 1;
}
- Rule.Match = XDP_MATCH_ALL;
- Rule.Action = XDP_PROGRAM_ACTION_PASS;
-
- if (XdpCreateProgram(
- Params.IfIndex, &XdpInspectRxL2, 0, XDP_CREATE_PROGRAM_FLAG_NONE, &Rule, 1,
- &CreatedProgram) != HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) {
- LOGERR("[Child] XdpCreateProgram was not denied access");
- return 1;
- }
-
//
- // Register our frame buffer with the AF_XDP socket. The parent created
- // and bound the socket, but UMEM registration, ring setup, and activation
- // are done here in the child's address space.
+ // Register our frame buffer with the AF_XDP socket.
//
UmemReg.TotalSize = sizeof(Frame);
UmemReg.ChunkSize = sizeof(Frame);
@@ -149,9 +140,6 @@ RunChild(
return 1;
}
- //
- // Set ring sizes.
- //
Result = XskSetSockopt(Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize));
if (XDP_FAILED(Result)) {
LOGERR("[Child] XSK_SOCKOPT_RX_RING_SIZE failed: %x", Result);
@@ -176,18 +164,12 @@ RunChild(
return 1;
}
- //
- // Activate the socket so rings become available.
- //
Result = XskActivate(Socket, XSK_ACTIVATE_FLAG_NONE);
if (XDP_FAILED(Result)) {
LOGERR("[Child] XskActivate failed: %x", Result);
return 1;
}
- //
- // Retrieve ring info and initialize ring helpers.
- //
OptionLength = sizeof(RingInfo);
Result = XskGetSockopt(Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength);
if (XDP_FAILED(Result)) {
@@ -209,10 +191,6 @@ RunChild(
LOGINFO("[Child] Forwarding packets (Ctrl+C to stop)...");
- //
- // RX-to-TX forwarding loop: receive frames, swap MAC addresses, and
- // transmit them back. If a timeout was specified, exit after the deadline.
- //
{
ULONGLONG Deadline = 0;
if (Params.TimeoutSeconds > 0) {
@@ -235,10 +213,6 @@ RunChild(
XskRingProducerReserve(&TxRing, 1, &RingIndex);
TxBuffer = XskRingGetElement(&TxRing, RingIndex);
- //
- // Swap source and destination MAC addresses to echo the frame
- // back to the sender.
- //
{
UCHAR *FrameData =
&Frame[RxBuffer->Address.BaseAddress + RxBuffer->Address.Offset];
@@ -280,27 +254,30 @@ RunChild(
LOGINFO("[Child] Exiting successfully");
CloseHandle(Socket);
- CloseHandle(Program);
return 0;
}
//
-// Parent process: create the AF_XDP socket and XDP program, create a
-// restricted child process, duplicate the handles into it, and communicate
-// the handle values via a named pipe.
+// Parent process: load eBPF program, create AF_XDP socket, populate XSKMAP,
+// create a restricted child process, and duplicate the socket handle.
//
static
INT
RunParent(
_In_ UINT32 IfIndex,
+ _In_ const CHAR *BpfProgramPath,
+ _In_ const CHAR *ProgramName,
_In_ UINT32 TimeoutSeconds
)
{
- HRESULT Result;
+ HRESULT HResult;
+ int Result;
+ struct bpf_object *BpfObject = NULL;
+ struct bpf_program *BpfProgram = NULL;
+ int ProgramFd;
+ fd_t XskMapFd;
HANDLE Socket = NULL;
- HANDLE Program = NULL;
- XDP_RULE Rule = {0};
HANDLE RestrictedToken = NULL;
HANDLE CurrentToken = NULL;
SID_AND_ATTRIBUTES SidsToDisable[1];
@@ -310,7 +287,6 @@ RunParent(
PROCESS_INFORMATION ProcessInfo;
WCHAR CommandLine[512];
HANDLE ChildSocket = NULL;
- HANDLE ChildProgram = NULL;
INT ExitCode = 1;
WCHAR ModulePathW[MAX_PATH];
HANDLE PipeHandle = INVALID_HANDLE_VALUE;
@@ -318,15 +294,15 @@ RunParent(
XSKRESTRICTED_CHILD_PARAMS ChildParams;
DWORD BytesWritten;
SECURITY_ATTRIBUTES PipeSa;
- BOOL ProcessCreated = FALSE;
+ BOOLEAN ProcessCreated = FALSE;
+ UINT32 QueueId = 0;
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
- LOGINFO("[Parent] Creating AF_XDP socket and XDP program for IfIndex=%u", IfIndex);
+ LOGINFO("[Parent] Loading eBPF program and creating socket for IfIndex=%u", IfIndex);
//
- // Create a named pipe with a unique name based on the current process ID.
- // The child will read its handle values from this pipe on startup.
+ // Create a named pipe for communicating with the child.
//
_snwprintf_s(
PipeName, ARRAYSIZE(PipeName), _TRUNCATE,
@@ -341,57 +317,76 @@ RunParent(
PipeName,
PIPE_ACCESS_OUTBOUND,
PIPE_TYPE_BYTE | PIPE_WAIT,
- 1, // Max instances
+ 1,
sizeof(XSKRESTRICTED_CHILD_PARAMS),
- 0, // In buffer size
- 0, // Default timeout
+ 0,
+ 0,
&PipeSa);
if (PipeHandle == INVALID_HANDLE_VALUE) {
LOGERR("[Parent] CreateNamedPipeW failed: %u", GetLastError());
goto Exit;
}
- //
- // Create an AF_XDP socket.
- //
- Result = XskCreate(&Socket);
- if (XDP_FAILED(Result)) {
- LOGERR("[Parent] XskCreate failed: %x", Result);
+ HResult = XskCreate(&Socket);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("[Parent] XskCreate failed: %x", HResult);
goto Exit;
}
- //
- // Bind the socket to the specified interface and 0th queue.
- //
- Result = XskBind(Socket, IfIndex, 0, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
- if (XDP_FAILED(Result)) {
- LOGERR("[Parent] XskBind failed: %x", Result);
+ HResult = XskBind(Socket, IfIndex, QueueId, XSK_BIND_FLAG_RX | XSK_BIND_FLAG_TX);
+ if (XDP_FAILED(HResult)) {
+ LOGERR("[Parent] XskBind failed: %x", HResult);
goto Exit;
}
LOGINFO("[Parent] Socket created and bound (handle=%p)", Socket);
- //
- // Create an XDP program that redirects UDP port 1234 traffic to the socket.
- //
- Rule.Match = XDP_MATCH_UDP_DST;
- Rule.Pattern.Port = _byteswap_ushort(1234);
- Rule.Action = XDP_PROGRAM_ACTION_REDIRECT;
- Rule.Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSK;
- Rule.Redirect.Target = Socket;
+ BpfObject = bpf_object__open(BpfProgramPath);
+ if (BpfObject == NULL) {
+ LOGERR("[Parent] bpf_object__open(%s) failed", BpfProgramPath);
+ goto Exit;
+ }
- Result = XdpCreateProgram(IfIndex, &XdpInspectRxL2, 0, 0, &Rule, 1, &Program);
- if (XDP_FAILED(Result)) {
- LOGERR("[Parent] XdpCreateProgram failed: %x", Result);
+ Result = bpf_object__load(BpfObject);
+ if (Result != 0) {
+ LOGERR("[Parent] bpf_object__load failed: %d", Result);
goto Exit;
}
- LOGINFO("[Parent] XDP program created (handle=%p)", Program);
+ BpfProgram = bpf_object__find_program_by_name(BpfObject, ProgramName);
+ if (BpfProgram == NULL) {
+ LOGERR("[Parent] Program '%s' not found", ProgramName);
+ goto Exit;
+ }
+
+ ProgramFd = bpf_program__fd(BpfProgram);
+ if (ProgramFd < 0) {
+ LOGERR("[Parent] bpf_program__fd failed");
+ goto Exit;
+ }
+
+ XskMapFd = bpf_object__find_map_fd_by_name(BpfObject, "xsk_map");
+ if (XskMapFd < 0) {
+ LOGERR("[Parent] xsk_map not found");
+ goto Exit;
+ }
+
+ Result = bpf_map_update_elem(XskMapFd, &QueueId, &Socket, 0);
+ if (Result != 0) {
+ LOGERR("[Parent] bpf_map_update_elem(xsk_map) failed: %d", Result);
+ goto Exit;
+ }
+
+ Result = bpf_xdp_attach(IfIndex, ProgramFd, 0, NULL);
+ if (Result != 0) {
+ LOGERR("[Parent] bpf_xdp_attach failed: %d", Result);
+ goto Exit;
+ }
+
+ LOGINFO("[Parent] eBPF program attached");
//
// Create a restricted token by disabling the Administrators group SID.
- // This demonstrates privilege reduction: the child process cannot create
- // new XDP objects but can use pre-existing duplicated handles.
//
SidSize = SECURITY_MAX_SID_SIZE;
AdminSid = LocalAlloc(LMEM_FIXED, SidSize);
@@ -414,15 +409,7 @@ RunParent(
SidsToDisable[0].Attributes = 0;
if (!CreateRestrictedToken(
- CurrentToken,
- 0, // Flags
- 1, // DisableSidCount
- SidsToDisable, // SidsToDisable
- 0, // DeletePrivilegeCount
- NULL, // PrivilegesToDelete
- 0, // RestrictedSidCount
- NULL, // SidsToRestrict
- &RestrictedToken)) {
+ CurrentToken, 0, 1, SidsToDisable, 0, NULL, 0, NULL, &RestrictedToken)) {
LOGERR("[Parent] CreateRestrictedToken failed: %u", GetLastError());
goto Exit;
}
@@ -430,8 +417,7 @@ RunParent(
LOGINFO("[Parent] Restricted token created (Administrators SID disabled)");
//
- // Create the child process in a suspended state. We pass the pipe name
- // on the command line so the child can read its handle values.
+ // Create the child process in a suspended state.
//
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
@@ -447,17 +433,8 @@ RunParent(
ModulePathW, PipeName);
if (!CreateProcessAsUserW(
- RestrictedToken,
- NULL, // Application name (use command line)
- CommandLine, // Command line
- NULL, // Process attributes
- NULL, // Thread attributes
- FALSE, // Inherit handles
- CREATE_SUSPENDED, // Creation flags
- NULL, // Environment
- NULL, // Current directory
- &StartupInfo,
- &ProcessInfo)) {
+ RestrictedToken, NULL, CommandLine, NULL, NULL, FALSE,
+ CREATE_SUSPENDED, NULL, NULL, &StartupInfo, &ProcessInfo)) {
LOGERR("[Parent] CreateProcessAsUserW failed: %u", GetLastError());
goto Exit;
}
@@ -466,56 +443,23 @@ RunParent(
LOGINFO("[Parent] Child process created (PID=%u), suspended", ProcessInfo.dwProcessId);
- //
- // Duplicate the AF_XDP socket handle into the child process.
- //
if (!DuplicateHandle(
- GetCurrentProcess(), // Source process
- Socket, // Source handle
- ProcessInfo.hProcess, // Target process
- &ChildSocket, // Target handle (value in child's table)
- 0, // Desired access (ignored with DUPLICATE_SAME_ACCESS)
- FALSE, // Inherit handle
- DUPLICATE_SAME_ACCESS)) {
+ GetCurrentProcess(), Socket, ProcessInfo.hProcess, &ChildSocket,
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
LOGERR("[Parent] DuplicateHandle(Socket) failed: %u", GetLastError());
goto Exit;
}
- //
- // Duplicate the XDP program handle into the child process.
- //
- if (!DuplicateHandle(
- GetCurrentProcess(),
- Program,
- ProcessInfo.hProcess,
- &ChildProgram,
- 0,
- FALSE,
- DUPLICATE_SAME_ACCESS)) {
- LOGERR("[Parent] DuplicateHandle(Program) failed: %u", GetLastError());
- goto Exit;
- }
-
- LOGINFO(
- "[Parent] Handles duplicated into child: Socket=%p Program=%p",
- ChildSocket, ChildProgram);
+ LOGINFO("[Parent] Socket handle duplicated into child: %p", ChildSocket);
- //
- // Resume the child process. It will connect to the named pipe and read
- // its parameters.
- //
ResumeThread(ProcessInfo.hThread);
- //
- // Wait for the child to connect, then send the handle values.
- //
if (!ConnectNamedPipe(PipeHandle, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
LOGERR("[Parent] ConnectNamedPipe failed: %u", GetLastError());
goto Exit;
}
ChildParams.SocketHandle = (UINT64)(ULONG_PTR)ChildSocket;
- ChildParams.ProgramHandle = (UINT64)(ULONG_PTR)ChildProgram;
ChildParams.IfIndex = IfIndex;
ChildParams.TimeoutSeconds = TimeoutSeconds;
@@ -525,17 +469,11 @@ RunParent(
goto Exit;
}
- //
- // Close the pipe; the child has its data.
- //
CloseHandle(PipeHandle);
PipeHandle = INVALID_HANDLE_VALUE;
LOGINFO("[Parent] Parameters sent to child, waiting for child to exit...");
- //
- // Wait for the child process to exit.
- //
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
{
@@ -546,7 +484,6 @@ RunParent(
}
Exit:
-
if (ProcessCreated) {
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
@@ -555,8 +492,11 @@ RunParent(
_Analysis_assume_(PipeHandle != NULL);
CloseHandle(PipeHandle);
}
- if (Program != NULL) {
- CloseHandle(Program);
+
+ bpf_xdp_detach(IfIndex, 0, NULL);
+
+ if (BpfObject != NULL) {
+ bpf_object__close(BpfObject);
}
if (Socket != NULL) {
CloseHandle(Socket);
@@ -582,9 +522,6 @@ main(
)
{
if (argc >= 3 && _stricmp(argv[1], "-child") == 0) {
- //
- // Child mode: receive the pipe name, read handle values from the pipe.
- //
WCHAR PipeName[128];
if (MultiByteToWideChar(
@@ -597,10 +534,9 @@ main(
}
if (argc >= 2 && argv[1][0] != '-') {
- //
- // Parent mode: create XDP objects and spawn restricted child.
- //
UINT32 IfIndex = (UINT32)atoi(argv[1]);
+ const CHAR *BpfProgramPath = "xskrestricted_redirect.sys";
+ const CHAR *ProgramName = "xskrestricted_redirect";
UINT32 TimeoutSeconds = 0;
if (IfIndex == 0) {
@@ -608,31 +544,23 @@ main(
return 1;
}
- //
- // Check for optional -TimeoutSeconds argument.
- //
for (INT i = 2; i < argc - 1; i++) {
- if (_stricmp(argv[i], "-TimeoutSeconds") == 0) {
+ if (_stricmp(argv[i], "-BpfProgram") == 0) {
+ BpfProgramPath = argv[i + 1];
+ i++;
+ } else if (_stricmp(argv[i], "-ProgramName") == 0) {
+ ProgramName = argv[i + 1];
+ i++;
+ } else if (_stricmp(argv[i], "-TimeoutSeconds") == 0) {
TimeoutSeconds = (UINT32)atoi(argv[i + 1]);
- break;
+ i++;
}
}
- return RunParent(IfIndex, TimeoutSeconds);
- }
-
- fprintf(
- stderr,
- "Usage:\n"
- " xskrestricted.exe [-TimeoutSeconds ]\n"
- "\n"
- "Creates an AF_XDP socket and XDP program, then spawns a child process\n"
- "with a restricted token and duplicates the handles into it. The child\n"
- "forwards UDP port 1234 traffic back to the sender.\n"
- "\n"
- "Options:\n"
- " -TimeoutSeconds Exit after the specified number of seconds.\n"
- " If 0 or omitted, run indefinitely.\n");
+ return RunParent(IfIndex, BpfProgramPath, ProgramName, TimeoutSeconds);
+ }
+
+ fprintf(stderr, UsageText);
return 1;
}
diff --git a/samples/xskrestricted/xskrestricted.vcxproj b/samples/xskrestricted/xskrestricted.vcxproj
index d546cf2fa..f8f26c21b 100644
--- a/samples/xskrestricted/xskrestricted.vcxproj
+++ b/samples/xskrestricted/xskrestricted.vcxproj
@@ -4,9 +4,10 @@
- {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}
xskrestricted
exe
+ true
diff --git a/src/xdp/ebpfstore.h b/src/xdp/ebpfstore.h
index c48dd7abc..b7dc75508 100644
--- a/src/xdp/ebpfstore.h
+++ b/src/xdp/ebpfstore.h
@@ -20,13 +20,11 @@ static const ebpf_ctx_descriptor_t EbpfXdpContextDescriptor = {
.meta = FIELD_OFFSET(xdp_md_t, data_meta),
};
-#define XDP_EXT_HELPER_FUNCTION_START EBPF_MAX_GENERAL_HELPER_FUNCTION
-
// XDP helper function prototype descriptors.
static const ebpf_helper_function_prototype_t EbpfXdpHelperFunctionPrototype[] = {
{
.header = EBPF_HELPER_FUNCTION_PROTOTYPE_HEADER,
- .helper_id = XDP_EXT_HELPER_FUNCTION_START + 1,
+ .helper_id = BPF_FUNC_xdp_adjust_head,
.name = "bpf_xdp_adjust_head",
.return_type = EBPF_RETURN_TYPE_INTEGER,
.arguments = {
@@ -34,6 +32,17 @@ static const ebpf_helper_function_prototype_t EbpfXdpHelperFunctionPrototype[] =
EBPF_ARGUMENT_TYPE_ANYTHING,
},
},
+ {
+ .header = EBPF_HELPER_FUNCTION_PROTOTYPE_HEADER,
+ .helper_id = BPF_FUNC_redirect_map,
+ .name = "bpf_redirect_map",
+ .return_type = EBPF_RETURN_TYPE_INTEGER,
+ .arguments = {
+ EBPF_ARGUMENT_TYPE_PTR_TO_MAP,
+ EBPF_ARGUMENT_TYPE_ANYTHING,
+ EBPF_ARGUMENT_TYPE_ANYTHING,
+ },
+ },
};
static const ebpf_program_type_descriptor_t EbpfXdpProgramTypeDescriptor = {
diff --git a/src/xdp/ebpfxskmap.c b/src/xdp/ebpfxskmap.c
new file mode 100644
index 000000000..7994ef8f6
--- /dev/null
+++ b/src/xdp/ebpfxskmap.c
@@ -0,0 +1,473 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+//
+// This module implements the BPF_MAP_TYPE_XSKMAP extensible map provider,
+// enabling eBPF programs to redirect packets to AF_XDP sockets via
+// bpf_redirect_map().
+//
+// The XSKMAP relies entirely on the eBPF base array map for storage. The
+// provider callbacks intercept CRUD operations to reference-count XSK handles.
+// The base map stores HANDLE-sized values; the provider validates and transforms
+// them on add/delete.
+//
+
+#include "precomp.h"
+#include "ebpfxskmap.h"
+#include "ebpfxskmap.tmh"
+
+//
+// Per-binding context for the XSKMAP provider. Created when the eBPF runtime
+// attaches as a client; stores a copy of the client's dispatch table.
+//
+typedef struct _XDP_XSKMAP_BINDING_CONTEXT {
+ ebpf_base_map_client_dispatch_table_t ClientDispatch;
+} XDP_XSKMAP_BINDING_CONTEXT;
+
+//
+// Per-map context for an XSKMAP instance.
+//
+typedef struct _XDP_XSKMAP_CONTEXT {
+ ebpf_base_map_client_dispatch_table_t *ClientDispatch;
+} XDP_XSKMAP_CONTEXT;
+
+//
+// XSKMAP provider module ID.
+// {1b8c9ddd-9722-467c-b2b0-4ffa7fb7fe84}
+//
+static const NPI_MODULEID EbpfXskmapProviderModuleId = {
+ .Length = sizeof(NPI_MODULEID),
+ .Type = MIT_GUID,
+ .Guid = {
+ 0x1b8c9ddd,
+ 0x9722,
+ 0x467c,
+ {0xb2, 0xb0, 0x4f, 0xfa, 0x7f, 0xb7, 0xfe, 0x84}
+ },
+};
+
+static EBPF_EXTENSION_PROVIDER *EbpfXskmapProvider;
+
+//
+// Offset within the eBPF map structure where the provider context (map_context)
+// is stored. Set during client attach; used by XdpXskmapFindElement to derive
+// the XDP_XSKMAP_CONTEXT from a raw map pointer.
+//
+// N.B. The eBPF contract guarantees all maps share the same context offset.
+//
+static ULONG64 XdpXskmapContextOffset;
+
+static
+XDP_XSKMAP_BINDING_CONTEXT *
+XdpXskmapGetBindingContext(
+ _In_ void *BindingContext
+ )
+{
+ return (XDP_XSKMAP_BINDING_CONTEXT *)EbpfExtensionClientGetProviderData(
+ (const EBPF_EXTENSION_CLIENT *)BindingContext);
+}
+
+static
+ebpf_result_t
+XdpXskmapProcessCreate(
+ _In_ void *BindingContext,
+ uint32_t MapType,
+ uint32_t KeySize,
+ uint32_t ValueSize,
+ uint32_t MaxEntries,
+ _Out_ uint32_t *ActualValueSize,
+ _Outptr_ void **MapContext
+ )
+{
+ XDP_XSKMAP_CONTEXT *Context = NULL;
+ XDP_XSKMAP_BINDING_CONTEXT *Binding = XdpXskmapGetBindingContext(BindingContext);
+
+ UNREFERENCED_PARAMETER(MaxEntries);
+
+ TraceEnter(
+ TRACE_CORE, "MapType=%u KeySize=%u ValueSize=%u MaxEntries=%u",
+ MapType, KeySize, ValueSize, MaxEntries);
+
+ *ActualValueSize = 0;
+ *MapContext = NULL;
+
+ if (MapType != BPF_MAP_TYPE_XSKMAP) {
+ return EBPF_INVALID_ARGUMENT;
+ }
+
+ if (KeySize != sizeof(UINT32) || ValueSize != sizeof(HANDLE)) {
+ return EBPF_INVALID_ARGUMENT;
+ }
+
+ //
+ // The value stored in the base map is a referenced XSK handle (pointer).
+ //
+ *ActualValueSize = sizeof(HANDLE);
+
+ //
+ // Allocate the per-map context using epoch-protected allocation.
+ //
+ Context = Binding->ClientDispatch.epoch_allocate_with_tag(sizeof(*Context), XDP_POOLTAG_MAP);
+ if (Context == NULL) {
+ return EBPF_NO_MEMORY;
+ }
+
+ Context->ClientDispatch = &Binding->ClientDispatch;
+ *MapContext = Context;
+
+ TraceExitSuccess(TRACE_CORE);
+ return EBPF_SUCCESS;
+}
+
+static
+void
+XdpXskmapProcessDelete(
+ _In_ void *BindingContext,
+ _In_ _Post_invalid_ void *MapContext
+ )
+{
+ XDP_XSKMAP_CONTEXT *Context = (XDP_XSKMAP_CONTEXT *)MapContext;
+
+ UNREFERENCED_PARAMETER(BindingContext);
+
+ TraceEnter(TRACE_CORE, "MapContext=%p", MapContext);
+
+ //
+ // Note: The eBPF runtime calls process_map_delete_element for each entry
+ // before calling process_map_delete, so all XSK handles are already
+ // dereferenced by the time we get here.
+ //
+
+ Context->ClientDispatch->epoch_free(Context);
+
+ TraceExitSuccess(TRACE_CORE);
+}
+
+static
+ebpf_result_t
+XdpXskmapAssociateProgramType(
+ _In_ void *BindingContext,
+ _In_ void *MapContext,
+ _In_ const ebpf_program_type_t *ProgramType
+ )
+{
+ static const ebpf_program_type_t ExpectedProgramType = EBPF_PROGRAM_TYPE_XDP_INIT;
+
+ UNREFERENCED_PARAMETER(BindingContext);
+ UNREFERENCED_PARAMETER(MapContext);
+
+ TraceEnter(TRACE_CORE, "MapContext=%p", MapContext);
+
+ if (!IsEqualGUID(ProgramType, &ExpectedProgramType)) {
+ TraceError(TRACE_CORE, "XSKMAP only supports XDP program type");
+ return EBPF_OPERATION_NOT_SUPPORTED;
+ }
+
+ TraceExitSuccess(TRACE_CORE);
+ return EBPF_SUCCESS;
+}
+
+static
+ebpf_result_t
+XdpXskmapProcessFindElement(
+ _In_ void *BindingContext,
+ _In_ void *MapContext,
+ size_t KeySize,
+ _In_reads_opt_(KeySize) const uint8_t *Key,
+ size_t InValueSize,
+ _In_reads_(InValueSize) const uint8_t *InValue,
+ size_t OutValueSize,
+ _Out_writes_opt_(OutValueSize) uint8_t *OutValue,
+ uint32_t Flags
+ )
+{
+ UNREFERENCED_PARAMETER(BindingContext);
+ UNREFERENCED_PARAMETER(MapContext);
+ UNREFERENCED_PARAMETER(KeySize);
+ UNREFERENCED_PARAMETER(Key);
+ UNREFERENCED_PARAMETER(OutValueSize);
+ UNREFERENCED_PARAMETER(OutValue);
+ DBG_UNREFERENCED_PARAMETER(Flags);
+
+ //
+ // The eBPF runtime blocks BPF program lookups on maps with
+ // updates_original_value set. Assert this invariant.
+ //
+ ASSERT(!(Flags & EBPF_MAP_OPERATION_HELPER));
+
+ //
+ // The base map handles the lookup. The in_value contains the stored
+ // HANDLE. Validate it is non-NULL.
+ //
+ if (InValueSize != sizeof(HANDLE) || InValue == NULL) {
+ return EBPF_INVALID_ARGUMENT;
+ }
+
+ if (*(const HANDLE *)InValue == NULL) {
+ return EBPF_KEY_NOT_FOUND;
+ }
+
+ return EBPF_SUCCESS;
+}
+
+static
+ebpf_result_t
+XdpXskmapProcessAddElement(
+ _In_ void *BindingContext,
+ _In_ void *MapContext,
+ size_t KeySize,
+ _In_reads_opt_(KeySize) const uint8_t *Key,
+ size_t InValueSize,
+ _In_reads_(InValueSize) const uint8_t *InValue,
+ size_t OutValueSize,
+ _Out_writes_opt_(OutValueSize) uint8_t *OutValue,
+ uint32_t Flags
+ )
+{
+ HANDLE NewXskHandle = NULL;
+ NTSTATUS Status;
+
+ UNREFERENCED_PARAMETER(BindingContext);
+ UNREFERENCED_PARAMETER(MapContext);
+ UNREFERENCED_PARAMETER(KeySize);
+ UNREFERENCED_PARAMETER(Key);
+ DBG_UNREFERENCED_PARAMETER(Flags);
+
+ TraceEnter(TRACE_CORE, "MapContext=%p", MapContext);
+
+ //
+ // The eBPF runtime blocks BPF program updates on maps with
+ // updates_original_value set. Assert this invariant.
+ //
+ ASSERT(!(Flags & EBPF_MAP_OPERATION_HELPER));
+
+ if (InValueSize != sizeof(HANDLE) || InValue == NULL) {
+ Status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ if (OutValue == NULL || OutValueSize != sizeof(HANDLE)) {
+ Status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ //
+ // Reference the XSK handle provided by the user. This validates the handle
+ // and increments the reference count.
+ //
+ Status = XskReferenceDatapathHandle(UserMode, InValue, TRUE, &NewXskHandle);
+ if (!NT_SUCCESS(Status)) {
+ goto Exit;
+ }
+
+ //
+ // Write the referenced handle into the output value buffer. The base map
+ // will store this as the entry's value.
+ //
+ *(HANDLE *)OutValue = NewXskHandle;
+ NewXskHandle = NULL;
+ Status = STATUS_SUCCESS;
+
+Exit:
+
+ if (NewXskHandle != NULL) {
+ XskDereferenceDatapathHandle(NewXskHandle);
+ }
+
+ TraceExitStatus(TRACE_CORE);
+
+ return NT_SUCCESS(Status) ? EBPF_SUCCESS : EBPF_INVALID_ARGUMENT;
+}
+
+static
+void
+XdpXskmapProcessDeleteElement(
+ _In_ void *BindingContext,
+ _In_ void *MapContext,
+ size_t KeySize,
+ _In_reads_opt_(KeySize) const uint8_t *Key,
+ size_t ValueSize,
+ _In_reads_(ValueSize) const uint8_t *Value,
+ uint32_t Flags
+ )
+{
+ HANDLE XskHandle;
+
+ UNREFERENCED_PARAMETER(BindingContext);
+ UNREFERENCED_PARAMETER(MapContext);
+ UNREFERENCED_PARAMETER(KeySize);
+ UNREFERENCED_PARAMETER(Key);
+ DBG_UNREFERENCED_PARAMETER(Flags);
+
+ TraceEnter(TRACE_CORE, "MapContext=%p", MapContext);
+
+ //
+ // The eBPF runtime blocks BPF program deletes on maps with
+ // updates_original_value set (except during map cleanup).
+ //
+ ASSERT(!(Flags & EBPF_MAP_OPERATION_HELPER));
+
+ //
+ // The Value parameter contains the entry being removed from the base map.
+ // Dereference the XSK handle stored there.
+ //
+ if (ValueSize == sizeof(HANDLE) && Value != NULL) {
+ XskHandle = *(const HANDLE *)Value;
+ if (XskHandle != NULL) {
+ XskDereferenceDatapathHandle(XskHandle);
+ }
+ }
+
+ TraceExitSuccess(TRACE_CORE);
+}
+
+//
+// Provider dispatch table for XSKMAP operations.
+//
+static ebpf_base_map_provider_dispatch_table_t XdpXskmapProviderDispatchTable = {
+ .header = EBPF_BASE_MAP_PROVIDER_DISPATCH_TABLE_HEADER,
+ .preprocess_map_create = XdpXskmapProcessCreate,
+ .postprocess_map_delete = XdpXskmapProcessDelete,
+ .preprocess_associate_program_type = XdpXskmapAssociateProgramType,
+ .postprocess_map_find_element = XdpXskmapProcessFindElement,
+ .preprocess_map_update_element = XdpXskmapProcessAddElement,
+ .postprocess_map_delete_element = XdpXskmapProcessDeleteElement,
+};
+
+static ebpf_base_map_provider_properties_t XdpXskmapProviderProperties = {
+ .header = EBPF_BASE_MAP_PROVIDER_PROPERTIES_HEADER,
+ .updates_original_value = TRUE,
+};
+
+static ebpf_map_provider_data_t XdpXskmapProviderData = {
+ .header = EBPF_MAP_PROVIDER_DATA_HEADER,
+ .map_type = BPF_MAP_TYPE_XSKMAP,
+ .base_map_type = BPF_MAP_TYPE_HASH,
+ .base_properties = &XdpXskmapProviderProperties,
+ .base_provider_table = &XdpXskmapProviderDispatchTable,
+};
+
+//
+// Client attach callback: create a per-binding context with a copy of the
+// client's dispatch table.
+//
+static
+NTSTATUS
+XdpXskmapOnClientAttach(
+ _In_ const EBPF_EXTENSION_CLIENT *AttachingClient,
+ _In_ const EBPF_EXTENSION_PROVIDER *AttachingProvider
+ )
+{
+ const ebpf_extension_data_t *ClientExtData;
+ const ebpf_map_client_data_t *ClientData;
+ XDP_XSKMAP_BINDING_CONTEXT *Binding;
+
+ UNREFERENCED_PARAMETER(AttachingProvider);
+
+ TraceEnter(TRACE_CORE, "Client=%p", AttachingClient);
+
+ ClientExtData = EbpfExtensionClientGetClientData(AttachingClient);
+ if (ClientExtData == NULL) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ ClientData = (const ebpf_map_client_data_t *)ClientExtData;
+
+ Binding = ExAllocatePoolZero(NonPagedPoolNx, sizeof(*Binding), XDP_POOLTAG_EBPF_NMR);
+ if (Binding == NULL) {
+ return STATUS_NO_MEMORY;
+ }
+
+ RtlCopyMemory(
+ &Binding->ClientDispatch, ClientData->base_client_table,
+ min(sizeof(Binding->ClientDispatch), ClientData->base_client_table->header.total_size));
+
+ WriteULong64NoFence(
+ (volatile UINT64 *)&XdpXskmapContextOffset, ClientData->map_context_offset);
+
+ EbpfExtensionClientSetProviderData(AttachingClient, Binding);
+
+ TraceExitSuccess(TRACE_CORE);
+ return STATUS_SUCCESS;
+}
+
+static
+NTSTATUS
+XdpXskmapOnClientDetach(
+ _In_ const EBPF_EXTENSION_CLIENT *DetachingClient
+ )
+{
+ XDP_XSKMAP_BINDING_CONTEXT *Binding = EbpfExtensionClientGetProviderData(DetachingClient);
+
+ TraceEnter(TRACE_CORE, "Client=%p", DetachingClient);
+
+ if (Binding != NULL) {
+ ExFreePoolWithTag(Binding, XDP_POOLTAG_EBPF_NMR);
+ }
+
+ TraceExitSuccess(TRACE_CORE);
+ return STATUS_SUCCESS;
+}
+
+ebpf_result_t
+XdpXskmapFindElement(
+ _In_ const void *Map,
+ _In_ const VOID *Key,
+ _Outptr_ VOID **Value
+ )
+{
+ XDP_XSKMAP_CONTEXT *Context;
+
+ Context = *(XDP_XSKMAP_CONTEXT **)MAP_CONTEXT(Map, ReadULong64NoFence(&XdpXskmapContextOffset));
+ if (Context == NULL) {
+ return EBPF_OPERATION_NOT_SUPPORTED;
+ }
+
+ return Context->ClientDispatch->find_element_function(Map, Key, (uint8_t **)Value);
+}
+
+NTSTATUS
+XdpXskmapStart(
+ VOID
+ )
+{
+ const EBPF_EXTENSION_PROVIDER_PARAMETERS Parameters = {
+ .ProviderModuleId = &EbpfXskmapProviderModuleId,
+ .ProviderData = &XdpXskmapProviderData,
+ };
+ NTSTATUS Status;
+
+ TraceEnter(TRACE_CORE, "-");
+
+ Status =
+ EbpfExtensionProviderRegister(
+ &EBPF_MAP_INFO_EXTENSION_IID, &Parameters,
+ XdpXskmapOnClientAttach, XdpXskmapOnClientDetach,
+ NULL, &EbpfXskmapProvider);
+ if (!NT_SUCCESS(Status)) {
+ TraceError(TRACE_CORE, "Failed to register XSKMAP provider Status=%!STATUS!", Status);
+ goto Exit;
+ }
+
+Exit:
+
+ TraceExitStatus(TRACE_CORE);
+ return Status;
+}
+
+VOID
+XdpXskmapStop(
+ VOID
+ )
+{
+ TraceEnter(TRACE_CORE, "-");
+
+ if (EbpfXskmapProvider != NULL) {
+ EbpfExtensionProviderUnregister(EbpfXskmapProvider);
+ EbpfXskmapProvider = NULL;
+ }
+
+ TraceExitSuccess(TRACE_CORE);
+}
diff --git a/src/xdp/ebpfxskmap.h b/src/xdp/ebpfxskmap.h
new file mode 100644
index 000000000..da764c191
--- /dev/null
+++ b/src/xdp/ebpfxskmap.h
@@ -0,0 +1,23 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+#pragma once
+
+NTSTATUS
+XdpXskmapStart(
+ VOID
+ );
+
+VOID
+XdpXskmapStop(
+ VOID
+ );
+
+ebpf_result_t
+XdpXskmapFindElement(
+ _In_ const void *Map,
+ _In_ const VOID *Key,
+ _Outptr_ VOID **Value
+ );
diff --git a/src/xdp/precomp.h b/src/xdp/precomp.h
index 2d4c622e6..1e0ad27ee 100644
--- a/src/xdp/precomp.h
+++ b/src/xdp/precomp.h
@@ -97,6 +97,7 @@
#include "rx.h"
#include "tx.h"
#include "xsk.h"
+#include "ebpfxskmap.h"
#include "map.h"
#include "xskmap.h"
diff --git a/src/xdp/program.c b/src/xdp/program.c
index 76f868bb4..f787cce8f 100644
--- a/src/xdp/program.c
+++ b/src/xdp/program.c
@@ -12,6 +12,9 @@
#include "programinspect.h"
#include "program.tmh"
+#define REDIRECT_FALLBACK_MASK 0x3
+#define REDIRECT_VALID_FLAGS_MASK (REDIRECT_FALLBACK_MASK)
+
typedef struct _EBPF_PROG_TEST_RUN_CONTEXT {
char* Data;
SIZE_T DataSize;
@@ -21,6 +24,9 @@ typedef struct _EBPF_XDP_MD {
EBPF_CONTEXT_HEADER;
xdp_md_t Base;
EBPF_PROG_TEST_RUN_CONTEXT* ProgTestRunContext;
+ XDP_INSPECTION_CONTEXT *InspectionContext;
+ VOID *RedirectTarget;
+ XDP_REDIRECT_TARGET_TYPE RedirectTargetType;
} EBPF_XDP_MD;
static __forceinline NTSTATUS EbpfResultToNtStatus(ebpf_result_t Result)
@@ -119,6 +125,7 @@ XdpCreateContext(
xdp_md_t* xdp_context = (xdp_md_t*)context_in;
XdpMd->Base.data_meta = xdp_context->data_meta;
XdpMd->Base.ingress_ifindex = xdp_context->ingress_ifindex;
+ XdpMd->Base.rx_queue_index = xdp_context->rx_queue_index;
}
*Context = &XdpMd->Base;
@@ -180,6 +187,7 @@ XdpDeleteContext(
xdp_md_t* XdpContextOut = (xdp_md_t*)ContextOut;
XdpContextOut->data_meta = XdpMd->Base.data_meta;
XdpContextOut->ingress_ifindex = XdpMd->Base.ingress_ifindex;
+ XdpContextOut->rx_queue_index = XdpMd->Base.rx_queue_index;
*ContextSizeOut = context_size;
} else {
*ContextSizeOut = 0;
@@ -204,6 +212,7 @@ XdpInvokeEbpf(
_In_ XDP_FRAME *Frame,
_In_opt_ XDP_RING *FragmentRing,
_In_opt_ XDP_EXTENSION *FragmentExtension,
+ _In_ UINT32 FrameIndex,
_In_ UINT32 FragmentIndex,
_In_ XDP_EXTENSION *VirtualAddressExtension
)
@@ -219,8 +228,6 @@ XdpInvokeEbpf(
XDP_RX_ACTION RxAction;
UINT32 Result;
- UNREFERENCED_PARAMETER(FragmentIndex);
-
ASSERT((FragmentRing == NULL) || (FragmentExtension != NULL));
//
@@ -246,6 +253,10 @@ XdpInvokeEbpf(
XdpMd.Base.data_end = Va + Buffer->DataLength;
XdpMd.Base.data_meta = 0;
XdpMd.Base.ingress_ifindex = InspectionContext->IfIndex;
+ XdpMd.Base.rx_queue_index = InspectionContext->QueueId;
+ XdpMd.InspectionContext = InspectionContext;
+ XdpMd.RedirectTarget = NULL;
+ XdpMd.ProgTestRunContext = NULL;
ebpf_program_batch_invoke_function_t EbpfInvokeProgram =
EbpfExtensionClientGetProgramDispatch(Client)->ebpf_program_batch_invoke_function;
@@ -269,8 +280,20 @@ XdpInvokeEbpf(
STAT_INC(RxQueueStats, InspectFramesForwarded);
break;
+ case XDP_REDIRECT:
+ if (XdpMd.RedirectTarget != NULL) {
+ XdpRedirect(
+ &InspectionContext->RedirectContext, FrameIndex, FragmentIndex,
+ XdpMd.RedirectTargetType, XdpMd.RedirectTarget);
+ RxAction = XDP_RX_ACTION_DROP;
+ STAT_INC(RxQueueStats, InspectFramesRedirected);
+ } else {
+ RxAction = XDP_RX_ACTION_DROP;
+ STAT_INC(RxQueueStats, InspectFramesDropped);
+ }
+ break;
+
default:
- ASSERT(FALSE);
__fallthrough;
case XDP_DROP:
RxAction = XDP_RX_ACTION_DROP;
@@ -309,7 +332,7 @@ XdpInspectEbpf(
return
XdpInvokeEbpf(
Program->Rules[0].Ebpf.Target, InspectionContext, Frame, FragmentRing,
- FragmentExtension, FragmentIndex, VirtualAddressExtension);
+ FragmentExtension, FrameIndex, FragmentIndex, VirtualAddressExtension);
}
static
@@ -677,8 +700,91 @@ EbpfXdpAdjustHead(
return -1;
}
+static
+intptr_t
+EbpfXdpRedirectMap(
+ _In_ const void *Map,
+ _In_ uint32_t Key,
+ _In_ uint64_t Flags,
+ _In_ uint64_t Reserved4,
+ _In_ uint64_t Reserved5,
+ _In_ void *ProgramContext /* Implicitly forwarded by eBPF as the 6th param. */
+ )
+{
+ intptr_t const FallbackAction = (intptr_t)(Flags & REDIRECT_FALLBACK_MASK);
+ intptr_t ReturnAction = FallbackAction;
+ EBPF_XDP_MD *XdpMd = CONTAINING_RECORD(ProgramContext, EBPF_XDP_MD, Base);
+ BOOLEAN IsProgTestRun = XdpMd->ProgTestRunContext != NULL;
+ XDP_REDIRECT_CONTEXT *RedirectContext = &XdpMd->InspectionContext->RedirectContext;
+ XDP_RX_QUEUE *RxQueue;
+ VOID *Value = NULL;
+ HANDLE Xsk;
+
+ UNREFERENCED_PARAMETER(Reserved4);
+ UNREFERENCED_PARAMETER(Reserved5);
+
+ if (IsProgTestRun) {
+ //
+ // N.B. RxQueue and other nontrivial structures are not present in
+ // eBPF prog_test_run callbacks.
+ //
+ RxQueue = NULL;
+ } else {
+ RxQueue = XdpRxQueueFromRedirectContext(RedirectContext);
+ ASSERT(RxQueue != NULL);
+ }
+
+ if (Flags & ~REDIRECT_VALID_FLAGS_MASK) {
+ //
+ // Unsupported flags are set.
+ //
+ goto Exit;
+ }
+
+ //
+ // Review: is it worth caching the most recent key/value lookup per
+ // inspection batch and skipping the following validation?
+ //
+
+ //
+ // Look up the XSK handle in the map using the eBPF runtime's find_element.
+ // The map provider's process_map_find_element callback will be invoked,
+ // which returns the XSK handle stored at the given key.
+ //
+ if (XdpXskmapFindElement(Map, &Key, &Value) != EBPF_SUCCESS) {
+ if (RxQueue != NULL) {
+ STAT_INC(XdpRxQueueGetStats(RxQueue), EbpfXskMapLookupFailures);
+ }
+ EventWriteEbpfRedirectMapLookupFailure(
+ &MICROSOFT_XDP_PROVIDER, RxQueue, Key, (UINT32)FallbackAction);
+ goto Exit;
+ }
+
+ ASSERT(Value != NULL);
+ Xsk = *(HANDLE *)Value;
+
+ if (!IsProgTestRun && !XskCanRedirect(Xsk, RxQueue)) {
+ if (RxQueue != NULL) {
+ STAT_INC(XdpRxQueueGetStats(RxQueue), EbpfXskMapRedirectFailures);
+ }
+ EventWriteEbpfRedirectMapRedirectFailure(
+ &MICROSOFT_XDP_PROVIDER, RxQueue, Key, Xsk, (UINT32)FallbackAction);
+ goto Exit;
+ }
+
+ XdpMd->RedirectTarget = Xsk;
+ XdpMd->RedirectTargetType = XDP_REDIRECT_TARGET_TYPE_XSK;
+ ReturnAction = XDP_REDIRECT;
+ EventWriteEbpfRedirectMapSuccess(&MICROSOFT_XDP_PROVIDER, RxQueue, Key, Xsk);
+
+Exit:
+
+ return ReturnAction;
+}
+
static const VOID *EbpfXdpHelperFunctions[] = {
(VOID *)EbpfXdpAdjustHead,
+ (VOID *)EbpfXdpRedirectMap,
};
static const ebpf_helper_function_addresses_t XdpHelperFunctionAddresses = {
@@ -1848,6 +1954,11 @@ XdpProgramStart(
if (!NT_SUCCESS(Status)) {
goto Exit;
}
+
+ Status = XdpXskmapStart();
+ if (!NT_SUCCESS(Status)) {
+ goto Exit;
+ }
}
Status = STATUS_SUCCESS;
@@ -1866,6 +1977,8 @@ XdpProgramStop(
{
TraceEnter(TRACE_CORE, "-");
+ XdpXskmapStop();
+
if (EbpfXdpProgramHookProvider != NULL) {
EbpfExtensionProviderUnregister(EbpfXdpProgramHookProvider);
EbpfXdpProgramHookProvider = NULL;
diff --git a/src/xdp/program.h b/src/xdp/program.h
index 2284da656..92ff4243f 100644
--- a/src/xdp/program.h
+++ b/src/xdp/program.h
@@ -16,6 +16,7 @@ typedef struct _XDP_INSPECTION_CONTEXT {
XDP_INSPECTION_EBPF_CONTEXT EbpfContext;
XDP_REDIRECT_CONTEXT RedirectContext;
ULONG IfIndex;
+ UINT32 QueueId;
LOCK_STATE_EX MapLockState;
} XDP_INSPECTION_CONTEXT;
diff --git a/src/xdp/rx.c b/src/xdp/rx.c
index 008fa01a0..8bb428710 100644
--- a/src/xdp/rx.c
+++ b/src/xdp/rx.c
@@ -1213,6 +1213,7 @@ XdpRxQueueCreate(
RxQueue->Binding = Binding;
RxQueue->Key = Key;
RxQueue->InspectionContext.IfIndex = XdpIfGetIfIndex(Binding);
+ RxQueue->InspectionContext.QueueId = QueueId;
XdpInitializeQueueInfo(&RxQueue->QueueInfo, XDP_QUEUE_TYPE_DEFAULT_RSS, QueueId);
XdbgInitializeQueueEc(RxQueue);
diff --git a/src/xdp/xdp.vcxproj b/src/xdp/xdp.vcxproj
index 12b9f98e1..23961b4ce 100644
--- a/src/xdp/xdp.vcxproj
+++ b/src/xdp/xdp.vcxproj
@@ -15,6 +15,7 @@
+
diff --git a/src/xdp/xsk.c b/src/xdp/xsk.c
index 83b303d22..4e63a2a1e 100644
--- a/src/xdp/xsk.c
+++ b/src/xdp/xsk.c
@@ -1971,6 +1971,17 @@ XskCanBypass(
return TRUE;
}
+BOOLEAN
+XskCanRedirect(
+ _In_ HANDLE XskHandle,
+ _In_ XDP_RX_QUEUE *RxQueue
+ )
+{
+ XSK *Xsk = (XSK *)XskHandle;
+
+ return (Xsk->State == XskActive) && (Xsk->Rx.Xdp.Queue == RxQueue);
+}
+
static
_IRQL_requires_max_(PASSIVE_LEVEL)
_IRQL_requires_same_
diff --git a/src/xdp/xsk.h b/src/xdp/xsk.h
index 20a5429ac..e7bb80189 100644
--- a/src/xdp/xsk.h
+++ b/src/xdp/xsk.h
@@ -47,6 +47,12 @@ XskCanBypass(
_In_ XDP_RX_QUEUE *RxQueue
);
+BOOLEAN
+XskCanRedirect(
+ _In_ HANDLE XskHandle,
+ _In_ XDP_RX_QUEUE *RxQueue
+ );
+
VOID
XskDereferenceDatapathHandle(
_In_ HANDLE XskHandle
diff --git a/src/xdpetw/xdpetw.man b/src/xdpetw/xdpetw.man
index d3bc27bc1..bf2d77ec6 100644
--- a/src/xdpetw/xdpetw.man
+++ b/src/xdpetw/xdpetw.man
@@ -315,6 +315,57 @@
outType="win:HexInt32"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
rx_queue_index;
+ return bpf_redirect_map(&xsk_map, index, XDP_PASS);
+}
diff --git a/test/bpf/xsk_redirect_fallback.c b/test/bpf/xsk_redirect_fallback.c
new file mode 100644
index 000000000..3248206ab
--- /dev/null
+++ b/test/bpf/xsk_redirect_fallback.c
@@ -0,0 +1,45 @@
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+
+#include "bpf_endian.h"
+#include "bpf_helpers.h"
+#include "xdp/ebpfhook.h"
+
+struct
+{
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __type(key, uint32_t);
+ __type(value, void *);
+ __uint(max_entries, 64);
+} xsk_map SEC(".maps");
+
+//
+// A single-element array map that controls the fallback action passed to
+// bpf_redirect_map. The test populates index 0 with the desired xdp_action
+// value (XDP_PASS, XDP_DROP, or XDP_TX).
+//
+struct
+{
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, uint32_t);
+ __type(value, uint32_t);
+ __uint(max_entries, 1);
+} fallback_map SEC(".maps");
+
+SEC("xdp/xsk_redirect_fallback")
+int
+xsk_redirect_fallback(xdp_md_t *ctx)
+{
+ uint32_t index = ctx->rx_queue_index;
+ uint32_t zero = 0;
+ uint64_t fallback = XDP_PASS;
+
+ uint32_t *fb = bpf_map_lookup_elem(&fallback_map, &zero);
+ if (fb != NULL) {
+ fallback = *fb;
+ }
+
+ return bpf_redirect_map(&xsk_map, index, fallback);
+}
diff --git a/test/common/inc/testframeworkapi.h b/test/common/inc/testframeworkapi.h
index c41d033ff..e064b17c8 100644
--- a/test/common/inc/testframeworkapi.h
+++ b/test/common/inc/testframeworkapi.h
@@ -15,6 +15,15 @@
VOID
StopTest();
+//
+// Marks the current test as skipped for the given reason and stops executing
+// the remainder of the test. Use via the TEST_SKIP macro.
+//
+VOID
+SkipTest(
+ _In_z_ PCWSTR Reason
+ );
+
VOID
LogTestFailure(
_In_z_ PCWSTR File,
diff --git a/test/common/inc/xdptest.h b/test/common/inc/xdptest.h
index e3fb666ff..734cdbd41 100644
--- a/test/common/inc/xdptest.h
+++ b/test/common/inc/xdptest.h
@@ -19,6 +19,11 @@
#define TEST_WARNING(Format, ...) LogTestWarning(L"" __FILE__, L"" __FUNCTION__, __LINE__, L"" Format, ##__VA_ARGS__)
+#define TEST_SKIP(Reason) { \
+ SkipTest(L"" Reason); \
+ return; \
+}
+
#define TEST_EQUAL(expected, condition) { \
if ((condition) != (expected)) \
{ \
diff --git a/test/functional/lib/tests.cpp b/test/functional/lib/tests.cpp
index 1dd0d1b6a..588c1df76 100644
--- a/test/functional/lib/tests.cpp
+++ b/test/functional/lib/tests.cpp
@@ -1996,19 +1996,19 @@ static
HRESULT
LwfRxGetFrame(
_In_ const unique_fnlwf_handle &Handle,
- _In_ UINT32 Index,
+ _In_ UINT32 FrameIndex,
_Inout_ UINT32 *FrameBufferLength,
_Out_opt_ DATA_FRAME *Frame
)
{
- return FnLwfRxGetFrame(Handle.get(), Index, FrameBufferLength, Frame);
+ return FnLwfRxGetFrame(Handle.get(), FrameIndex, FrameBufferLength, Frame);
}
static
unique_malloc_ptr
LwfRxAllocateAndGetFrame(
_In_ const unique_fnlwf_handle &Handle,
- _In_ UINT32 Index
+ _In_ UINT32 FrameIndex
)
{
unique_malloc_ptr FrameBuffer;
@@ -2020,7 +2020,7 @@ LwfRxAllocateAndGetFrame(
// Poll FNLWF for RX: the driver doesn't support overlapped IO.
//
do {
- Result = LwfRxGetFrame(Handle, Index, &FrameLength, NULL);
+ Result = LwfRxGetFrame(Handle, FrameIndex, &FrameLength, NULL);
if (Result != HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) {
break;
}
@@ -2031,7 +2031,7 @@ LwfRxAllocateAndGetFrame(
FrameBuffer.reset((DATA_FRAME *)AllocMem(FrameLength));
TEST_NOT_NULL(FrameBuffer.get());
- TEST_HRESULT(LwfRxGetFrame(Handle, Index, &FrameLength, FrameBuffer.get()));
+ TEST_HRESULT(LwfRxGetFrame(Handle, FrameIndex, &FrameLength, FrameBuffer.get()));
return FrameBuffer;
}
@@ -2040,7 +2040,7 @@ static
VOID
LwfRxDequeueFrame(
_In_ const unique_fnlwf_handle &Handle,
- _In_ UINT32 Index
+ _In_ UINT32 FrameIndex
)
{
HRESULT Result;
@@ -2050,7 +2050,7 @@ LwfRxDequeueFrame(
// Poll FNLWF for RX: the driver doesn't support overlapped IO.
//
do {
- Result = FnLwfRxDequeueFrame(Handle.get(), Index);
+ Result = FnLwfRxDequeueFrame(Handle.get(), FrameIndex);
} while (!Watchdog.IsExpired() && Result == HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
TEST_HRESULT(Result);
@@ -6139,7 +6139,7 @@ GenericRxEbpfDrop()
UINT32 FrameLength = 0;
TEST_EQUAL(
HRESULT_FROM_WIN32(ERROR_NOT_FOUND),
- LwfRxGetFrame(FnLwf, If.GetQueueId(), &FrameLength, NULL));
+ LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL));
}
VOID
@@ -6163,8 +6163,8 @@ GenericRxEbpfPass()
TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
MpRxFlush(GenericMp);
- LwfRxAllocateAndGetFrame(FnLwf, If.GetQueueId());
- LwfRxDequeueFrame(FnLwf, If.GetQueueId());
+ LwfRxAllocateAndGetFrame(FnLwf, 0);
+ LwfRxDequeueFrame(FnLwf, 0);
LwfRxFlush(FnLwf);
}
@@ -6231,8 +6231,8 @@ GenericRxEbpfPayload()
TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
MpRxFlush(GenericMp);
- LwfRxAllocateAndGetFrame(FnLwf, If.GetQueueId());
- LwfRxDequeueFrame(FnLwf, If.GetQueueId());
+ LwfRxAllocateAndGetFrame(FnLwf, 0);
+ LwfRxDequeueFrame(FnLwf, 0);
LwfRxFlush(FnLwf);
}
@@ -6334,7 +6334,7 @@ GenericRxEbpfIfIndex()
// Packet should be dropped.
TEST_EQUAL(
HRESULT_FROM_WIN32(ERROR_NOT_FOUND),
- LwfRxGetFrame(FnLwf, If.GetQueueId(), &FrameLength, NULL));
+ LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL));
// Validate that the dropped_packet_map contains a non-0 entry for the IfIndex.
fd_t dropped_packet_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "dropped_packet_map");
@@ -6356,7 +6356,7 @@ GenericRxEbpfIfIndex()
// Packet should not be dropped.
TEST_EQUAL(
HRESULT_FROM_WIN32(ERROR_MORE_DATA),
- LwfRxGetFrame(FnLwf, If.GetQueueId(), &FrameLength, NULL));
+ LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL));
}
VOID
@@ -6415,6 +6415,556 @@ GenericRxEbpfUnload()
TEST_HRESULT(TryStartService(XDP_SERVICE_NAME));
}
+//
+// The eBPF XSKMAP (bpf_redirect_map to AF_XDP sockets) requires the eBPF for
+// Windows base-map-provider, which is available in eBPF for Windows v1.3.0 and
+// later. functional.ps1 provides the installed runtime version via the
+// XDP_EBPF_RUNTIME_VERSION environment variable; when it is absent the
+// build-time default (>= 1.3.0) is assumed.
+//
+static
+bool
+EbpfXskmapSupported()
+{
+ CHAR VersionBuffer[64];
+ size_t Length = 0;
+
+ if (getenv_s(
+ &Length, VersionBuffer, sizeof(VersionBuffer),
+ "XDP_EBPF_RUNTIME_VERSION") != 0 || Length == 0) {
+ return true;
+ }
+
+ INT Major = 0;
+ INT Minor = 0;
+ if (sscanf_s(VersionBuffer, "%d.%d", &Major, &Minor) < 2) {
+ return true;
+ }
+
+ return (Major > 1) || (Major == 1 && Minor >= 3);
+}
+
+#define SKIP_IF_EBPF_XSKMAP_UNSUPPORTED() \
+ if (!EbpfXskmapSupported()) { \
+ TEST_SKIP("eBPF XSKMAP requires eBPF for Windows v1.3.0 or later"); \
+ }
+
+VOID
+GenericRxEbpfXskRedirect()
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+ unique_fnmp_handle GenericMp;
+ const UCHAR Payload[] = "GenericRxEbpfXskRedirect";
+
+ //
+ // Create and activate an XSK socket for RX on the test interface.
+ //
+ auto Xsk =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+
+ //
+ // Attach the eBPF XSK redirect program.
+ //
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(If, "\\bpf\\xsk_redirect.sys", "xsk_redirect");
+
+ //
+ // Get the xsk_map FD and populate it with the XSK handle at the queue index.
+ //
+ fd_t xsk_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "xsk_map");
+ TEST_NOT_EQUAL(xsk_map_fd, ebpf_fd_invalid);
+
+ UINT32 QueueId = If.GetQueueId();
+ HANDLE XskHandle = Xsk.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &QueueId, &XskHandle, BPF_ANY));
+
+ GenericMp = MpOpenGeneric(If.GetIfIndex());
+
+ //
+ // Produce one XSK fill descriptor.
+ //
+ SocketProduceRxFill(&Xsk, 1);
+
+ //
+ // Build one frame and indicate it.
+ //
+ RX_FRAME Frame;
+ RxInitializeFrame(&Frame, If.GetQueueId(), Payload, sizeof(Payload));
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ TEST_HRESULT(TryMpRxFlush(GenericMp));
+
+ //
+ // Wait for the frame to arrive at XSK.
+ //
+ UINT32 ConsumerIndex = SocketConsumerReserve(&Xsk.Rings.Rx, 1);
+
+ //
+ // Verify the frame was redirected to XSK.
+ //
+ TEST_EQUAL(1, XskRingConsumerReserve(&Xsk.Rings.Rx, MAXUINT32, &ConsumerIndex));
+ auto RxDesc = SocketGetAndFreeRxDesc(&Xsk, ConsumerIndex);
+ TEST_EQUAL(sizeof(Payload), RxDesc->Length);
+ TEST_TRUE(
+ RtlEqualMemory(
+ Xsk.Umem.Buffer.get() + RxDesc->Address.BaseAddress + RxDesc->Address.Offset,
+ Payload,
+ sizeof(Payload)));
+}
+
+VOID
+GenericRxEbpfXskRedirectFallback()
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+ unique_fnmp_handle GenericMp;
+ unique_fnlwf_handle FnLwf;
+ const UCHAR Payload[] = "GenericRxEbpfXskRedirectFallback";
+
+ //
+ // Attach the eBPF XSK redirect program without populating the map.
+ // The fallback action is XDP_PASS, so packets should pass through.
+ //
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(If, "\\bpf\\xsk_redirect.sys", "xsk_redirect");
+
+ GenericMp = MpOpenGeneric(If.GetIfIndex());
+ FnLwf = LwfOpenDefault(If.GetIfIndex());
+
+ CxPlatVector Mask(sizeof(Payload), 0xFF);
+ auto LwfFilter = LwfRxFilter(FnLwf, Payload, Mask.data(), sizeof(Payload));
+
+ RX_FRAME Frame;
+ RxInitializeFrame(&Frame, If.GetQueueId(), Payload, sizeof(Payload));
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ MpRxFlush(GenericMp);
+
+ //
+ // Packet should pass through since the map entry is empty and
+ // the fallback action is XDP_PASS.
+ //
+ CxPlatSleep(TEST_TIMEOUT_ASYNC_MS);
+
+ UINT32 FrameLength = 0;
+ TEST_EQUAL(
+ HRESULT_FROM_WIN32(ERROR_MORE_DATA),
+ LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL));
+}
+
+VOID
+GenericRxEbpfXskRedirectReplace()
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+ unique_fnmp_handle GenericMp;
+ const UCHAR Payload[] = "GenericRxEbpfXskRedirectReplace";
+
+ //
+ // Create two XSK sockets. We'll redirect to the first, then replace the
+ // map entry with the second and verify the redirect target changes.
+ //
+ auto Xsk1 =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+ auto Xsk2 =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(If, "\\bpf\\xsk_redirect.sys", "xsk_redirect");
+
+ fd_t xsk_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "xsk_map");
+ TEST_NOT_EQUAL(xsk_map_fd, ebpf_fd_invalid);
+
+ GenericMp = MpOpenGeneric(If.GetIfIndex());
+
+ //
+ // Set the map entry to the first XSK.
+ //
+ UINT32 QueueId = If.GetQueueId();
+ HANDLE XskHandle1 = Xsk1.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &QueueId, &XskHandle1, BPF_ANY));
+
+ SocketProduceRxFill(&Xsk1, 1);
+
+ RX_FRAME Frame;
+ RxInitializeFrame(&Frame, If.GetQueueId(), Payload, sizeof(Payload));
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ TEST_HRESULT(TryMpRxFlush(GenericMp));
+
+ UINT32 ConsumerIndex = SocketConsumerReserve(&Xsk1.Rings.Rx, 1);
+ TEST_EQUAL(1, XskRingConsumerReserve(&Xsk1.Rings.Rx, MAXUINT32, &ConsumerIndex));
+ auto RxDesc = SocketGetAndFreeRxDesc(&Xsk1, ConsumerIndex);
+ TEST_EQUAL(sizeof(Payload), RxDesc->Length);
+
+ //
+ // Replace the map entry with the second XSK.
+ //
+ HANDLE XskHandle2 = Xsk2.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &QueueId, &XskHandle2, BPF_ANY));
+
+ SocketProduceRxFill(&Xsk2, 1);
+
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ TEST_HRESULT(TryMpRxFlush(GenericMp));
+
+ ConsumerIndex = SocketConsumerReserve(&Xsk2.Rings.Rx, 1);
+ TEST_EQUAL(1, XskRingConsumerReserve(&Xsk2.Rings.Rx, MAXUINT32, &ConsumerIndex));
+ RxDesc = SocketGetAndFreeRxDesc(&Xsk2, ConsumerIndex);
+ TEST_EQUAL(sizeof(Payload), RxDesc->Length);
+}
+
+VOID
+GenericRxEbpfXskRedirectDelete()
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+ unique_fnmp_handle GenericMp;
+ unique_fnlwf_handle FnLwf;
+ const UCHAR Payload[] = "GenericRxEbpfXskRedirectDelete";
+
+ auto Xsk =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(If, "\\bpf\\xsk_redirect.sys", "xsk_redirect");
+
+ fd_t xsk_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "xsk_map");
+ TEST_NOT_EQUAL(xsk_map_fd, ebpf_fd_invalid);
+
+ GenericMp = MpOpenGeneric(If.GetIfIndex());
+ FnLwf = LwfOpenDefault(If.GetIfIndex());
+
+ //
+ // Populate the map entry, then delete it.
+ //
+ UINT32 QueueId = If.GetQueueId();
+ HANDLE XskHandle = Xsk.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &QueueId, &XskHandle, BPF_ANY));
+ TEST_EQUAL(0, bpf_map_delete_elem(xsk_map_fd, &QueueId));
+
+ //
+ // After deletion, bpf_redirect_map should fall back to XDP_PASS.
+ //
+ CxPlatVector Mask(sizeof(Payload), 0xFF);
+ auto LwfFilter = LwfRxFilter(FnLwf, Payload, Mask.data(), sizeof(Payload));
+
+ RX_FRAME Frame;
+ RxInitializeFrame(&Frame, If.GetQueueId(), Payload, sizeof(Payload));
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ MpRxFlush(GenericMp);
+
+ CxPlatSleep(TEST_TIMEOUT_ASYNC_MS);
+
+ UINT32 FrameLength = 0;
+ TEST_EQUAL(
+ HRESULT_FROM_WIN32(ERROR_MORE_DATA),
+ LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL));
+}
+
+VOID
+GenericRxEbpfXskMapControlPath()
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+
+ auto Xsk =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(If, "\\bpf\\xsk_redirect.sys", "xsk_redirect");
+
+ fd_t xsk_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "xsk_map");
+ TEST_NOT_EQUAL(xsk_map_fd, ebpf_fd_invalid);
+
+ //
+ // Verify lookup on a non-existent key returns an error.
+ //
+ UINT32 QueueId = If.GetQueueId();
+ HANDLE LookupValue = NULL;
+ TEST_NOT_EQUAL(0, bpf_map_lookup_elem(xsk_map_fd, &QueueId, &LookupValue));
+
+ //
+ // Insert an entry and verify lookup succeeds.
+ //
+ HANDLE XskHandle = Xsk.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &QueueId, &XskHandle, BPF_ANY));
+ TEST_EQUAL(0, bpf_map_lookup_elem(xsk_map_fd, &QueueId, &LookupValue));
+
+ //
+ // Delete the entry and verify lookup fails again.
+ //
+ TEST_EQUAL(0, bpf_map_delete_elem(xsk_map_fd, &QueueId));
+ TEST_NOT_EQUAL(0, bpf_map_lookup_elem(xsk_map_fd, &QueueId, &LookupValue));
+
+ //
+ // Verify deleting a non-existent key returns an error.
+ //
+ TEST_NOT_EQUAL(0, bpf_map_delete_elem(xsk_map_fd, &QueueId));
+
+ //
+ // Verify that BPF programs cannot perform CRUD operations on the XSKMAP.
+ // The eBPF runtime blocks these at runtime because updates_original_value
+ // is TRUE. Load a BPF program that attempts bpf_map_lookup_elem,
+ // bpf_map_update_elem, and bpf_map_delete_elem on the XSKMAP, run it via
+ // bpf_prog_test_run, and verify each operation failed.
+ //
+ {
+ CHAR BpfPath[MAX_PATH];
+ TEST_HRESULT(GetCurrentBinaryPath(BpfPath, RTL_NUMBER_OF(BpfPath)));
+ TEST_EQUAL(0, strcat_s(BpfPath, sizeof(BpfPath), "\\bpf\\xsk_map_lookup.sys"));
+
+ unique_bpf_object CrudProgram(bpf_object__open(BpfPath));
+ TEST_NOT_EQUAL(CrudProgram.get(), nullptr);
+ TEST_EQUAL(0, bpf_object__load(CrudProgram.get()));
+
+ bpf_program *CrudBpfProg =
+ bpf_object__find_program_by_name(CrudProgram.get(), "xsk_map_crud");
+ TEST_NOT_EQUAL(CrudBpfProg, nullptr);
+
+ fd_t CrudProgFd = bpf_program__fd(CrudBpfProg);
+ TEST_NOT_EQUAL(CrudProgFd, ebpf_fd_invalid);
+
+ UCHAR DummyPacket[64] = {0};
+ UCHAR DummyPacketOut[64] = {0};
+ xdp_md_t ContextIn = {0};
+ xdp_md_t ContextOut = {0};
+ ContextIn.ingress_ifindex = If.GetIfIndex();
+ ContextIn.rx_queue_index = If.GetQueueId();
+
+ bpf_test_run_opts Opts = {};
+ Opts.data_in = DummyPacket;
+ Opts.data_size_in = sizeof(DummyPacket);
+ Opts.data_out = DummyPacketOut;
+ Opts.data_size_out = sizeof(DummyPacketOut);
+ Opts.ctx_in = &ContextIn;
+ Opts.ctx_size_in = sizeof(ContextIn);
+ Opts.ctx_out = &ContextOut;
+ Opts.ctx_size_out = sizeof(ContextOut);
+
+ TEST_EQUAL(0, bpf_prog_test_run_opts(CrudProgFd, &Opts));
+ TEST_EQUAL(Opts.retval, (UINT32)XDP_PASS);
+
+ //
+ // Read back the results from the results_map.
+ //
+ fd_t ResultsMapFd =
+ bpf_object__find_map_fd_by_name(CrudProgram.get(), "results_map");
+ TEST_NOT_EQUAL(ResultsMapFd, ebpf_fd_invalid);
+
+ int64_t ResultValue = 0;
+ UINT32 ResultKey;
+
+ //
+ // results_map[0]: bpf_map_lookup_elem should have returned NULL (1).
+ //
+ ResultKey = 0;
+ TEST_EQUAL(0, bpf_map_lookup_elem(ResultsMapFd, &ResultKey, &ResultValue));
+ TEST_EQUAL(1, ResultValue);
+
+ //
+ // results_map[1]: bpf_map_update_elem should have returned non-zero.
+ //
+ ResultKey = 1;
+ TEST_EQUAL(0, bpf_map_lookup_elem(ResultsMapFd, &ResultKey, &ResultValue));
+ TEST_NOT_EQUAL(0, ResultValue);
+
+ //
+ // results_map[2]: bpf_map_delete_elem should have returned non-zero.
+ //
+ ResultKey = 2;
+ TEST_EQUAL(0, bpf_map_lookup_elem(ResultsMapFd, &ResultKey, &ResultValue));
+ TEST_NOT_EQUAL(0, ResultValue);
+ }
+
+ //
+ // Use bpf_prog_test_run to invoke the redirect program without a map entry
+ // and verify it returns the fallback action (XDP_PASS).
+ //
+ {
+ fd_t ProgFd = bpf_program__fd(
+ bpf_object__find_program_by_name(BpfProgram.get(), "xsk_redirect"));
+ TEST_NOT_EQUAL(ProgFd, ebpf_fd_invalid);
+
+ UCHAR DummyPacket[64] = {0};
+ UCHAR DummyPacketOut[64] = {0};
+ xdp_md_t ContextIn = {0};
+ xdp_md_t ContextOut = {0};
+ ContextIn.ingress_ifindex = If.GetIfIndex();
+ ContextIn.rx_queue_index = If.GetQueueId();
+
+ bpf_test_run_opts Opts = {};
+ Opts.data_in = DummyPacket;
+ Opts.data_size_in = sizeof(DummyPacket);
+ Opts.data_out = DummyPacketOut;
+ Opts.data_size_out = sizeof(DummyPacketOut);
+ Opts.ctx_in = &ContextIn;
+ Opts.ctx_size_in = sizeof(ContextIn);
+ Opts.ctx_out = &ContextOut;
+ Opts.ctx_size_out = sizeof(ContextOut);
+
+ TEST_EQUAL(0, bpf_prog_test_run_opts(ProgFd, &Opts));
+ TEST_EQUAL(Opts.retval, (UINT32)XDP_PASS);
+ }
+}
+
+static
+VOID
+GenericRxEbpfXskRedirectFallbackHelper(
+ _In_ BOOLEAN CloseSocket,
+ _In_ BOOLEAN QueueMismatch
+ )
+{
+ SKIP_IF_EBPF_XSKMAP_UNSUPPORTED();
+ auto If = FnMpIf;
+
+ //
+ // Test each possible fallback action (XDP_PASS, XDP_DROP, XDP_TX) when the
+ // eBPF redirect to an XSK fails. The redirect can fail because the XSK was
+ // closed (stale map entry) or because the XSK is bound to a different queue
+ // than the current receive queue.
+ //
+ struct {
+ UINT32 FallbackAction;
+ BOOLEAN ExpectPass;
+ BOOLEAN ExpectTx;
+ } TestCases[] = {
+ { XDP_PASS, TRUE, FALSE },
+ { XDP_DROP, FALSE, FALSE },
+ { XDP_TX, FALSE, TRUE },
+ };
+
+ for (UINT32 tc = 0; tc < RTL_NUMBER_OF(TestCases); tc++) {
+ unique_fnmp_handle GenericMp;
+ unique_fnlwf_handle FnLwf;
+ UCHAR Payload[] = "GenericRxEbpfXskRedirectFallbackHelper";
+ UINT32 PayloadLength = sizeof(Payload);
+
+ unique_xdp_program BpfProgram =
+ AttachEbpfXdpProgram(
+ If, "\\bpf\\xsk_redirect_fallback.sys", "xsk_redirect_fallback");
+
+ //
+ // Set the fallback action in the fallback_map.
+ //
+ fd_t fallback_map_fd =
+ bpf_object__find_map_fd_by_name(BpfProgram.get(), "fallback_map");
+ TEST_NOT_EQUAL(fallback_map_fd, ebpf_fd_invalid);
+
+ UINT32 Zero = 0;
+ UINT32 FallbackAction = TestCases[tc].FallbackAction;
+ TEST_EQUAL(0, bpf_map_update_elem(fallback_map_fd, &Zero, &FallbackAction, BPF_ANY));
+
+ fd_t xsk_map_fd = bpf_object__find_map_fd_by_name(BpfProgram.get(), "xsk_map");
+ TEST_NOT_EQUAL(xsk_map_fd, ebpf_fd_invalid);
+
+ //
+ // The map key and indication queue differ by scenario:
+ // - CloseSocket: XSK bound to queue 0, map key = 0, indicate on queue 0.
+ // - QueueMismatch: XSK bound to queue 0, map key = 1, indicate on queue 1.
+ //
+ UINT32 MapKey = QueueMismatch ? If.GetQueueId() + 1 : If.GetQueueId();
+ UINT32 IndicateQueueId = MapKey;
+
+ MY_SOCKET Xsk;
+ {
+ auto XskTemp =
+ CreateAndActivateSocket(
+ If.GetIfIndex(), If.GetQueueId(), TRUE, FALSE, XDP_GENERIC);
+
+ HANDLE XskHandle = XskTemp.Handle.get();
+ TEST_EQUAL(0, bpf_map_update_elem(xsk_map_fd, &MapKey, &XskHandle, BPF_ANY));
+
+ if (CloseSocket) {
+ //
+ // XSK destroyed at end of scope; map entry becomes stale.
+ //
+ } else {
+ SocketProduceRxFill(&XskTemp, 1);
+ Xsk = std::move(XskTemp);
+ }
+ }
+
+ GenericMp = MpOpenGeneric(If.GetIfIndex());
+ FnLwf = LwfOpenDefault(If.GetIfIndex());
+
+ CxPlatVector Mask(PayloadLength, 0xFF);
+ auto LwfFilter = LwfRxFilter(FnLwf, Payload, Mask.data(), PayloadLength);
+
+ //
+ // For XDP_TX, set a TX filter so we can verify the packet is bounced
+ // back out the miniport.
+ //
+ unique_fnmp_filter_handle MpFilter;
+ if (TestCases[tc].ExpectTx) {
+ MpFilter = MpTxFilter(GenericMp, Payload, Mask.data(), PayloadLength);
+ }
+
+ RX_FRAME Frame;
+ RxInitializeFrame(&Frame, IndicateQueueId, Payload, PayloadLength);
+ TEST_HRESULT(MpRxEnqueueFrame(GenericMp, &Frame));
+ MpRxFlush(GenericMp);
+
+ CxPlatSleep(TEST_TIMEOUT_ASYNC_MS);
+
+ //
+ // If the XSK is still open, verify no packet was delivered to it.
+ //
+ if (!CloseSocket) {
+ UINT32 ConsumerIndex;
+ TEST_EQUAL(0, XskRingConsumerReserve(&Xsk.Rings.Rx, MAXUINT32, &ConsumerIndex));
+ }
+
+ UINT32 FrameLength = 0;
+ HRESULT LwfResult = LwfRxGetFrame(FnLwf, 0, &FrameLength, NULL);
+
+ if (TestCases[tc].ExpectPass) {
+ //
+ // Fallback is XDP_PASS: the packet should reach the LWF.
+ //
+ TEST_EQUAL(HRESULT_FROM_WIN32(ERROR_MORE_DATA), LwfResult);
+ } else {
+ //
+ // Fallback is XDP_DROP or XDP_TX: the packet should not reach
+ // the LWF.
+ //
+ TEST_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), LwfResult);
+ }
+
+ if (TestCases[tc].ExpectTx) {
+ //
+ // Fallback is XDP_TX: verify the packet was bounced back out
+ // the miniport TX path.
+ //
+ auto TxFrame = MpTxAllocateAndGetFrame(GenericMp, 0);
+ MpTxDequeueFrame(GenericMp, 0);
+ MpTxFlush(GenericMp);
+ } else {
+ //
+ // Fallback is XDP_PASS or XDP_DROP: verify no TX occurred.
+ //
+ MpTxVerifyNoFrame(GenericMp, 0);
+ }
+
+ bpf_map_delete_elem(xsk_map_fd, &MapKey);
+ }
+}
+
+VOID
+GenericRxEbpfXskRedirectCloseSocket()
+{
+ GenericRxEbpfXskRedirectFallbackHelper(TRUE, FALSE);
+}
+
+VOID
+GenericRxEbpfXskRedirectQueueMismatch()
+{
+ GenericRxEbpfXskRedirectFallbackHelper(FALSE, TRUE);
+}
+
VOID
GenericTxToRxInject()
{
diff --git a/test/functional/lib/tests.h b/test/functional/lib/tests.h
index 8b1e4a855..d8f72db24 100644
--- a/test/functional/lib/tests.h
+++ b/test/functional/lib/tests.h
@@ -187,6 +187,27 @@ GenericRxEbpfFragments();
VOID
GenericRxEbpfUnload();
+VOID
+GenericRxEbpfXskRedirect();
+
+VOID
+GenericRxEbpfXskRedirectFallback();
+
+VOID
+GenericRxEbpfXskRedirectReplace();
+
+VOID
+GenericRxEbpfXskRedirectDelete();
+
+VOID
+GenericRxEbpfXskMapControlPath();
+
+VOID
+GenericRxEbpfXskRedirectCloseSocket();
+
+VOID
+GenericRxEbpfXskRedirectQueueMismatch();
+
VOID
GenericTxToRxInject();
diff --git a/test/functional/taef/tests.cpp b/test/functional/taef/tests.cpp
index ae2ba11ef..befc67f5d 100644
--- a/test/functional/taef/tests.cpp
+++ b/test/functional/taef/tests.cpp
@@ -52,6 +52,16 @@ StopTest()
Assert::Fail(L"Stop test execution.");
}
+VOID
+SkipTest(
+ _In_z_ PCWSTR Reason
+ )
+{
+ TraceWarn("%S", Reason);
+ Logger::WriteMessage(L"[SKIPPED] ");
+ Logger::WriteMessage(Reason);
+}
+
VOID
LogTestFailure(
_In_z_ PCWSTR File,
@@ -648,6 +658,34 @@ TEST_CLASS(xdpfunctionaltests)
::GenericRxEbpfUnload();
}
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirect) {
+ ::GenericRxEbpfXskRedirect();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirectFallback) {
+ ::GenericRxEbpfXskRedirectFallback();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirectReplace) {
+ ::GenericRxEbpfXskRedirectReplace();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirectDelete) {
+ ::GenericRxEbpfXskRedirectDelete();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskMapControlPath) {
+ ::GenericRxEbpfXskMapControlPath();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirectCloseSocket) {
+ ::GenericRxEbpfXskRedirectCloseSocket();
+ }
+
+ TEST_METHOD_PRERELEASE(GenericRxEbpfXskRedirectQueueMismatch) {
+ ::GenericRxEbpfXskRedirectQueueMismatch();
+ }
+
TEST_METHOD(GenericLoopbackV4) {
GenericLoopback(AF_INET);
}
diff --git a/test/spinxsk/spinxsk.c b/test/spinxsk/spinxsk.c
index 19d8df097..78178a94d 100644
--- a/test/spinxsk/spinxsk.c
+++ b/test/spinxsk/spinxsk.c
@@ -159,15 +159,11 @@ typedef struct {
typedef enum {
ProgramHandleXdp,
- ProgramHandleEbpf,
} PROGRAM_HANDLE_TYPE;
typedef struct {
PROGRAM_HANDLE_TYPE Type;
- union {
- HANDLE Handle;
- struct bpf_object *BpfObject;
- };
+ HANDLE Handle;
} PROGRAM_HANDLE;
typedef struct {
@@ -176,6 +172,21 @@ typedef struct {
PROGRAM_HANDLE Handles[8];
} XSK_PROGRAM_SET;
+//
+// Shared interface-level eBPF program set. The eBPF program is attached to the
+// entire interface, while each per-queue worker registers its own XSK socket
+// handle in the shared XSKMAP at its queue index.
+//
+// Threads must call AcquireSharedEbpfMapFd / ReleaseSharedEbpfMapFd to safely
+// use the map FD outside the lock.
+//
+typedef struct {
+ CRITICAL_SECTION Lock;
+ struct bpf_object *BpfObject;
+ int XskMapFd;
+ LONG RefCount;
+} XSK_INTERFACE_EBPF_PROGRAM;
+
typedef struct {
HANDLE threadHandle;
XSK_DATAPATH_SHARED *shared;
@@ -296,6 +307,39 @@ UINT32 globalConcurrentWorkerCount = DEFAULT_GLOBAL_CONCURRENT_WORKERS_COUNT;
ULONGLONG perfFreq;
CONST CHAR *watchdogCmd = "";
CONST CHAR *powershellPrefix;
+XSK_INTERFACE_EBPF_PROGRAM sharedEbpfProgram;
+
+//
+// Acquire a reference to the shared XSKMAP FD. Returns the FD if available,
+// or -1 if no program is attached. The caller must call ReleaseSharedEbpfMapFd
+// when done with the FD.
+//
+static
+int
+AcquireSharedEbpfMapFd(
+ VOID
+ )
+{
+ int MapFd = -1;
+
+ EnterCriticalSection(&sharedEbpfProgram.Lock);
+ if (sharedEbpfProgram.BpfObject != NULL && sharedEbpfProgram.XskMapFd >= 0) {
+ MapFd = sharedEbpfProgram.XskMapFd;
+ InterlockedIncrement(&sharedEbpfProgram.RefCount);
+ }
+ LeaveCriticalSection(&sharedEbpfProgram.Lock);
+
+ return MapFd;
+}
+
+static
+VOID
+ReleaseSharedEbpfMapFd(
+ VOID
+ )
+{
+ InterlockedDecrement(&sharedEbpfProgram.RefCount);
+}
ULONG
RandUlong(
@@ -568,19 +612,43 @@ AttachXdpEbpfProgram(
int AttachFlags = 0;
int OriginalThreadPriority;
+ UNREFERENCED_PARAMETER(Queue);
+ UNREFERENCED_PARAMETER(Sock);
+ UNREFERENCED_PARAMETER(RxProgramSet);
+
//
// Since eBPF does not support per-queue programs, attach to the entire
// interface.
//
- UNREFERENCED_PARAMETER(Queue);
+
+ if (!enableEbpf) {
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ }
//
- // Since eBPF does not yet support AF_XDP, ignore the socket.
+ // Only one eBPF program can be attached at a time. If there's already one,
+ // just try to register this queue's socket in the existing map.
//
- UNREFERENCED_PARAMETER(Sock);
+ {
+ int ExistingMapFd = AcquireSharedEbpfMapFd();
+ if (ExistingMapFd >= 0) {
+ if (Sock != NULL) {
+ UINT32 QueueId = Queue->queueId;
+ bpf_map_update_elem(ExistingMapFd, &QueueId, &Sock, 0);
+ }
+ ReleaseSharedEbpfMapFd();
+ return S_OK;
+ }
- if (!enableEbpf) {
- return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ //
+ // Check if an eBPF program is attached but has no map (e.g. drop/pass).
+ //
+ EnterCriticalSection(&sharedEbpfProgram.Lock);
+ if (sharedEbpfProgram.BpfObject != NULL) {
+ LeaveCriticalSection(&sharedEbpfProgram.Lock);
+ return S_OK;
+ }
+ LeaveCriticalSection(&sharedEbpfProgram.Lock);
}
OriginalThreadPriority = GetThreadPriority(GetCurrentThread());
@@ -591,7 +659,7 @@ AttachXdpEbpfProgram(
goto Exit;
}
- switch (RandUlong() % 3) {
+ switch (RandUlong() % 4) {
case 0:
ProgramRelativePath = "\\bpf\\drop.sys";
break;
@@ -601,6 +669,9 @@ AttachXdpEbpfProgram(
case 2:
ProgramRelativePath = "\\bpf\\l1fwd.sys";
break;
+ case 3:
+ ProgramRelativePath = "\\bpf\\xsk_redirect.sys";
+ break;
default:
ASSERT_FRE(FALSE);
}
@@ -670,16 +741,31 @@ AttachXdpEbpfProgram(
goto Exit;
}
- EnterCriticalSection(&RxProgramSet->Lock);
- if (RxProgramSet->HandleCount < RTL_NUMBER_OF(RxProgramSet->Handles)) {
- RxProgramSet->Handles[RxProgramSet->HandleCount].Type = ProgramHandleEbpf;
- RxProgramSet->Handles[RxProgramSet->HandleCount].BpfObject = BpfObject;
- RxProgramSet->HandleCount++;
- Result = S_OK;
- } else {
- Result = E_NOT_SUFFICIENT_BUFFER;
+ //
+ // Store the eBPF program and map FD in the shared interface-level set.
+ //
+ {
+ int XskMapFd = -1;
+ if (ProgramRelativePath != NULL &&
+ strcmp(ProgramRelativePath, "\\bpf\\xsk_redirect.sys") == 0) {
+ XskMapFd = bpf_object__find_map_fd_by_name(BpfObject, "xsk_map");
+ }
+
+ EnterCriticalSection(&sharedEbpfProgram.Lock);
+ sharedEbpfProgram.BpfObject = BpfObject;
+ sharedEbpfProgram.XskMapFd = XskMapFd;
+ LeaveCriticalSection(&sharedEbpfProgram.Lock);
+
+ //
+ // Register this queue's socket in the map.
+ //
+ if (XskMapFd >= 0 && Sock != NULL) {
+ UINT32 QueueId = Queue->queueId;
+ bpf_map_update_elem(XskMapFd, &QueueId, &Sock, 0);
+ }
}
- LeaveCriticalSection(&RxProgramSet->Lock);
+
+ BpfObject = NULL; // Ownership transferred to shared set.
Exit:
@@ -841,7 +927,6 @@ DetachXdpProgram(
)
{
HANDLE Handle = NULL;
- struct bpf_object *BpfObject = NULL;
EnterCriticalSection(&RxProgramSet->Lock);
if (RxProgramSet->HandleCount > 0) {
@@ -852,10 +937,6 @@ DetachXdpProgram(
Handle = RxProgramSet->Handles[detachIndex].Handle;
break;
- case ProgramHandleEbpf:
- BpfObject = RxProgramSet->Handles[detachIndex].BpfObject;
- break;
-
default:
ASSERT_FRE(FALSE);
}
@@ -866,28 +947,6 @@ DetachXdpProgram(
if (Handle != NULL) {
ASSERT_FRE(CloseHandle(Handle));
}
-
- if (BpfObject != NULL) {
- int OriginalThreadPriority = GetThreadPriority(GetCurrentThread());
- ASSERT_FRE(OriginalThreadPriority != THREAD_PRIORITY_ERROR_RETURN);
-
- //
- // To work around control path delays caused by eBPF's epoch implementation,
- // boost this thread's priority when invoking eBPF APIs.
- //
- ASSERT_FRE(SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST));
-
- //
- // Detach the XDP program from the interface before closing the object.
- //
- TraceVerbose("bpf_xdp_detach(%d, 0, NULL)", ifindex);
- bpf_xdp_detach(ifindex, 0, NULL);
-
- TraceVerbose("bpf_object__close(%p)", BpfObject);
- bpf_object__close(BpfObject);
-
- ASSERT_FRE(SetThreadPriority(GetCurrentThread(), OriginalThreadPriority));
- }
}
HRESULT
@@ -1852,6 +1911,53 @@ FuzzSocketMisc(
if (!cleanDatapath && !(RandUlong() % 3)) {
AttachXdpProgram(Queue, Sock, FALSE, RxProgramSet);
}
+
+ //
+ // Exercise randomized XSKMAP operations on the shared map to stress the
+ // map control path concurrently with the data path.
+ //
+ if (enableEbpf && !(RandUlong() % 4)) {
+ int MapFd;
+ UINT32 QueueId = Queue->queueId;
+ HANDLE LookupValue;
+
+ MapFd = AcquireSharedEbpfMapFd();
+
+ if (MapFd >= 0) {
+ switch (RandUlong() % 4) {
+ case 0:
+ //
+ // Update this queue's entry with the current socket.
+ //
+ bpf_map_update_elem(MapFd, &QueueId, &Sock, 0);
+ break;
+
+ case 1:
+ //
+ // Lookup this queue's entry.
+ //
+ bpf_map_lookup_elem(MapFd, &QueueId, &LookupValue);
+ break;
+
+ case 2:
+ //
+ // Delete this queue's entry.
+ //
+ bpf_map_delete_elem(MapFd, &QueueId);
+ break;
+
+ case 3:
+ //
+ // Delete then re-add to exercise the replace path.
+ //
+ bpf_map_delete_elem(MapFd, &QueueId);
+ bpf_map_update_elem(MapFd, &QueueId, &Sock, 0);
+ break;
+ }
+
+ ReleaseSharedEbpfMapFd();
+ }
+ }
}
VOID
@@ -3241,6 +3347,9 @@ main(
stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ASSERT_FRE(stopEvent != NULL);
+ InitializeCriticalSection(&sharedEbpfProgram.Lock);
+ sharedEbpfProgram.XskMapFd = -1;
+
workersDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ASSERT_FRE(workersDoneEvent != NULL);
@@ -3334,6 +3443,37 @@ main(
free(queueWorkers);
+ //
+ // Clean up the shared eBPF program. Wait for outstanding references to
+ // drain before closing the BPF object.
+ //
+ if (sharedEbpfProgram.BpfObject != NULL) {
+ int OriginalThreadPriority = GetThreadPriority(GetCurrentThread());
+ ASSERT_FRE(OriginalThreadPriority != THREAD_PRIORITY_ERROR_RETURN);
+ ASSERT_FRE(SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST));
+
+ //
+ // Prevent new acquisitions by clearing the map FD under lock.
+ //
+ EnterCriticalSection(&sharedEbpfProgram.Lock);
+ sharedEbpfProgram.XskMapFd = -1;
+ LeaveCriticalSection(&sharedEbpfProgram.Lock);
+
+ //
+ // Wait for any outstanding map FD references to drain.
+ //
+ while (ReadNoFence(&sharedEbpfProgram.RefCount) > 0) {
+ SwitchToThread();
+ }
+
+ bpf_xdp_detach(ifindex, 0, NULL);
+ bpf_object__close(sharedEbpfProgram.BpfObject);
+ sharedEbpfProgram.BpfObject = NULL;
+
+ ASSERT_FRE(SetThreadPriority(GetCurrentThread(), OriginalThreadPriority));
+ }
+ DeleteCriticalSection(&sharedEbpfProgram.Lock);
+
printf("done\n");
WPP_CLEANUP();
diff --git a/tools/functional.ps1 b/tools/functional.ps1
index a787f17aa..06b526813 100644
--- a/tools/functional.ps1
+++ b/tools/functional.ps1
@@ -184,6 +184,8 @@ for ($i = 1; $i -le $Iterations; $i++) {
}
}
+ $env:XDP_EBPF_RUNTIME_VERSION = if ([string]::IsNullOrEmpty($EbpfVersion)) { Get-EbpfVersion } else { $EbpfVersion }
+
Write-Verbose "$VsTestPath\$VsTestConsole.exe $TestArgs"
& $VsTestPath\$VsTestConsole.exe $TestArgs
diff --git a/tools/rxfilter.ps1 b/tools/rxfilter.ps1
index 343eb5545..e2753cff3 100644
--- a/tools/rxfilter.ps1
+++ b/tools/rxfilter.ps1
@@ -47,7 +47,7 @@ if ($Forwarded) { return }
$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
for ($i = 0; $i -lt $QueueCount; $i++) {
- Start-Process $ArtifactsDir\test\rxfilter.exe -ArgumentList `
+ Start-Process $ArtifactsDir\test\rxfilter-deprecated.exe -ArgumentList `
"-IfIndex", (Get-NetAdapter -Name $AdapterName).ifIndex, `
"-QueueId", $i, "-MatchType" ,"All", "-Action", $Action, `
"-XdpMode", $XdpMode
diff --git a/tools/rxfilterperf.ps1 b/tools/rxfilterperf.ps1
index 913a8abe1..3105acf7a 100644
--- a/tools/rxfilterperf.ps1
+++ b/tools/rxfilterperf.ps1
@@ -55,7 +55,7 @@ if ($Forwarded -is [array]) { $Forwarded = $Forwarded[-1] }
if ($Forwarded) { return }
$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
$LogsDir = "$RootDir\artifacts\logs"
-$RxFilter = "$ArtifactsDir\test\rxfilter.exe"
+$RxFilter = "$ArtifactsDir\test\rxfilter-deprecated.exe"
# Ensure the output path exists.
New-Item -ItemType Directory -Force -Path $LogsDir | Out-Null
diff --git a/tools/sample-rxfilter-test.ps1 b/tools/sample-rxfilter-test.ps1
new file mode 100644
index 000000000..5c3a58a0e
--- /dev/null
+++ b/tools/sample-rxfilter-test.ps1
@@ -0,0 +1,109 @@
+<#
+
+.SYNOPSIS
+This script tests the rxfilter eBPF sample by attaching a drop-all eBPF
+program to a virtual XDPMP adapter and verifying traffic is processed.
+
+.PARAMETER Config
+ Specifies the build configuration to use.
+
+.PARAMETER Platform
+ The CPU architecture to use.
+
+.PARAMETER Duration
+ Duration of the test in seconds.
+
+#>
+
+param (
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("Debug", "Release")]
+ [string]$Config = "Debug",
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("x64", "arm64")]
+ [string]$Platform = "x64",
+
+ [Parameter(Mandatory = $false)]
+ [Int32]$Duration = 10
+)
+
+Set-StrictMode -Version 'Latest'
+$ErrorActionPreference = 'Stop'
+
+# Important paths.
+$RootDir = Split-Path $PSScriptRoot -Parent
+. $RootDir\tools\common.ps1
+
+$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
+$RxFilter = "$ArtifactsDir\test\rxfilter.exe"
+$BpfProgram = "$ArtifactsDir\test\bpf\drop.sys"
+
+# Verify binaries exist.
+if (!(Test-Path $RxFilter)) {
+ Write-Error "$RxFilter does not exist!"
+}
+if (!(Test-Path $BpfProgram)) {
+ Write-Error "$BpfProgram does not exist!"
+}
+
+$RxFilterProcess = $null
+
+try {
+ & "$RootDir\tools\log.ps1" -Start -Name sample_rxfilter -Profile XdpFunctional.Verbose -Config $Config -Platform $Platform
+
+ Write-Verbose "installing ebpf..."
+ & "$RootDir\tools\setup.ps1" -Install ebpf -Config $Config -Platform $Platform
+ Write-Verbose "installed ebpf."
+
+ Write-Verbose "installing xdp..."
+ & "$RootDir\tools\setup.ps1" -Install xdp -Config $Config -Platform $Platform -EnableEbpf
+ Write-Verbose "installed xdp."
+
+ Write-Verbose "installing fndis..."
+ & "$RootDir\tools\setup.ps1" -Install fndis -Config $Config -Platform $Platform
+ Write-Verbose "installed fndis."
+
+ Write-Verbose "installing xdpmp..."
+ & "$RootDir\tools\setup.ps1" -Install xdpmp -Config $Config -Platform $Platform -XdpmpPollProvider FNDIS
+ Write-Verbose "installed xdpmp."
+
+ $IfIndex = (Get-NetAdapter XDPMP).ifIndex
+
+ #
+ # Start rxfilter with the drop-all eBPF program.
+ #
+ $ArgList = "-IfIndex", $IfIndex, "-BpfProgram", $BpfProgram, "-ProgramName", "drop"
+ Write-Verbose "$RxFilter $ArgList"
+ $RxFilterProcess = Start-Process $RxFilter -PassThru -ArgumentList $ArgList
+
+ $StartPackets = (Get-NetAdapterStatistics -Name XDPMP).ReceivedUnicastPackets
+ Write-Verbose "Waiting $Duration seconds with traffic flowing..."
+ Start-Sleep -Seconds $Duration
+ $EndPackets = (Get-NetAdapterStatistics -Name XDPMP).ReceivedUnicastPackets
+
+ & $RootDir\tools\xdpmpratesim.ps1 -AdapterName XDPMP -RxFramesPerInterval 1000
+
+ $PacketsProcessed = $EndPackets - $StartPackets
+ Write-Output "rxfilter processed $PacketsProcessed packets in $Duration seconds."
+
+ if ($RxFilterProcess.HasExited) {
+ Write-Error "rxfilter exited unexpectedly with code $($RxFilterProcess.ExitCode)"
+ }
+
+ if ($PacketsProcessed -eq 0) {
+ Write-Error "No packets were processed - eBPF program may not have attached correctly"
+ }
+
+ Write-Output "rxfilter sample test PASSED"
+
+} finally {
+ if ($null -ne $RxFilterProcess -and !$RxFilterProcess.HasExited) {
+ Stop-Process -Force -InputObject $RxFilterProcess -ErrorAction 'Continue'
+ }
+ & "$RootDir\tools\setup.ps1" -Uninstall ebpf -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdpmp -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall fndis -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdp -Config $Config -Platform $Platform -Force -ErrorAction 'Continue'
+ & "$RootDir\tools\log.ps1" -Stop -Name sample_rxfilter -Config $Config -Platform $Platform -ErrorAction 'Continue'
+}
diff --git a/tools/sample-xskfwd-test.ps1 b/tools/sample-xskfwd-test.ps1
new file mode 100644
index 000000000..539c60924
--- /dev/null
+++ b/tools/sample-xskfwd-test.ps1
@@ -0,0 +1,101 @@
+<#
+
+.SYNOPSIS
+This script tests the xskfwd eBPF sample by attaching an XSK redirect eBPF
+program to a virtual XDPMP adapter, generating traffic, and forwarding
+received frames back to the sender.
+
+.PARAMETER Config
+ Specifies the build configuration to use.
+
+.PARAMETER Platform
+ The CPU architecture to use.
+
+.PARAMETER Duration
+ Duration of the test in seconds.
+
+#>
+
+param (
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("Debug", "Release")]
+ [string]$Config = "Debug",
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("x64", "arm64")]
+ [string]$Platform = "x64",
+
+ [Parameter(Mandatory = $false)]
+ [Int32]$Duration = 10
+)
+
+Set-StrictMode -Version 'Latest'
+$ErrorActionPreference = 'Stop'
+
+# Important paths.
+$RootDir = Split-Path $PSScriptRoot -Parent
+. $RootDir\tools\common.ps1
+
+$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
+$XskFwd = "$ArtifactsDir\test\xskfwd.exe"
+$BpfProgram = "$ArtifactsDir\test\bpf\xsk_redirect.sys"
+
+# Verify binaries exist.
+if (!(Test-Path $XskFwd)) {
+ Write-Error "$XskFwd does not exist!"
+}
+if (!(Test-Path $BpfProgram)) {
+ Write-Error "$BpfProgram does not exist!"
+}
+
+$XskFwdProcess = $null
+
+try {
+ & "$RootDir\tools\log.ps1" -Start -Name sample_xskfwd -Profile XdpFunctional.Verbose -Config $Config -Platform $Platform
+
+ Write-Verbose "installing ebpf..."
+ & "$RootDir\tools\setup.ps1" -Install ebpf -Config $Config -Platform $Platform
+ Write-Verbose "installed ebpf."
+
+ Write-Verbose "installing xdp..."
+ & "$RootDir\tools\setup.ps1" -Install xdp -Config $Config -Platform $Platform -EnableEbpf
+ Write-Verbose "installed xdp."
+
+ Write-Verbose "installing fndis..."
+ & "$RootDir\tools\setup.ps1" -Install fndis -Config $Config -Platform $Platform
+ Write-Verbose "installed fndis."
+
+ Write-Verbose "installing xdpmp..."
+ & "$RootDir\tools\setup.ps1" -Install xdpmp -Config $Config -Platform $Platform -XdpmpPollProvider FNDIS
+ Write-Verbose "installed xdpmp."
+
+ $IfIndex = (Get-NetAdapter XDPMP).ifIndex
+
+ #
+ # Start xskfwd with the xsk_redirect eBPF program.
+ #
+ $ArgList = "$IfIndex", "-BpfProgram", $BpfProgram, "-ProgramName", "xsk_redirect"
+ Write-Verbose "$XskFwd $ArgList"
+ $XskFwdProcess = Start-Process $XskFwd -PassThru -ArgumentList $ArgList
+
+ Write-Verbose "Waiting $Duration seconds with traffic flowing..."
+ Start-Sleep -Seconds $Duration
+
+ & $RootDir\tools\xdpmpratesim.ps1 -AdapterName XDPMP -RxFramesPerInterval 1000
+
+ if ($XskFwdProcess.HasExited) {
+ Write-Error "xskfwd exited unexpectedly with code $($XskFwdProcess.ExitCode)"
+ }
+
+ Write-Output "xskfwd sample test PASSED"
+
+} finally {
+ if ($null -ne $XskFwdProcess -and !$XskFwdProcess.HasExited) {
+ Stop-Process -Force -InputObject $XskFwdProcess -ErrorAction 'Continue'
+ }
+ & "$RootDir\tools\setup.ps1" -Uninstall ebpf -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdpmp -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall fndis -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdp -Config $Config -Platform $Platform -Force -ErrorAction 'Continue'
+ & "$RootDir\tools\log.ps1" -Stop -Name sample_xskfwd -Config $Config -Platform $Platform -ErrorAction 'Continue'
+}
diff --git a/tools/sample-xskrestricted-test.ps1 b/tools/sample-xskrestricted-test.ps1
new file mode 100644
index 000000000..d4edf3903
--- /dev/null
+++ b/tools/sample-xskrestricted-test.ps1
@@ -0,0 +1,92 @@
+<#
+
+.SYNOPSIS
+This script tests the xskrestricted eBPF sample by attaching an XSK redirect
+eBPF program to a virtual XDPMP adapter, spawning a restricted child process,
+and forwarding received frames back to the sender.
+
+.PARAMETER Config
+ Specifies the build configuration to use.
+
+.PARAMETER Platform
+ The CPU architecture to use.
+
+.PARAMETER TimeoutSeconds
+ Duration of execution in seconds.
+
+#>
+
+param (
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("Debug", "Release")]
+ [string]$Config = "Debug",
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("x64", "arm64")]
+ [string]$Platform = "x64",
+
+ [Parameter(Mandatory = $false)]
+ [Int32]$TimeoutSeconds = 10
+)
+
+Set-StrictMode -Version 'Latest'
+$ErrorActionPreference = 'Stop'
+
+# Important paths.
+$RootDir = Split-Path $PSScriptRoot -Parent
+. $RootDir\tools\common.ps1
+
+$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
+$XskRestricted = "$ArtifactsDir\test\xskrestricted.exe"
+$BpfProgram = "$ArtifactsDir\test\bpf\xsk_redirect.sys"
+
+# Verify binaries exist.
+if (!(Test-Path $XskRestricted)) {
+ Write-Error "$XskRestricted does not exist!"
+}
+if (!(Test-Path $BpfProgram)) {
+ Write-Error "$BpfProgram does not exist!"
+}
+
+try {
+ & "$RootDir\tools\log.ps1" -Start -Name sample_xskrestricted -Profile XdpFunctional.Verbose -Config $Config -Platform $Platform
+
+ Write-Verbose "installing ebpf..."
+ & "$RootDir\tools\setup.ps1" -Install ebpf -Config $Config -Platform $Platform
+ Write-Verbose "installed ebpf."
+
+ Write-Verbose "installing xdp..."
+ & "$RootDir\tools\setup.ps1" -Install xdp -Config $Config -Platform $Platform -EnableEbpf
+ Write-Verbose "installed xdp."
+
+ Write-Verbose "installing fndis..."
+ & "$RootDir\tools\setup.ps1" -Install fndis -Config $Config -Platform $Platform
+ Write-Verbose "installed fndis."
+
+ Write-Verbose "installing xdpmp..."
+ & "$RootDir\tools\setup.ps1" -Install xdpmp -Config $Config -Platform $Platform -XdpmpPollProvider FNDIS
+ Write-Verbose "installed xdpmp."
+
+ $IfIndex = (Get-NetAdapter XDPMP).ifIndex
+
+ #
+ # Run xskrestricted with the xsk_redirect eBPF program.
+ # The program spawns a restricted child, forwards traffic for TimeoutSeconds,
+ # then exits.
+ #
+ Write-Verbose "$XskRestricted $IfIndex -BpfProgram $BpfProgram -ProgramName xsk_redirect -TimeoutSeconds $TimeoutSeconds"
+ & $XskRestricted $IfIndex -BpfProgram $BpfProgram -ProgramName xsk_redirect -TimeoutSeconds $TimeoutSeconds
+
+ if ($LastExitCode -ne 0) {
+ Write-Error "xskrestricted failed with exit code $LastExitCode"
+ }
+
+ Write-Output "xskrestricted sample test PASSED"
+
+} finally {
+ & "$RootDir\tools\setup.ps1" -Uninstall ebpf -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdpmp -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall fndis -Config $Config -Platform $Platform -ErrorAction 'Continue'
+ & "$RootDir\tools\setup.ps1" -Uninstall xdp -Config $Config -Platform $Platform -Force -ErrorAction 'Continue'
+ & "$RootDir\tools\log.ps1" -Stop -Name sample_xskrestricted -Config $Config -Platform $Platform -ErrorAction 'Continue'
+}
diff --git a/tools/setup.ps1 b/tools/setup.ps1
index 85346ff79..85a4e6089 100644
--- a/tools/setup.ps1
+++ b/tools/setup.ps1
@@ -95,7 +95,7 @@ $XdpMpCert = "$ArtifactsDir\test\xdpmp.cer"
$XdpMpComponentId = "ms_xdpmp"
$XdpMpDeviceId = "xdpmp0"
$XdpMpServiceName = "XDPMP"
-$XskFwdKmSys = "$ArtifactsDir\test\xskfwdkm.sys"
+$XskFwdKmSys = "$ArtifactsDir\test\xskfwdkm-deprecated.sys"
# Ensure the output path exists.
New-Item -ItemType Directory -Force -Path $LogsDir | Out-Null
diff --git a/tools/xskrestricted.ps1 b/tools/xskrestricted.ps1
index 0441b2b2f..b9607572f 100644
--- a/tools/xskrestricted.ps1
+++ b/tools/xskrestricted.ps1
@@ -35,7 +35,7 @@ $RootDir = Split-Path $PSScriptRoot -Parent
. $RootDir\tools\common.ps1
$ArtifactsDir = Get-ArtifactBinPath -Config $Config -Platform $Platform
-$XskRestricted = "$ArtifactsDir\test\xskrestricted.exe"
+$XskRestricted = "$ArtifactsDir\test\xskrestricted-deprecated.exe"
# Verify the binary exists.
if (!(Test-Path $XskRestricted)) {
diff --git a/xdp.sln b/xdp.sln
index 995f89738..6d9c7c31a 100644
--- a/xdp.sln
+++ b/xdp.sln
@@ -43,7 +43,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rssconfig", "test\rssconfig
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xdpetw", "src\xdpetw\xdpetw.vcxproj", "{A87D995B-384F-4853-ABDB-3EC024BE18E3}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rxfilter", "samples\rxfilter\rxfilter.vcxproj", "{C0C730A8-2DAB-4C28-8C4B-5EFFA6B494DD}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rxfilter-deprecated", "samples\deprecated\rxfilter\rxfilter-deprecated.vcxproj", "{C0C730A8-2DAB-4C28-8C4B-5EFFA6B494DD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskmaprx", "samples\xskmaprx\xskmaprx.vcxproj", "{E86961B5-24DD-435F-8844-C7875252FA73}"
EndProject
@@ -51,7 +51,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bpf", "test\bpf\bpf.vcxproj
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xdpcfg", "src\xdpcfg\xdpcfg.vcxproj", "{E64CCF9C-9D27-4AAC-8119-197FABA5E8C2}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskfwd", "samples\xskfwd\xskfwd.vcxproj", "{238CD65B-59D5-4EC5-BA23-630F567395FA}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskfwd-deprecated", "samples\deprecated\xskfwd\xskfwd-deprecated.vcxproj", "{238CD65B-59D5-4EC5-BA23-630F567395FA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xdppcw", "src\xdppcw\xdppcw.vcxproj", "{ED611744-B780-41A2-A995-2C100D86B3A6}"
EndProject
@@ -65,11 +65,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "onebranch", "src\onebranch\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xdpruntime", "src\xdpruntime\xdpruntime.vcxproj", "{8250C528-6A2C-44E7-AA76-1E6BEC9127A3}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskfwdkm", "samples\xskfwd\xskfwdkm.vcxproj", "{F722DA97-3252-42F1-92D3-215C99935607}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskfwdkm-deprecated", "samples\deprecated\xskfwd\xskfwdkm-deprecated.vcxproj", "{F722DA97-3252-42F1-92D3-215C99935607}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ringperf", "test\ringperf\ringperf.vcxproj", "{838975CE-9BC8-4BA8-A129-2C33791BA339}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskrestricted", "samples\xskrestricted\xskrestricted.vcxproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskrestricted-deprecated", "samples\deprecated\xskrestricted\xskrestricted-deprecated.vcxproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rxfilter", "samples\rxfilter\rxfilter.vcxproj", "{AE14127D-704C-4B54-AE84-FDC5F3A72B99}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskfwd", "samples\xskfwd\xskfwd.vcxproj", "{2A9A8060-B806-4BF8-AF21-C89965D49AA0}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xskrestricted", "samples\xskrestricted\xskrestricted.vcxproj", "{EAC6D867-EF75-4284-8603-6D202B7C7F93}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pktmonclnt", "src\pktmonclnt\pktmonclnt.vcxproj", "{DEE8C283-682F-40F2-818B-06123BCC7844}"
EndProject
@@ -425,6 +431,30 @@ Global
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Deploy.0 = Release|x64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Debug|ARM64.Build.0 = Debug|ARM64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Debug|x64.ActiveCfg = Debug|x64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Debug|x64.Build.0 = Debug|x64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Release|ARM64.ActiveCfg = Release|ARM64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Release|ARM64.Build.0 = Release|ARM64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Release|x64.ActiveCfg = Release|x64
+ {AE14127D-704C-4B54-AE84-FDC5F3A72B99}.Release|x64.Build.0 = Release|x64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Debug|ARM64.Build.0 = Debug|ARM64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Debug|x64.ActiveCfg = Debug|x64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Debug|x64.Build.0 = Debug|x64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Release|ARM64.ActiveCfg = Release|ARM64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Release|ARM64.Build.0 = Release|ARM64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Release|x64.ActiveCfg = Release|x64
+ {2A9A8060-B806-4BF8-AF21-C89965D49AA0}.Release|x64.Build.0 = Release|x64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Debug|ARM64.Build.0 = Debug|ARM64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Debug|x64.ActiveCfg = Debug|x64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Debug|x64.Build.0 = Debug|x64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Release|ARM64.ActiveCfg = Release|ARM64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Release|ARM64.Build.0 = Release|ARM64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Release|x64.ActiveCfg = Release|x64
+ {EAC6D867-EF75-4284-8603-6D202B7C7F93}.Release|x64.Build.0 = Release|x64
{DEE8C283-682F-40F2-818B-06123BCC7844}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DEE8C283-682F-40F2-818B-06123BCC7844}.Debug|ARM64.Build.0 = Debug|ARM64
{DEE8C283-682F-40F2-818B-06123BCC7844}.Debug|x64.ActiveCfg = Debug|x64