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" /> + + +