This repository contains eBPF kfuncs to interact with the eBPF kernel module and an example eBPF XDP and TC program that uses them to encrypt and decrypt WireGuard packets.
The following kfuncs provide access to WireGuard device and peer references, as well as encryption/decryption helpers inside eBPF programs.
The eBPF verifier will reject the program if the eBPF program attempts to dereference or doesn't release an acquired reference before exiting the program. The __sz and __ksz suffixes inside the parameter names are used as memory/size annotations for the verifier.
struct wg_device *bpf_xdp_wg_device_get_by_index(struct xdp_md *ctx, u32 ifindex)
struct wg_device *bpf_skb_wg_device_get_by_index(struct __sk_buff *ctx, u32 ifindex)
- Parameters:
ctx: XDP/TC program contextifindex: Network interface index
- Returns: Pointer to a
wg_deviceorNULLif not found. - Description:
Acquire a reference to the WireGuard device bound to the given interface index.
Must be released before the program exits withbpf_wg_device_put.
struct wg_device *bpf_skb_wg_device_get_by_port(struct xdp_md *ctx, u16 port)
struct wg_device *bpf_skb_wg_device_get_by_port(struct __sk_buff *ctx, u16 port)
- Parameters:
ctx: XDP/TC program contextport: UDP port
- Returns: Pointer to a
wg_deviceorNULLif not found. - Description:
Acquire a reference to the WireGuard device bound to the given UDP port.
Must be released later withbpf_wg_device_put.
- Parameters:
wg: WireGuard device referenceaddr: IPv4/IPv6 address pointeraddr__sz: Size of the address (4 for IPv4, 16 for IPv6)
- Returns: Pointer to a
wg_peerorNULLif not found. - Description:
Look up a WireGuard peer from the plaintext source IP address. Must be released before the program exits withbpf_wg_peer_put.
- Parameters:
wg: WireGuard device referenceidx: Receiver index (from encrypted packet header)
- Returns: Pointer to a
wg_peerorNULLif not found. - Description:
Look up a WireGuard peer from the receiver index of an incoming encrypted packet. Must be released before the program exits withbpf_wg_peer_put.
- Parameters:
peer: WireGuard peer referencetuple: Output buffer for socket tuple (IP + port)tuple__ksz: Size oftuple
- Returns:
0on success, negative error code otherwise. - Description:
Retrieve the UDP endpoint information (source/destination IP and port) used by the peer.
int bpf_xdp_wg_encrypt(struct xdp_md *ctx, u32 offset, u32 length, struct wg_peer *peer)
int bpf_skb_wg_encrypt(struct __sk_buff *ctx, u32 offset, u32 length, struct wg_peer *peer)
- Parameters:
ctx: XDP/TC program contextoffset: Plaintext data offsetlength: Data lengthpeer: WireGuard peer reference
- Returns:
0on success, negative error code otherwise. - Description:
Encrypts data in the packet starting atoffsetwith lengthlength, using the peer’s sending key.
Pushes a WireGuard header and increments the peer’s sending counter.
int bpf_xdp_wg_decrypt(struct xdp_md *ctx, u32 offset, u32 length, struct wg_peer *peer)
int bpf_skb_wg_decrypt(struct __sk_buff *ctx, u32 offset, u32 length, struct wg_peer *peer)
- Parameters:
ctx: XDP/TC program contextoffset: WireGuard header offsetlength: Data lengthpeer: WireGuard peer reference
- Returns:
0on success, negative error code otherwise. - Description:
Decrypts data in the packet starting atoffsetwith lengthlength, using the peer’s receiving key.
Validates and updates the peer’s receive counter.
- Release a previously acquired WireGuard device reference.
- Release a previously acquired WireGuard peer reference.
To compile the program, switch to the src/ directory and execute make. This will generate a little endian wg_le.o and big endian wg_be.o eBPF object inside the obj/ directory.
$ cd src/
$ make
clang -O2 -g -Wall -target bpf -std=gnu23 -mlittle-endian -c wg.c -o obj/wg_le.o
clang -O2 -g -Wall -target bpf -std=gnu23 -mbig-endian -c wg.c -o obj/wg_be.o
To attach the eBPF/XDP program to a network interface, you can use the ip command line tool:
$ ip link set <ifname> xdp obj wg_*e.o program xdp_wg
To attach the eBPF/TC program to a network interface, you can use the tc command line tool:
$ tc qdisc add dev <ifname> clsact
$ tc filter add dev <ifname> ingress bpf da obj wg_*e.o program tc_wg
The following flow chart shows how the eBPF program uses the Kernel API/kfuncs to encrypt or decrypt incoming packets:
flowchart TD
A([Parse packet header]) --> B["bpf_fib_lookup()"]
B --> |BPF_FIB_LKUP_RET_SUCCESS| C["bpf_{xdp,skb}_wg_device_get_by_index()"]
B --> |BPF_FIB_LKUP_RET_NOT_FWDED| D["bpf_{xdp,skb}_wg_device_get_by_port()"]
%% Encryption branch
C --> C1["bpf_wg_peer_allowedips_lookup()"]
C1 --> C2["bpf_wg_endpoint_tuple_get()"]
C2 --> C3["bpf_{xdp,skb}_wg_encrypt()"]
C3 --> V([Create UDP Tunnel])
%% Decryption branch
D --> D1["bpf_wg_peer_hashtable_lookup()"]
D1 --> D2["bpf_{xdp,skb}_wg_decrypt()"]
D2 --> D3["bpf_wg_peer_allowedips_lookup()"]
D3 --> W
V --> W["bpf_wg_peer_put()"]
W --> X["bpf_wg_device_put()"]
X --> Y["bpf_fib_lookup()"]
Y --> Z["bpf_redirect()"]