/*
* Header for OpenPPP2 system NAT (eBPF TC based NAT).
*
* This API provides functions to attach/detach an eBPF program to the TC egress
* hook of a network interface, and to manage NAT rules in a pinned BPF map.
*
* The library assumes single-process usage and does not include cross-process
* locking or reference counting. It is the caller's responsibility to ensure
* proper usage (e.g., not attaching to multiple interfaces simultaneously).
*/
#ifdef SYSNAT
#ifndef OPENPPP2_SYSNAT_H
#define OPENPPP2_SYSNAT_H
#include <stdint.h>
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Error codes */
#define ERR_IFINDEX -1 /* Invalid interface name */
#define ERR_BPF_OPEN -2 /* Failed to open BPF object file */
#define ERR_BPF_PROG -3 /* Program not found in object */
#define ERR_BPF_LOAD -4 /* BPF object loading failed */
#define ERR_MAP_PIN -5 /* Failed to set map pin path */
#define ERR_TC_CREATE -6 /* Failed to create TC hook */
#define ERR_TC_ATTACH -7 /* Failed to attach TC program */
#define ERR_TC_DETACH -8 /* Failed to detach TC program */
#define ERR_MAP_OPEN -9 /* Failed to open pinned map */
#define ERR_MAP_UPDATE -10 /* Failed to update map element */
#define ERR_MAP_DELETE -11 /* Failed to delete map element */
/*
* NAT key structure (matches eBPF map key).
* All IP addresses and ports are in network byte order.
*/
struct openppp2_sysnat_key {
uint32_t src_ip; /* Source IP address */
uint32_t dst_ip; /* Destination IP address */
uint16_t src_port; /* Source port */
uint16_t dst_port; /* Destination port */
uint16_t proto; /* IP protocol (e.g., IPPROTO_TCP, IPPROTO_UDP) */
uint16_t pad; /* Padding to align to 8 bytes */
} __attribute__((packed));
/*
* NAT value structure (matches eBPF map value).
* All IP addresses and ports are in network byte order.
*/
struct openppp2_sysnat_value {
uint32_t new_src_addr; /* New source IP address */
uint16_t new_src_port; /* New source port */
uint32_t new_dst_addr; /* New destination IP address */
uint16_t new_dst_port; /* New destination port */
int32_t redirect_ifindex; /* 0 = no redirect, else interface index */
uint8_t pad[4]; /* Padding for alignment */
} __attribute__((packed));
/*
* Mount the BPF filesystem if not already mounted.
* This function is automatically called by attach(), but may be called
* explicitly to ensure the BPF filesystem is available early.
*
* Returns:
* 0 on success
* -1 on failure (errno set)
*/
int openppp2_sysnat_mount(void);
/*
* Attach the eBPF TC egress NAT program to the given network interface.
*
* The BPF object file at `bpf_obj_path` must contain a program named
* "tc_egress" and a map named "openppp2_sysnat_rules".
*
* If the map is already pinned at MAP_PIN_PATH, this function assumes the
* program is already attached and returns success without reloading.
*
* Returns:
* 0 on success
* one of the ERR_xxx codes on failure
*/
int openppp2_sysnat_attach(const char* ifname, const char* bpf_obj_path);
/*
* Detach the eBPF TC egress NAT program from the network interface.
*
* This function removes the TC program, unpins the map, and cleans up the
* TC hook. If the map is not present, it returns success.
*
* Returns:
* 0 on success
* one of the ERR_xxx codes on failure
*/
int openppp2_sysnat_detach(const char* ifname);
/*
* Check whether the NAT program is currently attached.
*
* This function checks the existence of the pinned map file.
* It does not verify the actual TC attachment state.
*
* Returns:
* 1 if attached (map exists)
* 0 if not attached
*/
int openppp2_sysnat_is_attached(void);
/*
* Add a NAT rule to the map.
*
* The map must be already pinned (i.e., the program attached).
*
* Returns:
* 0 on success
* ERR_MAP_OPEN if the map is not found
* ERR_MAP_UPDATE if the update operation fails
*/
int openppp2_sysnat_add_rule(const struct openppp2_sysnat_key* key, const struct openppp2_sysnat_value* val);
/*
* Delete a NAT rule from the map.
*
* Returns:
* 0 on success
* ERR_MAP_OPEN if the map is not found
* ERR_MAP_DELETE if the deletion operation fails
*/
int openppp2_sysnat_del_rule(const struct openppp2_sysnat_key* key);
#ifdef __cplusplus
}
#endif
#endif /* OPENPPP2_SYSNAT_H */
#endif
openppp2_sysnat.c
cpp复制代码
/*
* Implementation of OpenPPP2 system NAT (eBPF TC based NAT).
*
* Provides functions to attach/detach an eBPF program to the TC egress hook
* of a network interface, and to manage NAT rules stored in a pinned BPF map.
*
* The implementation is intended for single-process use and does not include
* cross-process locking or reference counting.
*/
#ifdef SYSNAT
#include "openppp2_sysnat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <mntent.h>
#include <net/if.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
/* Fixed pin path for the NAT rule map (also used as attach flag) */
#define MAP_PIN_PATH "/sys/fs/bpf/openppp2_sysnat_rules"
/* Check if a filesystem is mounted at the given path */
static int is_fs_mounted(const char* path, const char* fstype) {
FILE* fp = setmntent("/proc/mounts", "r");
if (!fp) {
return 0;
}
struct mntent* mnt;
while ((mnt = getmntent(fp)) != NULL) {
if (strcmp(mnt->mnt_dir, path) == 0 && strcmp(mnt->mnt_type, fstype) == 0) {
endmntent(fp);
return 1;
}
}
endmntent(fp);
return 0;
}
/* Ensure a directory exists, create if missing */
static int ensure_dir(const char* path) {
struct stat st;
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
return 0;
}
errno = ENOTDIR;
return -1;
}
if (errno == ENOENT) {
if (mkdir(path, 0755) == 0) {
return 0;
}
}
return -1;
}
/* Mount BPF filesystem if not already mounted */
int openppp2_sysnat_mount(void) {
const char* bpf_path = "/sys/fs/bpf";
if (ensure_dir(bpf_path) != 0) {
return -1;
}
if (!is_fs_mounted(bpf_path, "bpf")) {
if (mount("none", bpf_path, "bpf", 0, NULL) != 0) {
if (errno != EBUSY && errno != EEXIST) {
return -1;
}
}
}
return 0;
}
/* Check if the pinned map exists */
static int map_exists(void) {
int fd = bpf_obj_get(MAP_PIN_PATH);
if (fd >= 0) {
close(fd);
return 1;
}
return 0;
}
/* Delete the pinned map file */
static void delete_map_pin(void) {
unlink(MAP_PIN_PATH);
}
/* Remove the TC hook from the interface (best effort) */
static void delete_tc_hook(unsigned int ifindex) {
struct bpf_tc_hook hook = {
.sz = sizeof(hook),
.ifindex = ifindex,
.attach_point = BPF_TC_EGRESS,
};
(void)bpf_tc_hook_destroy(&hook);
}
/* Attach the eBPF TC egress program to the interface */
int openppp2_sysnat_attach(const char* ifname, const char* bpf_obj_path) {
struct bpf_tc_hook hook = { .sz = sizeof(hook) };
struct bpf_tc_opts opts = { .sz = sizeof(opts) };
struct bpf_object* obj = NULL;
struct bpf_program* prog = NULL;
struct bpf_map* map = NULL;
unsigned int ifindex;
int err = 0;
int map_pinned = 0;
if (openppp2_sysnat_mount() != 0) {
return ERR_BPF_OPEN;
}
ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
return ERR_IFINDEX;
}
/* Remove any stale map file from previous crashes */
delete_map_pin();
/* Load BPF object */
obj = bpf_object__open_file(bpf_obj_path, NULL);
if (libbpf_get_error(obj)) {
err = ERR_BPF_OPEN;
goto cleanup;
}
prog = bpf_object__find_program_by_name(obj, "tc_egress");
if (!prog) {
err = ERR_BPF_PROG;
goto cleanup;
}
bpf_program__set_type(prog, BPF_PROG_TYPE_SCHED_CLS);
map = bpf_object__find_map_by_name(obj, "openppp2_sysnat_rules");
if (!map) {
err = ERR_MAP_PIN;
goto cleanup;
}
if (bpf_map__set_pin_path(map, MAP_PIN_PATH) != 0) {
err = ERR_MAP_PIN;
goto cleanup;
}
if (bpf_object__load(obj) != 0) {
err = ERR_BPF_LOAD;
goto cleanup;
}
map_pinned = 1;
/* Create TC hook */
hook.ifindex = ifindex;
hook.attach_point = BPF_TC_EGRESS;
int ret = bpf_tc_hook_create(&hook);
if (ret != 0 && ret != -EEXIST) {
err = ERR_TC_CREATE;
goto cleanup;
}
/* Attach program */
opts.prog_fd = bpf_program__fd(prog);
opts.handle = 1;
opts.priority = 1;
opts.flags = BPF_TC_F_REPLACE;
if (bpf_tc_attach(&hook, &opts) != 0) {
err = ERR_TC_ATTACH;
goto cleanup;
}
err = 0;
cleanup:
if (err != 0) {
if (map_pinned) {
delete_map_pin();
}
if (ifindex) {
delete_tc_hook(ifindex);
}
}
if (obj)
bpf_object__close(obj);
return err;
}
/* Detach the eBPF TC egress program from the interface */
int openppp2_sysnat_detach(const char* ifname) {
struct bpf_tc_hook hook = { .sz = sizeof(hook) };
struct bpf_tc_opts opts = { .sz = sizeof(opts) };
unsigned int ifindex;
ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
return ERR_IFINDEX;
}
if (!map_exists()) {
return 0;
}
hook.ifindex = ifindex;
hook.attach_point = BPF_TC_EGRESS;
opts.prog_fd = 0;
opts.handle = 1;
opts.priority = 1;
int rc = bpf_tc_detach(&hook, &opts);
if (rc != 0 && rc != -ENOENT) {
return ERR_TC_DETACH;
}
delete_map_pin();
delete_tc_hook(ifindex);
return 0;
}
/* Check if attached (by map existence) */
int openppp2_sysnat_is_attached(void) {
return map_exists() ? 1 : 0;
}
/* Add a NAT rule to the pinned map */
int openppp2_sysnat_add_rule(const struct openppp2_sysnat_key* key, const struct openppp2_sysnat_value* val) {
int map_fd = bpf_obj_get(MAP_PIN_PATH);
if (map_fd < 0) {
return ERR_MAP_OPEN;
}
int ret = bpf_map_update_elem(map_fd, key, val, BPF_ANY);
close(map_fd);
return (ret == 0) ? 0 : ERR_MAP_UPDATE;
}
/* Delete a NAT rule from the pinned map */
int openppp2_sysnat_del_rule(const struct openppp2_sysnat_key* key) {
int map_fd = bpf_obj_get(MAP_PIN_PATH);
if (map_fd < 0) {
return ERR_MAP_OPEN;
}
int ret = bpf_map_delete_elem(map_fd, key);
close(map_fd);
return (ret == 0) ? 0 : ERR_MAP_DELETE;
}
#endif