Merge pull request #57 from gamemann/20250223-morerestructuring

More Restructuring & Cleanup
This commit is contained in:
Christian Deacon
2025-02-23 07:56:39 -05:00
committed by GitHub
18 changed files with 602 additions and 492 deletions

View File

@@ -12,6 +12,8 @@ COMMON_DIR = $(SRC_DIR)/common
LOADER_DIR = $(SRC_DIR)/loader
XDP_DIR = $(SRC_DIR)/xdp
ETC_DIR = /etc/xdpfw
# Additional build directories.
BUILD_LOADER_DIR = $(BUILD_DIR)/loader
BUILD_XDP_DIR = $(BUILD_DIR)/xdp
@@ -47,11 +49,17 @@ LOADER_UTILS_CONFIG_OBJ = config.o
LOADER_UTILS_CMDLINE_SRC = cmdline.c
LOADER_UTILS_CMDLINE_OBJ = cmdline.o
LOADER_UTILS_XDP_SRC = xdp.c
LOADER_UTILS_XDP_OBJ = xdp.o
LOADER_UTILS_STATS_SRC = stats.c
LOADER_UTILS_STATS_OBJ = stats.o
LOADER_UTILS_HELPERS_SRC = helpers.c
LOADER_UTILS_HELPERS_OBJ = helpers.o
# Loader objects.
LOADER_OBJS = $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CONFIG_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CMDLINE_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_HELPERS_OBJ)
LOADER_OBJS = $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CONFIG_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CMDLINE_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_XDP_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_STATS_OBJ) $(BUILD_LOADER_DIR)/$(LOADER_UTILS_HELPERS_OBJ)
ifeq ($(LIBBPF_LIBXDP_STATIC), 1)
LOADER_OBJS := $(LIBBPF_OBJS) $(LIBXDP_OBJS) $(LOADER_OBJS)
@@ -79,7 +87,7 @@ all: loader xdp
loader: loader_utils
$(CC) $(INCS) $(FLAGS) $(FLAGS_LOADER) -o $(BUILD_LOADER_DIR)/$(LOADER_OUT) $(LOADER_OBJS) $(LOADER_DIR)/$(LOADER_SRC)
loader_utils: loader_utils_config loader_utils_cmdline loader_utils_helpers
loader_utils: loader_utils_config loader_utils_cmdline loader_utils_helpers loader_utils_xdp loader_utils_stats
loader_utils_config:
$(CC) $(INCS) $(FLAGS) -c -o $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CONFIG_OBJ) $(LOADER_UTILS_DIR)/$(LOADER_UTILS_CONFIG_SRC)
@@ -87,6 +95,12 @@ loader_utils_config:
loader_utils_cmdline:
$(CC) $(INCS) $(FLAGS) -c -o $(BUILD_LOADER_DIR)/$(LOADER_UTILS_CMDLINE_OBJ) $(LOADER_UTILS_DIR)/$(LOADER_UTILS_CMDLINE_SRC)
loader_utils_xdp:
$(CC) $(INCS) $(FLAGS) -c -o $(BUILD_LOADER_DIR)/$(LOADER_UTILS_XDP_OBJ) $(LOADER_UTILS_DIR)/$(LOADER_UTILS_XDP_SRC)
loader_utils_stats:
$(CC) $(INCS) $(FLAGS) -c -o $(BUILD_LOADER_DIR)/$(LOADER_UTILS_STATS_OBJ) $(LOADER_UTILS_DIR)/$(LOADER_UTILS_STATS_SRC)
loader_utils_helpers:
$(CC) $(INCS) $(FLAGS) -c -o $(BUILD_LOADER_DIR)/$(LOADER_UTILS_HELPERS_OBJ) $(LOADER_UTILS_DIR)/$(LOADER_UTILS_HELPERS_SRC)
@@ -100,22 +114,23 @@ libxdp:
sudo $(MAKE) -C $(LIBBPF_SRC) install
sudo $(MAKE) -C $(LIBXDP_DIR) install
clean:
libxdp_clean:
$(MAKE) -C $(XDP_TOOLS_DIR) clean
$(MAKE) -C $(LIBBPF_SRC) clean
clean:
find $(BUILD_DIR) -type f ! -name ".*" -exec rm -f {} +
find $(BUILD_LOADER_DIR) -type f ! -name ".*" -exec rm -f {} +
find $(BUILD_XDP_DIR) -type f ! -name ".*" -exec rm -f {} +
install:
mkdir -p /etc/xdpfw/
cp -n xdpfw.conf.example /etc/xdpfw/xdpfw.conf
cp -f $(BUILD_LOADER_DIR)/$(LOADER_OUT) /usr/bin
cp -f $(BUILD_XDP_DIR)/$(XDP_OBJ) /etc/xdpfw
mkdir -p $(ETC_DIR)
cp -n xdpfw.conf.example $(ETC_DIR)/xdpfw.conf
cp -n other/xdpfw.service /etc/systemd/system/
cp -f $(BUILD_LOADER_DIR)/$(LOADER_OUT) /usr/bin
cp -f $(BUILD_XDP_DIR)/$(XDP_OBJ) $(ETC_DIR)
.PHONY: all libxdp
.DEFAULT: all

View File

@@ -1,9 +1,20 @@
#pragma once
// Feel free to comment this out if you don't want the `blocked` entry on the stats map to be incremented every single time a packet is dropped from the source IP being on the blocked map. Commenting this line out should increase performance when blocking malicious traffic.
// The maximum amount of filters allowed.
// Decrease this value if you receive errors related to the BPF program being too large.
#define MAX_FILTERS 60
// The maximum amount of IPs/flows to track stats for.
// The higher this value is, the more memory that'll be used.
#define MAX_TRACK_IPS 100000
// Feel free to comment this out if you don't want the `blocked` entry on the stats map to be incremented every single time a packet is dropped from the source IP being on the blocked map.
// Commenting this line out should increase performance when blocking malicious traffic.
#define DO_STATS_ON_BLOCK_MAP
// When this is defined, a check will occur inside the IPv4 and IPv6 filters. For IPv6 packets, if no IPv6 source/destination IP addresses are set, but there is an IPv4 address, it will ignore the filter. The same goes for IPv4, if there is no IPv4 source/destination IP addresses set, if an IPv6 address is set, it will ignore the filter.
// When this is defined, a check will occur inside the IPv4 and IPv6 filters.
// For IPv6 packets, if no IPv6 source/destination IP addresses are set, but there is an IPv4 address, it will ignore the filter.
// The same goes for IPv4, if there is no IPv4 source/destination IP addresses set, if an IPv6 address is set, it will ignore the filter.
#define ALLOW_SINGLE_IP_V4_V6
// If uncommented, rate limits for clients are determined using the source IP, port, and protocol instead of just the source IP.

View File

@@ -1,7 +1,5 @@
#pragma once
#define MAX_PCKT_LENGTH 65535
#define MAX_FILTERS 60
#define MAX_TRACK_IPS 100000
#define MAX_CPUS 256
#define NANO_TO_SEC 1000000000

View File

@@ -1,569 +1,202 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <linux/types.h>
#include <time.h>
#include <getopt.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/sysinfo.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <net/if.h>
#include <linux/if_link.h>
#include <arpa/inet.h>
#include <bpf.h>
#include <libbpf.h>
#include <xdp/libxdp.h>
#include <common/all.h>
#include <loader/utils/cmdline.h>
#include <loader/utils/config.h>
#include <loader/utils/xdp.h>
#include <loader/utils/stats.h>
#include <loader/utils/helpers.h>
// Other variables.
static u8 cont = 1;
static int filtersmap = -1;
static int statsmap = -1;
void SignalHndl(int tmp)
{
cont = 0;
}
/**
* Updates the filter's BPF map.
*
* @param cfg A pointer to the config structure.
*
* @return Void
*/
void UpdateFilters(config__t *cfg)
{
// Loop through all filters and delete the map. We do this in the case rules were edited and were put out of order since the key doesn't uniquely map to a specific rule.
for (u8 i = 0; i < MAX_FILTERS; i++)
{
u32 key = i;
bpf_map_delete_elem(filtersmap, &key);
}
// Add a filter to the filter maps.
for (u32 i = 0; i < MAX_FILTERS; i++)
{
// Check if we have a valid ID.
if (cfg->filters[i].id < 1)
{
break;
}
// Create value array (max CPUs in size) since we're using a per CPU map.
filter_t filter[MAX_CPUS];
memset(filter, 0, sizeof(filter));
for (int j = 0; j < MAX_CPUS; j++)
{
filter[j] = cfg->filters[i];
}
// Attempt to update BPF map.
if (bpf_map_update_elem(filtersmap, &i, &filter, BPF_ANY) == -1)
{
fprintf(stderr, "Error updating BPF item #%d\n", i);
}
}
}
/**
* Loads the config on the file system.
*
* @param cfg A pointer to the config structure.
* @param cfgfile The path to the config file.
*
* @return 0 on success or -1 on error.
*/
int LoadConfig(config__t *cfg, char *cfgfile)
{
// Open config file.
if (OpenCfg(cfgfile) != 0)
{
fprintf(stderr, "Error opening filters file: %s\n", cfgfile);
return -1;
}
SetCfgDefaults(cfg);
memset(cfg->filters, 0, sizeof(cfg->filters));
// Read config and check for errors.
if (ReadCfg(cfg) != 0)
{
fprintf(stderr, "Error reading filters file.\n");
return -1;
}
return 0;
}
/**
* Finds a BPF map's FD.
*
* @param prog A pointer to the XDP program structure.
* @param mapname The name of the map to retrieve.
*
* @return The map's FD.
*/
int FindMapFd(struct xdp_program *prog, const char *mapname)
{
int fd = -1;
struct bpf_object *obj = xdp_program__bpf_obj(prog);
if (obj == NULL)
{
fprintf(stderr, "Error finding BPF object from XDP program.\n");
goto out;
}
struct bpf_map *map = bpf_object__find_map_by_name(obj, mapname);
if (!map)
{
fprintf(stderr, "Error finding eBPF map: %s\n", mapname);
goto out;
}
fd = bpf_map__fd(map);
out:
return fd;
}
/**
* Loads a BPF object file.
*
* @param filename The path to the BPF object file.
*
* @return XDP program structure (pointer) or NULL.
*/
struct xdp_program *LoadBpfObj(const char *filename)
{
struct xdp_program *prog = xdp_program__open_file(filename, "xdp_prog", NULL);
if (prog == NULL)
{
// The main function handles this error.
return NULL;
}
return prog;
}
/**
* Attempts to attach or detach (progfd = -1) a BPF/XDP program to an interface.
*
* @param prog A pointer to the XDP program structure.
* @param ifidx The index to the interface to attach to.
* @param detach If above 0, attempts to detach XDP program.
* @param cmd A pointer to a cmdline struct that includes command line arguments (mostly checking for offload/HW mode set).
*
* @return 0 on success and 1 on error.
*/
int AttachXdp(struct xdp_program *prog, int ifidx, u8 detach, cmdline_t *cmd)
{
int err;
u32 mode = XDP_MODE_NATIVE;
char *smode;
smode = "DRV/native";
if (cmd->offload)
{
smode = "HW/offload";
mode = XDP_MODE_HW;
}
else if (cmd->skb)
{
smode = "SKB/generic";
mode = XDP_MODE_SKB;
}
u8 exit = 0;
while (!exit)
{
// Try loading program with current mode.
int err;
if (detach)
{
err = xdp_program__detach(prog, ifidx, mode, 0);
}
else
{
err = xdp_program__attach(prog, ifidx, mode, 0);
}
if (err)
{
if (err)
{
fprintf(stderr, "Could not attach with mode %s (%s) (%d).\n", smode, strerror(-err), -err);
}
// Decrease mode.
switch (mode)
{
case XDP_MODE_HW:
mode = XDP_MODE_NATIVE;
smode = "DRV/native";
break;
case XDP_MODE_NATIVE:
mode = XDP_MODE_SKB;
smode = "SKB/generic";
break;
case XDP_MODE_SKB:
// Exit loop.
exit = 1;
smode = NULL;
break;
}
// Retry.
continue;
}
// Success, so break current loop.
break;
}
// If exit is set to 1 or smode is NULL, it indicates full failure.
if (exit || smode == NULL)
{
fprintf(stderr, "Error attaching XDP program.\n");
return 1;
}
fprintf(stdout, "Loaded XDP program on mode %s.\n", smode);
return 0;
}
struct stat conf_stat;
int cont = 1;
int main(int argc, char *argv[])
{
int ret;
// Parse the command line.
cmdline_t cmd =
{
.cfgfile = "/etc/xdpfw/xdpfw.conf",
.help = 0,
.list = 0,
.offload = 0
};
cmdline_t cmd = {0};
cmd.cfgfile = CONFIG_DEFAULT_PATH;
ParseCommandLine(&cmd, argc, argv);
// Check for help menu.
// Check for help.
if (cmd.help)
{
fprintf(stdout, "Usage:\n" \
"--config -c => Config file location (default is /etc/xdpfw/xdpfw.conf).\n" \
"--offload -o => Tries to load the XDP program in hardware/offload mode.\n" \
"--skb -s => Force the XDP program to load with SKB mode instead of DRV.\n" \
"--time -t => How long to run the program for in seconds before exiting. 0 or not set = infinite.\n" \
"--list -l => Print config details including filters (this will exit program after done).\n" \
"--help -h => Print help menu.\n");
PrintHelpMenu();
return EXIT_SUCCESS;
}
// Raise RLimit.
struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
if (setrlimit(RLIMIT_MEMLOCK, &rl))
{
fprintf(stderr, "Error setting rlimit.\n");
fprintf(stderr, "[ERROR] Failed to raise rlimit. Please make sure this program is ran as root!\n");
return EXIT_FAILURE;
}
// Check for --config argument.
if (cmd.cfgfile == NULL)
{
// Assign default.
cmd.cfgfile = "/etc/xdpfw/xdpfw.conf";
}
// Initialize config.
config__t cfg = {0};
SetCfgDefaults(&cfg);
// Update config.
LoadConfig(&cfg, cmd.cfgfile);
// Check for list option.
if (cmd.list)
// Load config.
if ((ret = LoadConfig(&cfg, cmd.cfgfile)) != 0)
{
fprintf(stdout, "Current Settings:\n");
fprintf(stdout, "Interface Name => %s\n", cfg.interface);
fprintf(stdout, "Update Time => %d\n", cfg.updatetime);
fprintf(stdout, "Stdout Update Time => %d\n\n", cfg.stdout_update_time);
for (uint16_t i = 0; i < MAX_FILTERS; i++)
{
filter_t *filter = &cfg.filters[i];
if (filter->id < 1)
{
break;
}
fprintf(stdout, "Filter #%d:\n", (i + 1));
// Main.
fprintf(stdout, "\tID => %d\n", filter->id);
fprintf(stdout, "\tEnabled => %d\n", filter->enabled);
fprintf(stdout, "\tAction => %d (0 = Block, 1 = Allow).\n\n", filter->action);
// IP Options.
fprintf(stdout, "\tIP Options\n");
// IP addresses require additional code for string printing.
struct sockaddr_in sin;
sin.sin_addr.s_addr = filter->src_ip;
fprintf(stdout, "\t\tSource IPv4 => %s\n", inet_ntoa(sin.sin_addr));
fprintf(stdout, "\t\tSource CIDR => %d\n", filter->src_cidr);
struct sockaddr_in din;
din.sin_addr.s_addr = filter->dst_ip;
fprintf(stdout, "\t\tDestination IPv4 => %s\n", inet_ntoa(din.sin_addr));
fprintf(stdout, "\t\tDestination CIDR => %d\n", filter->dst_cidr);
struct in6_addr sin6;
memcpy(&sin6, &filter->src_ip6, sizeof(sin6));
char srcipv6[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &sin6, srcipv6, sizeof(srcipv6));
fprintf(stdout, "\t\tSource IPv6 => %s\n", srcipv6);
struct in6_addr din6;
memcpy(&din6, &filter->dst_ip6, sizeof(din6));
char dstipv6[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &din6, dstipv6, sizeof(dstipv6));
fprintf(stdout, "\t\tDestination IPv6 => %s\n", dstipv6);
// Other IP header information.
fprintf(stdout, "\t\tMax Length => %d\n", filter->max_len);
fprintf(stdout, "\t\tMin Length => %d\n", filter->min_len);
fprintf(stdout, "\t\tMax TTL => %d\n", filter->max_ttl);
fprintf(stdout, "\t\tMin TTL => %d\n", filter->min_ttl);
fprintf(stdout, "\t\tTOS => %d\n", filter->tos);
fprintf(stdout, "\t\tPPS => %llu\n", filter->pps);
fprintf(stdout, "\t\tBPS => %llu\n", filter->bps);
fprintf(stdout, "\t\tBlock Time => %llu\n\n", filter->blocktime);
// TCP Options.
fprintf(stdout, "\tTCP Options\n");
fprintf(stdout, "\t\tTCP Enabled => %d\n", filter->tcpopts.enabled);
fprintf(stdout, "\t\tTCP Source Port => %d\n", filter->tcpopts.sport);
fprintf(stdout, "\t\tTCP Destination Port => %d\n", filter->tcpopts.dport);
fprintf(stdout, "\t\tTCP URG Flag => %d\n", filter->tcpopts.urg);
fprintf(stdout, "\t\tTCP ACK Flag => %d\n", filter->tcpopts.ack);
fprintf(stdout, "\t\tTCP RST Flag => %d\n", filter->tcpopts.rst);
fprintf(stdout, "\t\tTCP PSH Flag => %d\n", filter->tcpopts.psh);
fprintf(stdout, "\t\tTCP SYN Flag => %d\n", filter->tcpopts.syn);
fprintf(stdout, "\t\tTCP FIN Flag => %d\n", filter->tcpopts.fin);
fprintf(stdout, "\t\tTCP ECE Flag => %d\n", filter->tcpopts.ece);
fprintf(stdout, "\t\tTCP CWR Flag => %d\n\n", filter->tcpopts.cwr);
// UDP Options.
fprintf(stdout, "\tUDP Options\n");
fprintf(stdout, "\t\tUDP Enabled => %d\n", filter->udpopts.enabled);
fprintf(stdout, "\t\tUDP Source Port => %d\n", filter->udpopts.sport);
fprintf(stdout, "\t\tUDP Destination Port => %d\n\n", filter->udpopts.dport);
// ICMP Options.
fprintf(stdout, "\tICMP Options\n");
fprintf(stdout, "\t\tICMP Enabled => %d\n", filter->icmpopts.enabled);
fprintf(stdout, "\t\tICMP Code => %d\n", filter->icmpopts.code);
fprintf(stdout, "\t\tICMP Type => %d\n", filter->icmpopts.type);
fprintf(stdout, "\n\n");
}
return EXIT_SUCCESS;
}
// Get device.
int ifidx;
if ((ifidx = if_nametoindex(cfg.interface)) < 0)
{
fprintf(stderr, "Error finding device %s.\n", cfg.interface);
fprintf(stderr, "[ERROR] Failed to load config from file system (%s)(%d).\n", cmd.cfgfile, ret);
return EXIT_FAILURE;
}
// XDP variables.
const char *filename = "/etc/xdpfw/xdp_prog.o";
// Check for list option.
if (cmd.list)
{
PrintConfig(&cfg);
return EXIT_SUCCESS;
}
// Get interface index.
int ifidx = if_nametoindex(cfg.interface);
if (ifidx < 0)
{
fprintf(stderr, "[ERROR] Failed to retrieve index of network interface '%s'.\n", cfg.interface);
return EXIT_FAILURE;
}
// Load BPF object.
struct xdp_program *prog = LoadBpfObj(filename);
struct xdp_program *prog = LoadBpfObj(XDP_OBJ_PATH);
if (prog == NULL)
{
fprintf(stderr, "Error loading eBPF object file. File name => %s.\n", filename);
fprintf(stderr, "[ERROR] Failed to load eBPF object file. Object path => %s.\n", XDP_OBJ_PATH);
return EXIT_FAILURE;
}
// Attach XDP program.
if (AttachXdp(prog, ifidx, 0, &cmd))
if ((ret = AttachXdp(prog, ifidx, 0, &cmd)) != 0)
{
fprintf(stderr, "[ERROR] Failed to attach XDP program to interface '%s' (%d).\n", cfg.interface, ret);
return EXIT_FAILURE;
}
// Retrieve BPF maps.
filtersmap = FindMapFd(prog, "filters_map");
statsmap = FindMapFd(prog, "stats_map");
int filters_map = FindMapFd(prog, "filters_map");
// Check for valid maps.
if (filtersmap < 0)
if (filters_map < 0)
{
fprintf(stderr, "Error finding 'filters_map' BPF map.\n");
fprintf(stderr, "[ERROR] Failed to find 'filters_map' BPF map.\n");
return EXIT_FAILURE;
}
if (statsmap < 0)
int stats_map = FindMapFd(prog, "stats_map");
if (stats_map < 0)
{
fprintf(stderr, "Error finding 'stats_map' BPF map.\n");
fprintf(stderr, "[ERROR] Failed to find 'stats_map' BPF map.\n");
return EXIT_FAILURE;
}
// Update BPF maps.
UpdateFilters(&cfg);
UpdateFilters(filters_map, &cfg);
// Signal.
signal(SIGINT, SignalHndl);
signal(SIGTERM, SignalHndl);
// Receive CPU count for stats map parsing.
int cpus = get_nprocs_conf();
unsigned int endTime = (cmd.time > 0) ? time(NULL) + cmd.time : 0;
unsigned int end_time = (cmd.time > 0) ? time(NULL) + cmd.time : 0;
// Create last updated variable.
time_t lastupdatecheck = time(NULL);
time_t lastupdated = time(NULL);
// Create last updated variables.
time_t last_update_check = time(NULL);
time_t last_config_check = time(NULL);
unsigned int sleep_time = cfg.stdout_update_time * 1000;
struct stat conf_stat;
while (cont)
{
// Get current time.
time_t curTime = time(NULL);
time_t cur_time = time(NULL);
// Check if we should end the program.
if (endTime > 0 && curTime >= endTime)
if (end_time > 0 && cur_time >= end_time)
{
break;
}
// Check for auto-update.
if (cfg.updatetime > 0 && (curTime - lastupdatecheck) > cfg.updatetime)
if (cfg.updatetime > 0 && (cur_time - last_update_check) > cfg.updatetime)
{
// Check if config file have been modified
if (stat(cmd.cfgfile, &conf_stat) == 0 && conf_stat.st_mtime > lastupdated) {
if (stat(cmd.cfgfile, &conf_stat) == 0 && conf_stat.st_mtime > last_config_check) {
// Memleak fix for strdup() in LoadConfig()
// Before updating it again, we need to free the old return value
free(cfg.interface);
// Update config.
LoadConfig(&cfg, cmd.cfgfile);
if ((ret = LoadConfig(&cfg, cmd.cfgfile)) != 0)
{
fprintf(stderr, "[WARNING] Failed to load config after update check (%d)\n", ret);
}
// Update BPF maps.
UpdateFilters(&cfg);
UpdateFilters(filters_map, &cfg);
// Update timer
lastupdated = time(NULL);
last_config_check = time(NULL);
}
// Update last updated variable.
lastupdatecheck = time(NULL);
last_update_check = time(NULL);
}
// Update stats.
// Calculate and display stats if enabled.
if (!cfg.nostats)
{
u32 key = 0;
stats_t stats[MAX_CPUS];
memset(stats, 0, sizeof(stats));
u64 allowed = 0;
u64 dropped = 0;
u64 passed = 0;
if (bpf_map_lookup_elem(statsmap, &key, stats) != 0)
if (CalculateStats(stats_map, cpus))
{
fprintf(stderr, "Error performing stats map lookup. Stats map FD => %d.\n", statsmap);
continue;
fprintf(stderr, "[WARNING] Failed to calculate packet stats. Stats map FD => %d.\n", stats_map);
}
for (int i = 0; i < cpus; i++)
{
// Although this should NEVER happen, I'm seeing very strange behavior in the following GitHub issue.
// https://github.com/gamemann/XDP-Firewall/issues/10
// Therefore, before accessing stats[i], make sure the pointer to the specific CPU ID is not NULL.
if (&stats[i] == NULL)
{
fprintf(stderr, "Stats array at CPU ID #%d is NULL! Skipping...\n", i);
continue;
}
allowed += stats[i].allowed;
dropped += stats[i].dropped;
passed += stats[i].passed;
}
fflush(stdout);
fprintf(stdout, "\rAllowed: %llu | Dropped: %llu | Passed: %llu", allowed, dropped, passed);
}
usleep(sleep_time);
}
// Detach XDP program.
AttachXdp(prog, ifidx, 1, &cmd);
// Add spacing.
fprintf(stdout, "\n");
// Detach XDP program.
if (AttachXdp(prog, ifidx, 1, &cmd))
{
fprintf(stderr, "[ERROR] Failed to detach XDP program from interface '%s'.\n", cfg.interface);
return EXIT_FAILURE;
}
fprintf(stdout, "Cleaned up and exiting...\n");
// Exit program successfully.
return EXIT_SUCCESS;
}

View File

@@ -17,7 +17,7 @@ const struct option opts[] =
* @param cmd A pointer to the cmdline structure.
*
* @return Void
*/
*/
void ParseCommandLine(cmdline_t *cmd, int argc, char *argv[])
{
int c;

View File

@@ -2,7 +2,40 @@
#include <loader/utils/helpers.h>
FILE *file;
static FILE *file;
/**
* Loads the config from the file system.
*
* @param cfg A pointer to the config structure.
* @param cfg_file The path to the config file.
*
* @return 0 on success or 1 on error.
*/
int LoadConfig(config__t *cfg, char *cfg_file)
{
// Open config file.
if (OpenCfg(cfg_file) != 0)
{
fprintf(stderr, "Error opening config file.\n");
return EXIT_FAILURE;
}
SetCfgDefaults(cfg);
memset(cfg->filters, 0, sizeof(cfg->filters));
// Read config and check for errors.
if (ReadCfg(cfg) != 0)
{
fprintf(stderr, "Error reading config file.\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
/**
* Sets the config structure's default values.
@@ -10,7 +43,7 @@ FILE *file;
* @param cfg A pointer to the config structure.
*
* @return Void
*/
*/
void SetCfgDefaults(config__t *cfg)
{
cfg->updatetime = 0;
@@ -18,7 +51,7 @@ void SetCfgDefaults(config__t *cfg)
cfg->nostats = 0;
cfg->stdout_update_time = 1000;
for (u16 i = 0; i < MAX_FILTERS; i++)
for (int i = 0; i < MAX_FILTERS; i++)
{
cfg->filters[i].id = 0;
cfg->filters[i].enabled = 0;
@@ -26,7 +59,7 @@ void SetCfgDefaults(config__t *cfg)
cfg->filters[i].src_ip = 0;
cfg->filters[i].dst_ip = 0;
for (u8 j = 0; j < 4; j++)
for (int j = 0; j < 4; j++)
{
cfg->filters[i].src_ip6[j] = 0;
cfg->filters[i].dst_ip6[j] = 0;
@@ -80,11 +113,11 @@ void SetCfgDefaults(config__t *cfg)
/**
* Opens the config file.
*
* @param filename Path to config file.
* @param file_name Path to config file.
*
* @return 0 on success or 1 on error.
*/
int OpenCfg(const char *filename)
*/
int OpenCfg(const char *file_name)
{
// Close any existing files.
if (file != NULL)
@@ -94,7 +127,7 @@ int OpenCfg(const char *filename)
file = NULL;
}
file = fopen(filename, "r");
file = fopen(file_name, "r");
if (file == NULL)
{
@@ -110,7 +143,7 @@ int OpenCfg(const char *filename)
* @param cfg A pointer to the config structure.
*
* @return 0 on success or 1/-1 on error.
*/
*/
int ReadCfg(config__t *cfg)
{
// Not sure why this would be set to NULL after checking for it in OpenConfig(), but just for safety.
@@ -132,7 +165,7 @@ int ReadCfg(config__t *cfg)
config_destroy(&conf);
return 1;
return EXIT_FAILURE;
}
// Get interface.
@@ -144,7 +177,7 @@ int ReadCfg(config__t *cfg)
config_destroy(&conf);
return 1;
return EXIT_FAILURE;
}
cfg->interface = strdup(interface);
@@ -189,7 +222,7 @@ int ReadCfg(config__t *cfg)
// Set filter count.
int filters = 0;
for (u8 i = 0; i < config_setting_length(setting); i++)
for (int i = 0; i < config_setting_length(setting); i++)
{
config_setting_t* filter = config_setting_get_elem(setting, i);
@@ -251,10 +284,7 @@ int ReadCfg(config__t *cfg)
inet_pton(AF_INET6, sip6, &in);
for (u8 j = 0; j < 4; j++)
{
cfg->filters[i].src_ip6[j] = in.__in6_u.__u6_addr32[j];
}
memcpy(cfg->filters[i].src_ip6, in.__in6_u.__u6_addr32, 4);
}
// Destination IP (IPv6) (not required).
@@ -266,10 +296,7 @@ int ReadCfg(config__t *cfg)
inet_pton(AF_INET6, dip6, &in);
for (u8 j = 0; j < 4; j++)
{
cfg->filters[i].dst_ip6[j] = in.__in6_u.__u6_addr32[j];
}
memcpy(cfg->filters[i].dst_ip6, in.__in6_u.__u6_addr32, 4);
}
// Minimum TTL (not required).
@@ -507,5 +534,109 @@ int ReadCfg(config__t *cfg)
config_destroy(&conf);
return 0;
return EXIT_SUCCESS;
}
/**
* Prints config settings.
*
* @param cfg A pointer to the config structure.
*
* @return void
*/
void PrintConfig(config__t* cfg)
{
fprintf(stdout, "Printing config...\n");
fprintf(stdout, "\tGeneral Settings\n");
fprintf(stdout, "\t\tInterface Name => %s\n", cfg->interface);
fprintf(stdout, "\t\tUpdate Time => %d\n", cfg->updatetime);
fprintf(stdout, "\t\tStdout Update Time => %d\n", cfg->stdout_update_time);
fprintf(stdout, "\t\tNo Stats => %d\n\n", cfg->nostats);
fprintf(stdout, "\tFilters\n");
for (int i = 0; i < MAX_FILTERS; i++)
{
filter_t *filter = &cfg->filters[i];
if (filter->id < 1)
{
break;
}
fprintf(stdout, "\t\tFilter #%d:\n", (i + 1));
// Main.
fprintf(stdout, "\t\t\tID => %d\n", filter->id);
fprintf(stdout, "\t\t\tEnabled => %d\n", filter->enabled);
fprintf(stdout, "\t\t\tAction => %d (0 = Block, 1 = Allow).\n\n", filter->action);
// IP Options.
fprintf(stdout, "\t\t\tIP Options\n");
// IP addresses require additional code for string printing.
struct sockaddr_in sin;
sin.sin_addr.s_addr = filter->src_ip;
fprintf(stdout, "\t\t\t\tSource IPv4 => %s\n", inet_ntoa(sin.sin_addr));
fprintf(stdout, "\t\t\t\tSource CIDR => %d\n", filter->src_cidr);
struct sockaddr_in din;
din.sin_addr.s_addr = filter->dst_ip;
fprintf(stdout, "\t\t\t\tDestination IPv4 => %s\n", inet_ntoa(din.sin_addr));
fprintf(stdout, "\t\t\t\tDestination CIDR => %d\n", filter->dst_cidr);
struct in6_addr sin6;
memcpy(&sin6, &filter->src_ip6, sizeof(sin6));
char srcipv6[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &sin6, srcipv6, sizeof(srcipv6));
fprintf(stdout, "\t\t\t\tSource IPv6 => %s\n", srcipv6);
struct in6_addr din6;
memcpy(&din6, &filter->dst_ip6, sizeof(din6));
char dstipv6[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &din6, dstipv6, sizeof(dstipv6));
fprintf(stdout, "\t\t\t\tDestination IPv6 => %s\n", dstipv6);
// Other IP header information.
fprintf(stdout, "\t\t\t\tMax Length => %d\n", filter->max_len);
fprintf(stdout, "\t\t\t\tMin Length => %d\n", filter->min_len);
fprintf(stdout, "\t\t\t\tMax TTL => %d\n", filter->max_ttl);
fprintf(stdout, "\t\t\t\tMin TTL => %d\n", filter->min_ttl);
fprintf(stdout, "\t\t\t\tTOS => %d\n", filter->tos);
fprintf(stdout, "\t\t\t\tPPS => %llu\n", filter->pps);
fprintf(stdout, "\t\t\t\tBPS => %llu\n", filter->bps);
fprintf(stdout, "\t\t\t\tBlock Time => %llu\n\n", filter->blocktime);
// TCP Options.
fprintf(stdout, "\t\t\tTCP Options\n");
fprintf(stdout, "\t\t\t\tTCP Enabled => %d\n", filter->tcpopts.enabled);
fprintf(stdout, "\t\t\t\tTCP Source Port => %d\n", filter->tcpopts.sport);
fprintf(stdout, "\t\t\t\tTCP Destination Port => %d\n", filter->tcpopts.dport);
fprintf(stdout, "\t\t\t\tTCP URG Flag => %d\n", filter->tcpopts.urg);
fprintf(stdout, "\t\t\t\tTCP ACK Flag => %d\n", filter->tcpopts.ack);
fprintf(stdout, "\t\t\t\tTCP RST Flag => %d\n", filter->tcpopts.rst);
fprintf(stdout, "\t\t\t\tTCP PSH Flag => %d\n", filter->tcpopts.psh);
fprintf(stdout, "\t\t\t\tTCP SYN Flag => %d\n", filter->tcpopts.syn);
fprintf(stdout, "\t\t\t\tTCP FIN Flag => %d\n", filter->tcpopts.fin);
fprintf(stdout, "\t\t\t\tTCP ECE Flag => %d\n", filter->tcpopts.ece);
fprintf(stdout, "\t\t\t\tTCP CWR Flag => %d\n\n", filter->tcpopts.cwr);
// UDP Options.
fprintf(stdout, "\t\t\tUDP Options\n");
fprintf(stdout, "\t\t\t\tUDP Enabled => %d\n", filter->udpopts.enabled);
fprintf(stdout, "\t\t\t\tUDP Source Port => %d\n", filter->udpopts.sport);
fprintf(stdout, "\t\t\t\tUDP Destination Port => %d\n\n", filter->udpopts.dport);
// ICMP Options.
fprintf(stdout, "\t\t\tICMP Options\n");
fprintf(stdout, "\t\t\t\tICMP Enabled => %d\n", filter->icmpopts.enabled);
fprintf(stdout, "\t\t\t\tICMP Code => %d\n", filter->icmpopts.code);
fprintf(stdout, "\t\t\t\tICMP Type => %d\n", filter->icmpopts.type);
fprintf(stdout, "\n\n");
}
}

View File

@@ -10,6 +10,8 @@
#include <arpa/inet.h>
#define CONFIG_DEFAULT_PATH "/etc/xdpfw/xdpfw.conf"
struct config
{
char *interface;
@@ -19,6 +21,9 @@ struct config
filter_t filters[MAX_FILTERS];
} typedef config__t; // config_t is taken by libconfig -.-
int LoadConfig(config__t *cfg, char *cfg_file);
void SetCfgDefaults(config__t *cfg);
void PrintConfig(config__t* cfg);
int OpenCfg(const char *filename);
int ReadCfg(config__t *cfg);

View File

@@ -1,12 +1,40 @@
#include <loader/utils/helpers.h>
/**
* Prints help menu.
*
* @return void
*/
void PrintHelpMenu()
{
fprintf(stdout, "Usage:\n" \
"--config -c => Config file location (default is /etc/xdpfw/xdpfw.conf).\n" \
"--offload -o => Tries to load the XDP program in hardware/offload mode.\n" \
"--skb -s => Force the XDP program to load with SKB mode instead of DRV.\n" \
"--time -t => How long to run the program for in seconds before exiting. 0 or not set = infinite.\n" \
"--list -l => Print config details including filters (this will exit program after done).\n" \
"--help -h => Print help menu.\n");
}
/**
* Handles signals from user.
*
* @param code Signal code.
*
* @return void
*/
void SignalHndl(int code)
{
cont = 0;
}
/**
* Parses an IP string with CIDR support. Stores IP in network byte order in ip.ip and CIDR in ip.cidr.
*
* @param ip The IP string.
*
* @return Returns an IP structure with IP and CIDR.
*/
*/
ip_range_t ParseIpCidr(const char *ip)
{
ip_range_t ret = {0};
@@ -22,7 +50,7 @@ ip_range_t ParseIpCidr(const char *ip)
if (token)
{
ret.cidr = (unsigned int) strtoul(token, NULL, 10);
ret.cidr = (u8) strtoul(token, NULL, 10);
}
}

View File

@@ -10,7 +10,11 @@
struct ip_range
{
u32 ip;
u32 cidr;
u8 cidr;
} typedef ip_range_t;
ip_range_t ParseIpCidr(const char *ip);
extern int cont;
void PrintHelpMenu();
void SignalHndl(int code);
ip_range_t ParseIpCidr(const char* ip);

48
src/loader/utils/stats.c Normal file
View File

@@ -0,0 +1,48 @@
#include <loader/utils/stats.h>
/**
* Calculates and displays packet counters/stats.
*
* @param stats_map The stats map BPF FD.
* @param cpus The amount of CPUs the host has.
*
* @return 0 on success or 1 on failure.
*/
int CalculateStats(int stats_map, int cpus)
{
u32 key = 0;
stats_t stats[MAX_CPUS];
memset(stats, 0, sizeof(stats));
u64 allowed = 0;
u64 dropped = 0;
u64 passed = 0;
if (bpf_map_lookup_elem(stats_map, &key, stats) != 0)
{
return EXIT_FAILURE;
}
for (int i = 0; i < cpus; i++)
{
// Although this should NEVER happen, I'm seeing very strange behavior in the following GitHub issue.
// https://github.com/gamemann/XDP-Firewall/issues/10
// Therefore, before accessing stats[i], make sure the pointer to the specific CPU ID is not NULL.
if (&stats[i] == NULL)
{
fprintf(stderr, "Stats array at CPU ID #%d is NULL! Skipping...\n", i);
continue;
}
allowed += stats[i].allowed;
dropped += stats[i].dropped;
passed += stats[i].passed;
}
fflush(stdout);
fprintf(stdout, "\rAllowed: %llu | Dropped: %llu | Passed: %llu", allowed, dropped, passed);
return EXIT_SUCCESS;
}

13
src/loader/utils/stats.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <bpf.h>
#include <libbpf.h>
#include <xdp/libxdp.h>
#include <common/all.h>
#include <loader/utils/cmdline.h>
#include <loader/utils/config.h>
#include <loader/utils/helpers.h>
int CalculateStats(int stats_map, int cpus);

203
src/loader/utils/xdp.c Normal file
View File

@@ -0,0 +1,203 @@
#include <loader/utils/xdp.h>
/**
* Finds a BPF map's FD.
*
* @param prog A pointer to the XDP program structure.
* @param mapname The name of the map to retrieve.
*
* @return The map's FD.
*/
int FindMapFd(struct xdp_program *prog, const char *map_name)
{
int fd = -1;
struct bpf_object *obj = xdp_program__bpf_obj(prog);
if (obj == NULL)
{
fprintf(stderr, "Error finding BPF object from XDP program.\n");
goto out;
}
struct bpf_map *map = bpf_object__find_map_by_name(obj, map_name);
if (!map)
{
fprintf(stderr, "Error finding eBPF map: %s\n", map_name);
goto out;
}
fd = bpf_map__fd(map);
out:
return fd;
}
/**
* Loads a BPF object file.
*
* @param file_name The path to the BPF object file.
*
* @return XDP program structure (pointer) or NULL.
*/
struct xdp_program *LoadBpfObj(const char *file_name)
{
struct xdp_program *prog = xdp_program__open_file(file_name, "xdp_prog", NULL);
if (prog == NULL)
{
// The main function handles this error.
return NULL;
}
return prog;
}
/**
* Attempts to attach or detach (progfd = -1) a BPF/XDP program to an interface.
*
* @param prog A pointer to the XDP program structure.
* @param ifidx The index to the interface to attach to.
* @param detach If above 0, attempts to detach XDP program.
* @param cmd A pointer to a cmdline struct that includes command line arguments (mostly checking for offload/HW mode set).
*
* @return 0 on success and 1 on error.
*/
int AttachXdp(struct xdp_program *prog, int ifidx, u8 detach, cmdline_t *cmd)
{
int err;
u32 mode = XDP_MODE_NATIVE;
char *smode;
smode = "DRV/native";
if (cmd->offload)
{
smode = "HW/offload";
mode = XDP_MODE_HW;
}
else if (cmd->skb)
{
smode = "SKB/generic";
mode = XDP_MODE_SKB;
}
int exit = 0;
while (!exit)
{
// Try loading program with current mode.
int err;
if (detach)
{
err = xdp_program__detach(prog, ifidx, mode, 0);
}
else
{
err = xdp_program__attach(prog, ifidx, mode, 0);
}
if (err)
{
if (err)
{
fprintf(stderr, "Could not attach with mode %s (%s) (%d).\n", smode, strerror(-err), -err);
}
// Decrease mode.
switch (mode)
{
case XDP_MODE_HW:
mode = XDP_MODE_NATIVE;
smode = "DRV/native";
break;
case XDP_MODE_NATIVE:
mode = XDP_MODE_SKB;
smode = "SKB/generic";
break;
case XDP_MODE_SKB:
// Exit loop.
exit = 1;
smode = NULL;
break;
}
// Retry.
continue;
}
// Success, so break current loop.
break;
}
// If exit is set to 1 or smode is NULL, it indicates full failure.
if (exit || smode == NULL)
{
return EXIT_FAILURE;
}
if (detach < 1)
{
fprintf(stdout, "Loaded XDP program on mode %s.\n", smode);
}
return EXIT_SUCCESS;
}
/**
* Updates the filter's BPF map with current config settings.
*
* @param filters_map The filter's BPF map FD.
* @param cfg A pointer to the config structure.
*
* @return Void
*/
void UpdateFilters(int filters_map, config__t *cfg)
{
int i;
int ret;
// Loop through all filters and delete the map. We do this in the case rules were edited and were put out of order since the key doesn't uniquely map to a specific rule.
for (i = 0; i < MAX_FILTERS; i++)
{
u32 key = i;
bpf_map_delete_elem(filters_map, &key);
}
// Add a filter to the filter maps.
for (i = 0; i < MAX_FILTERS; i++)
{
// Check if we have a valid ID.
if (cfg->filters[i].id < 1)
{
break;
}
// Create value array (max CPUs in size) since we're using a per CPU map.
filter_t filter[MAX_CPUS];
memset(filter, 0, sizeof(filter));
for (int j = 0; j < MAX_CPUS; j++)
{
filter[j] = cfg->filters[i];
}
// Attempt to update BPF map.
if ((ret = bpf_map_update_elem(filters_map, &i, &filter, BPF_ANY)) != 0)
{
fprintf(stderr, "[WARNING] Failed to update filter #%d due to BPF update error (%d).\n", i, ret);
}
}
}

18
src/loader/utils/xdp.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <bpf.h>
#include <libbpf.h>
#include <xdp/libxdp.h>
#include <common/all.h>
#include <loader/utils/cmdline.h>
#include <loader/utils/config.h>
#include <loader/utils/helpers.h>
#define XDP_OBJ_PATH "/etc/xdpfw/xdp_prog.o"
int FindMapFd(struct xdp_program *prog, const char *map_name);
struct xdp_program *LoadBpfObj(const char *file_name);
int AttachXdp(struct xdp_program *prog, int ifidx, u8 detach, cmdline_t *cmd);
void UpdateFilters(int filters_map, config__t *cfg);

View File

@@ -246,7 +246,7 @@ int xdp_prog_main(struct xdp_md *ctx)
UpdateIpStats(&pps, &bps, iph->saddr, src_port, protocol, pkt_len, now);
}
for (u8 i = 0; i < MAX_FILTERS; i++)
for (int i = 0; i < MAX_FILTERS; i++)
{
u32 key = i;

View File

@@ -8,8 +8,8 @@
* @param cidr The CIDR range.
*
* @return 1 on yes, 0 on no.
*/
static __always_inline u8 IsIpInRange(u32 src_ip, u32 net_ip, u8 cidr)
*/
static __always_inline int IsIpInRange(u32 src_ip, u32 net_ip, u8 cidr)
{
return !((src_ip ^ net_ip) & htonl(0xFFFFFFFFu << (32 - cidr)));
}

View File

@@ -28,8 +28,9 @@
#define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n))
#endif
static __always_inline u8 IsIpInRange(u32 src_ip, u32 net_ip, u8 cidr);
static __always_inline int IsIpInRange(u32 src_ip, u32 net_ip, u8 cidr);
// NOTE: We include the C source file below because we can't link object files which includes the function logic into the main XDP program because we need to ensure the function is always inlined for performance which doesn't work with linked objects.
// 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 "helpers.c"

View File

@@ -12,7 +12,7 @@
* @param now The current time since boot in nanoseconds.alignas
*
* @return void
*/
*/
static __always_inline void UpdateIpStats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
{
#ifdef USE_FLOW_RL
@@ -77,7 +77,7 @@ static __always_inline void UpdateIpStats(u64 *pps, u64 *bps, u32 ip, u16 port,
* @param now The current time since boot in nanoseconds.alignas
*
* @return void
*/
*/
static __always_inline void UpdateIp6Stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
{
#ifdef USE_FLOW_RL

View File

@@ -9,6 +9,8 @@
static __always_inline void UpdateIpStats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
static __always_inline void UpdateIp6Stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
// NOTE: We include the C source file below because we can't link object files which includes the function logic into the main XDP program because we need to ensure the function is always inlined for performance which doesn't work with linked objects.
// 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 "rl.c"