Skip to content

Commit 5842cbc

Browse files
authored
fix: forward proxy response path with custom backend ports (#25)
1 parent 5c50809 commit 5842cbc

8 files changed

Lines changed: 225 additions & 47 deletions

File tree

ebpf/include/backend.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ struct {
3131
__type(value, __u32);
3232
} backend_count SEC(".maps");
3333

34+
// Set of ports that backends listen on, used to detect FROM_WG return traffic.
35+
// Key: backend port (host byte order), Value: unused.
36+
struct {
37+
__uint(type, BPF_MAP_TYPE_HASH);
38+
__uint(max_entries, MAX_BACKENDS);
39+
__type(key, __u16);
40+
__type(value, __u8);
41+
} backend_port_set SEC(".maps");
42+
3443
// jhash - Jenkins hash for consistent backend selection
3544
static __always_inline __u32 jhash_2words(__u32 a, __u32 b, __u32 initval) {
3645
__u32 c = initval;

ebpf/wg_forward_proxy.c

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -240,50 +240,10 @@ int wg_forward_proxy(struct xdp_md *xdp_ctx) {
240240
__u16 src_port = ctx.src_port;
241241
__u16 dst_port = ctx.dst_port;
242242
__u16 wg_port = CONFIG(wg_port);
243-
if (dst_port != wg_port && src_port != wg_port)
244-
return XDP_PASS;
245243

246244
__u8 is_to_wg = (dst_port == wg_port) ? 1 : 0;
247-
__u8 is_from_wg = (src_port == wg_port) ? 1 : 0;
248-
249245
__u32 pkt_len = (void *)(long)xdp_ctx->data_end - (void *)(long)xdp_ctx->data;
250246

251-
if (likely(is_from_wg)) {
252-
struct connection_key original_conn = { 0 };
253-
if (restore_nat_connection(&ctx, &original_conn) < 0) {
254-
DEBUG_PRINTK("Failed to restore NAT connection for FROM WG packet, passing "
255-
"through");
256-
return XDP_PASS;
257-
}
258-
259-
struct connection_value *conn_value = bpf_map_lookup_elem(&connection_map, &original_conn);
260-
if (!conn_value) {
261-
DEBUG_PRINTK("No connection value found for FROM WG packet");
262-
return XDP_PASS;
263-
}
264-
265-
__u8 backend_index = conn_value->backend_index;
266-
267-
// FROM_WG path: backend->proxy (upstream rx), proxy->client (downstream tx)
268-
update_metrics(backend_index, METRIC_UPSTREAM, pkt_len, 1, METRIC_REASON_FORWARDED);
269-
270-
if (instr_deobfuscate_xdp(&ctx) < 0) {
271-
DEBUG_PRINTK("Deobfuscation failed, dropping packet");
272-
update_metrics(backend_index, METRIC_UPSTREAM, pkt_len, 1, METRIC_REASON_DROPPED);
273-
return XDP_DROP;
274-
}
275-
276-
__u32 tx_pkt_len = (void *)(long)xdp_ctx->data_end - (void *)(long)xdp_ctx->data;
277-
278-
__u32 dst_addr = bpf_ntohl(ctx.ip->daddr);
279-
__u32 client_ip = bpf_ntohl(original_conn.client_ip);
280-
__u16 server_port = original_conn.server_port;
281-
__u16 client_port = original_conn.client_port;
282-
283-
update_metrics(backend_index, METRIC_DOWNSTREAM, tx_pkt_len, 0, METRIC_REASON_FORWARDED);
284-
return forward_packet(&ctx, dst_addr, server_port, client_ip, client_port);
285-
}
286-
287247
if (unlikely(is_to_wg)) {
288248
struct backend_entry backend = { 0 };
289249
if (create_nat_connection(&ctx, &backend) < 0) {
@@ -326,6 +286,44 @@ int wg_forward_proxy(struct xdp_md *xdp_ctx) {
326286
return forward_packet(&ctx, proxy_ip, conn_value->nat_port, server_ip, target_port);
327287
}
328288

289+
__u8 is_from_wg = bpf_map_lookup_elem(&backend_port_set, &src_port) != NULL ? 1 : 0;
290+
291+
if (likely(is_from_wg)) {
292+
struct connection_key original_conn = { 0 };
293+
if (restore_nat_connection(&ctx, &original_conn) < 0) {
294+
DEBUG_PRINTK("Failed to restore NAT connection for FROM WG packet, passing "
295+
"through");
296+
return XDP_PASS;
297+
}
298+
299+
struct connection_value *conn_value = bpf_map_lookup_elem(&connection_map, &original_conn);
300+
if (!conn_value) {
301+
DEBUG_PRINTK("No connection value found for FROM WG packet");
302+
return XDP_PASS;
303+
}
304+
305+
__u8 backend_index = conn_value->backend_index;
306+
307+
// FROM_WG path: backend->proxy (upstream rx), proxy->client (downstream tx)
308+
update_metrics(backend_index, METRIC_UPSTREAM, pkt_len, 1, METRIC_REASON_FORWARDED);
309+
310+
if (instr_deobfuscate_xdp(&ctx) < 0) {
311+
DEBUG_PRINTK("Deobfuscation failed, dropping packet");
312+
update_metrics(backend_index, METRIC_UPSTREAM, pkt_len, 1, METRIC_REASON_DROPPED);
313+
return XDP_DROP;
314+
}
315+
316+
__u32 tx_pkt_len = (void *)(long)xdp_ctx->data_end - (void *)(long)xdp_ctx->data;
317+
318+
__u32 dst_addr = bpf_ntohl(ctx.ip->daddr);
319+
__u32 client_ip = bpf_ntohl(original_conn.client_ip);
320+
__u16 server_port = original_conn.server_port;
321+
__u16 client_port = original_conn.client_port;
322+
323+
update_metrics(backend_index, METRIC_DOWNSTREAM, tx_pkt_len, 0, METRIC_REASON_FORWARDED);
324+
return forward_packet(&ctx, dst_addr, server_port, client_ip, client_port);
325+
}
326+
329327
DEBUG_PRINTK("No matching handler for WG packet, passing through");
330328
return XDP_PASS;
331329
}

ebpf/wg_forward_proxy_test.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,40 @@ func TestMultipleBackends(t *testing.T) {
332332
}
333333
}
334334

335+
func TestBackendCustomPort(t *testing.T) {
336+
spec, err := LoadWgForwardProxy()
337+
if err != nil {
338+
t.Fatalf("Failed to load spec: %v", err)
339+
}
340+
341+
setVar(t, spec, "__cfg_xor_enabled", false)
342+
setVar(t, spec, "__cfg_wg_port", uint16(wgPort))
343+
344+
objs := &WgForwardProxyObjects{}
345+
if err := spec.LoadAndAssign(objs, nil); err != nil {
346+
t.Fatalf("Failed to load objects: %v", err)
347+
}
348+
defer objs.Close()
349+
350+
if err := configureBackends(objs, []config.BackendServer{
351+
{IP: "10.0.0.1", Port: 11580},
352+
}); err != nil {
353+
t.Fatalf("Failed to configure backends: %v", err)
354+
}
355+
356+
packet := createWGPacket("192.168.1.1", "192.168.1.2", 12345, wgPort)
357+
result, outputPacket, err := objs.WgForwardProxy.Test(packet)
358+
if err != nil {
359+
t.Fatalf("Failed to run program: %v", err)
360+
}
361+
362+
if int(result) != xdpRedirect {
363+
t.Errorf("Expected XDP_REDIRECT, got %d", result)
364+
}
365+
366+
verifyPacket(t, outputPacket, "10.0.0.1", 11580)
367+
}
368+
335369
func TestWgPortConfig(t *testing.T) {
336370
tests := []struct {
337371
name string
@@ -568,5 +602,20 @@ func configureBackends(objs *WgForwardProxyObjects, backends []config.BackendSer
568602

569603
countKey := uint32(0)
570604
count := uint32(len(backends)) //nolint:gosec // G304: it's fine
571-
return objs.BackendCount.Put(&countKey, &count)
605+
if err := objs.BackendCount.Put(&countKey, &count); err != nil {
606+
return err
607+
}
608+
609+
dummy := uint8(1)
610+
for _, backend := range backends {
611+
port := backend.Port
612+
if port == 0 {
613+
port = wgPort
614+
}
615+
if err := objs.BackendPortSet.Put(&port, &dummy); err != nil {
616+
return err
617+
}
618+
}
619+
620+
return nil
572621
}

ebpf/wg_reverse_proxy_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,79 @@ func createObfuscatedAndPaddedWGPacket(srcIP, dstIP string, srcPort, dstPort uin
451451
return packet
452452
}
453453

454+
func TestReverseProxyWgPortConfig(t *testing.T) {
455+
xorKey := "test-key-1234567890abcdef12345678"
456+
keyBytes := []byte(xorKey)
457+
var keyArray [32]byte
458+
copy(keyArray[:], keyBytes)
459+
460+
tests := []struct {
461+
name string
462+
wgPort uint16
463+
packetDstPort uint16
464+
shouldProcess bool
465+
}{
466+
{
467+
name: "default_port",
468+
wgPort: 51820,
469+
packetDstPort: 51820,
470+
shouldProcess: true,
471+
},
472+
{
473+
name: "custom_port",
474+
wgPort: 11580,
475+
packetDstPort: 11580,
476+
shouldProcess: true,
477+
},
478+
{
479+
name: "wrong_port",
480+
wgPort: 51820,
481+
packetDstPort: 9999,
482+
shouldProcess: false,
483+
},
484+
}
485+
486+
for _, tt := range tests {
487+
t.Run(tt.name, func(t *testing.T) {
488+
spec, err := LoadWgReverseProxy()
489+
if err != nil {
490+
t.Fatalf("Failed to load spec: %v", err)
491+
}
492+
493+
if err := spec.Variables["__cfg_xor_enabled"].Set(true); err != nil {
494+
t.Fatalf("Failed to set xor_enabled: %v", err)
495+
}
496+
if err := spec.Variables["__cfg_xor_key"].Set(keyArray); err != nil {
497+
t.Fatalf("Failed to set xor_key: %v", err)
498+
}
499+
if err := spec.Variables["__cfg_padding_enabled"].Set(false); err != nil {
500+
t.Fatalf("Failed to set padding_enabled: %v", err)
501+
}
502+
if err := spec.Variables["__cfg_wg_port"].Set(tt.wgPort); err != nil {
503+
t.Fatalf("Failed to set wg_port: %v", err)
504+
}
505+
506+
objs := &WgReverseProxyObjects{}
507+
if err := spec.LoadAndAssign(objs, nil); err != nil {
508+
t.Fatalf("Failed to load objects: %v", err)
509+
}
510+
defer objs.Close()
511+
512+
inputPacket := createObfuscatedWGPacket("192.168.1.1", "192.168.1.2", 12345, tt.packetDstPort, keyBytes)
513+
_, outputPacket, err := objs.WgReverseProxy.Test(inputPacket)
514+
if err != nil {
515+
t.Fatalf("Failed to run program: %v", err)
516+
}
517+
518+
if tt.shouldProcess {
519+
verifyXORDeobfuscation(t, inputPacket, outputPacket, keyBytes)
520+
} else {
521+
verifyPayloadUnchanged(t, inputPacket, outputPacket)
522+
}
523+
})
524+
}
525+
}
526+
454527
// TestReverseProxyPaddingObfuscateMTUExceededDrop verifies that a FROM_WG packet is dropped
455528
// when adding padding would cause it to exceed the configured link MTU.
456529
func TestReverseProxyPaddingObfuscateMTUExceededDrop(t *testing.T) {

ebpf/wgforwardproxy_bpfeb.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ebpf/wgforwardproxy_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)