Skip to content

Major Loop Improvements #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Offloading your XDP/BPF program to your system's NIC allows for the fastest pack
At this time, I am not aware of any NIC manufacturers that will be able to offload this proxy completely to the NIC due to its BPF complexity.

### BPF Loop Support
This proxy requires loop support with BPF. Older kernels will not support this feature and output an error such as the following.
This proxy requires general loop support along with support for the [`bpf_loop()`](https://docs.ebpf.io/linux/helper-function/bpf_loop/) function. Older kernels will not support general loops and output an error such as the following.

```vim
libbpf: load bpf program failed: Invalid argument
Expand All @@ -247,7 +247,9 @@ libbpf: failed to load program 'xdp_prog'
libbpf: failed to load object '/etc/xdpfwd/xdp_prog.o'
```

It looks like BPF loop [support](https://lwn.net/Articles/794934/) was added in kernel 5.3. Therefore, you'll need kernel 5.3 or above for this tool to run properly.
It looks like general BPF loop [support](https://lwn.net/Articles/794934/) was added in kernel 5.3. Therefore, you'll need kernel 5.3 or above for this tool to run properly.

With that said, the `bpf_loop()` function was added in kernel `5.17`, but still requires `6.4` or above due to support for open coded iterators. If you do not wish to upgrade your kernel to 6.4 or above, you will need to disable/comment out the `USE_NEW_LOOP` constant in the [`config.h`](./src/common/config.h) file. Please note if you do this, you will be **extremely limited** in how many concurrent source ports you can use (I recommend up to 21). Therefore, it is recommended you use `bpf_loop()` since you will have a much larger source port range!

### Forward Rule Logging
This tool uses `bpf_ringbuf_reserve()` and `bpf_ringbuf_submit()` for logging a message when a new connection is created if the forward rule has logging enabled.
Expand All @@ -264,11 +266,6 @@ When loading the BPF/XDP program through LibXDP/LibBPF, logging is disabled unle
If the tool fails to load or attach the XDP program, it is recommended you set `verbose` to 5 or above so LibXDP outputs specific warnings and errors.

## ❓ F.A.Q.
### I receive BPF errors when increasing the `MIN_PORT` and `MAX_PORT` range. What can I do?
Unfortunately, the amount of source ports available is limited by default due to limitations with the BPF verifier. By default, 21 source ports are available meaning only 21 connections are supported concurrently.

In order to raise these limitations, you will need to build a custom Linux kernel. More details regarding this issue along with kernel patches are available [here](./patches).

### Why are binaries and configs named `xdpfwd`?
Originally, this project was called **XDP Forwarding**. After I revamped the project, I decided to rename the main project to **XDP Proxy** since that suits the project more in my opinion. However, executable names like `xdpproxy` doesn't flow as well in my opinion, so I decided to keep binaries and config names/paths set to `xdpfwd`.

Expand Down
8 changes: 6 additions & 2 deletions src/common/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// The port range to use when selecting an available source port.
// MAX_PORT - (MIN_PORT - 1) = The maximum amount of concurrent connections.
#define MIN_PORT 500
#define MAX_PORT 520
#define MAX_PORT 900

// Enables forward rule logging.
#define ENABLE_RULE_LOGGING
Expand All @@ -32,4 +32,8 @@

// Adds packet and last seen counters to connections.
// This isn't used anywhere in the program right now which is why it's disabled by default.
//#define CONNECTION_COUNTERS
//#define CONNECTION_COUNTERS

// If enabled, uses a newer bpf_loop() function when choosing a source port for a new connection.
// This allows for a much higher source port range. However, it requires a more recent kernel.
#define USE_NEW_LOOP
38 changes: 12 additions & 26 deletions src/xdp/prog.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <common/all.h>

#include <xdp/utils/forward.h>
#include <xdp/utils/port.h>
#include <xdp/utils/logging.h>
#include <xdp/utils/stats.h>
#include <xdp/utils/helpers.h>
Expand Down Expand Up @@ -206,38 +207,23 @@ int xdp_prog_main(struct xdp_md *ctx)

if (!icmph)
{
u64 last = UINT64_MAX;

port_ctx_t port_ctx = {0};
port_ctx.last = UINT64_MAX;
port_ctx.port_to_use = 0;
port_ctx.port_key = port_key;

#ifdef USE_NEW_LOOP
bpf_loop(MAX_PORTS, choose_port, &port_ctx, 0);
#else
for (u16 i = MIN_PORT; i <= MAX_PORT; i++)
{
port_key.port = htons(i);

port_val_t* port_lookup = bpf_map_lookup_elem(&map_ports, &port_key);

if (!port_lookup)
if (choose_port(i - MIN_PORT, &port_ctx))
{
port_to_use = i;

break;
}

#ifdef RECYCLE_LAST_SEEN
if (port_lookup->last_seen < last)
{
port_to_use = i;
last = port_lookup->last_seen;
}
#else
u64 pps = (port_lookup->last_seen - port_lookup->first_seen) / port_lookup->count;

// We'll want to replace the most inactive connection.
if (last > pps)
{
port_to_use = i;
last = pps;
}
#endif
}
#endif
port_to_use = port_ctx.port_to_use;
}

if (port_to_use > 0 || icmph)
Expand Down
2 changes: 0 additions & 2 deletions src/xdp/utils/helpers.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include <xdp/utils/helpers.h>

#include <xdp/utils/maps.h>

/**
* Swaps the Ethernet source and destination MAC addresses.
*
Expand Down
39 changes: 39 additions & 0 deletions src/xdp/utils/port.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <xdp/utils/port.h>

static __always_inline long choose_port(u32 idx, void* data)
{
port_ctx_t* ctx = data;

u16 port = MIN_PORT + idx;

ctx->port_key.port = htons(port);

port_val_t *port_lookup = bpf_map_lookup_elem(&map_ports, &ctx->port_key);

if (!port_lookup)
{
ctx->port_to_use = port;

return 1;
}

#ifdef RECYCLE_LAST_SEEN
if (port_lookup->last_seen < ctx->last)
{
ctx->port_to_use = port;
ctx->last = port_lookup->last_seen;
}
#else
if (port_lookup->count > 0)
{
u64 pps = (port_lookup->last_seen - port_lookup->first_seen) / port_lookup->count;
if (ctx->last > pps)
{
ctx->port_to_use = port;
ctx->last = pps;
}
}
#endif

return 0;
}
19 changes: 19 additions & 0 deletions src/xdp/utils/port.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <common/all.h>

#include <xdp/utils/maps.h>

struct port_ctx
{
u64 last;
u16 port_to_use;
port_key_t port_key;
} typedef port_ctx_t;

static __always_inline long choose_port(u32 idx, void* data);

// The source file is included directly below instead of compiled and linked as an object because when linking, there is no guarantee the compiler will inline the function (which is crucial for performance).
// I'd prefer not to include the function logic inside of the header file.
// More Info: https://stackoverflow.com/questions/24289599/always-inline-does-not-work-when-function-is-implemented-in-different-file
#include "port.c"