From 5d7ca715d18a5cec4b61fccb316182c77746f08a Mon Sep 17 00:00:00 2001 From: Christian Deacon Date: Mon, 10 Mar 2025 16:08:43 -0400 Subject: [PATCH] Implement support for multiple interfaces and improve code. --- src/common/config.h | 5 +- src/loader/prog.c | 119 +++++++++++------- src/loader/utils/config.c | 256 ++++++++++++++++++++++++++++++-------- src/loader/utils/config.h | 19 +-- src/loader/utils/xdp.c | 2 +- src/rule_add/prog.c | 2 +- src/rule_add/utils/cli.h | 12 +- src/rule_del/prog.c | 2 +- 8 files changed, 304 insertions(+), 113 deletions(-) diff --git a/src/common/config.h b/src/common/config.h index 99f61f9..b14a2cd 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -38,4 +38,7 @@ // Enables filter logging through XDP. // If performance is a concern, it is best to disable this feature by commenting out the below line with //. -#define ENABLE_FILTER_LOGGING \ No newline at end of file +#define ENABLE_FILTER_LOGGING + +// Maximum interfaces the firewall can attach to. +#define MAX_INTERFACES 6 \ No newline at end of file diff --git a/src/loader/prog.c b/src/loader/prog.c index ad5e22f..a88ab41 100644 --- a/src/loader/prog.c +++ b/src/loader/prog.c @@ -111,8 +111,6 @@ int main(int argc, char *argv[]) // Initialize config. config__t cfg = {0}; - set_cfg_defaults(&cfg); - // Create overrides for config and set arguments from CLI. config_overrides_t cfg_overrides = {0}; cfg_overrides.verbose = cli.verbose; @@ -125,7 +123,7 @@ int main(int argc, char *argv[]) cfg_overrides.stdout_update_time = cli.stdout_update_time; // Load config. - if ((ret = load_cfg(&cfg, cli.cfg_file, &cfg_overrides)) != 0) + if ((ret = load_cfg(&cfg, cli.cfg_file, 1, &cfg_overrides)) != 0) { fprintf(stderr, "[ERROR] Failed to load config from file system (%s)(%d).\n", cli.cfg_file, ret); @@ -146,10 +144,10 @@ int main(int argc, char *argv[]) print_tool_info(); } - // Check interface. - if (cfg.interface == NULL) + // Check first interface. + if (cfg.interfaces[0] == NULL) { - log_msg(&cfg, 0, 1, "[ERROR] No interface specified in config or CLI override."); + log_msg(&cfg, 0, 1, "[ERROR] No interface(s) specified in config or CLI override."); return EXIT_FAILURE; } @@ -166,18 +164,6 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - log_msg(&cfg, 2, 0, "Retrieving interface index for '%s'...", cfg.interface); - - // Get interface index. - int ifidx = if_nametoindex(cfg.interface); - - if (ifidx < 0) - { - log_msg(&cfg, 0, 1, "[ERROR] Failed to retrieve index of network interface '%s'.\n", cfg.interface); - - return EXIT_FAILURE; - } - log_msg(&cfg, 2, 0, "Loading XDP/BPF program at '%s'...", XDP_OBJ_PATH); // Determine custom LibBPF log level. @@ -200,21 +186,48 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - log_msg(&cfg, 2, 0, "Attaching XDP program to interface '%s'...", cfg.interface); + int if_idx[MAX_INTERFACES] = {0}; + + // Attach XDP program to interface(s). + for (int i = 0; i < cfg.interfaces_cnt; i++) + { + const char* interface = cfg.interfaces[i]; + + if (!interface) + { + continue; + } + + log_msg(&cfg, 4, 0, "Retrieving interface index for '%s'...", interface); + + // Get interface index. + if_idx[i] = if_nametoindex(interface); - // Attach XDP program. - char *mode_used = NULL; + if (if_idx[i] < 0) + { + log_msg(&cfg, 0, 1, "[WARNING] Failed to retrieve index of network interface '%s'.\n", interface); + + continue; + } - if ((ret = attach_xdp(prog, &mode_used, ifidx, 0, cli.skb, cli.offload)) != 0) - { - log_msg(&cfg, 0, 1, "[ERROR] Failed to attach XDP program to interface '%s' using available modes (%d).\n", cfg.interface, ret); + log_msg(&cfg, 3, 0, "Interface index for '%s' => %d.", interface, if_idx[i]); - return EXIT_FAILURE; - } + log_msg(&cfg, 2, 0, "Attaching XDP program to interface '%s'...", interface); + + // Attach XDP program. + char* mode_used = NULL; + + if ((ret = attach_xdp(prog, &mode_used, if_idx[i], 0, cli.skb, cli.offload)) != 0) + { + log_msg(&cfg, 0, 1, "[WARNING] Failed to attach XDP program to interface '%s' using available modes (%d).\n", interface, ret); - if (mode_used != NULL) - { - log_msg(&cfg, 1, 0, "Attached XDP program using mode '%s'...", mode_used); + continue; + } + + if (mode_used != NULL) + { + log_msg(&cfg, 1, 0, "Attached XDP program to interface '%s' using mode '%s'...", interface, mode_used); + } } log_msg(&cfg, 2, 0, "Retrieving BPF map FDs..."); @@ -398,27 +411,39 @@ int main(int argc, char *argv[]) // Check for auto-update. if (cfg.update_time > 0 && (cur_time - last_update_check) > cfg.update_time) { + log_msg(&cfg, 6, 0, "Checking for config updates..."); + // Check if config file have been modified if (stat(cli.cfg_file, &conf_stat) == 0 && conf_stat.st_mtime > last_config_check) { + log_msg(&cfg, 3, 0, "Config file change detected during update. Attempting to reload config..."); + // Reload config. - if ((ret = load_cfg(&cfg, cli.cfg_file, &cfg_overrides)) != 0) + if ((ret = load_cfg(&cfg, cli.cfg_file, 1, &cfg_overrides)) != 0) { log_msg(&cfg, 1, 0, "[WARNING] Failed to load config after update check (%d)...\n", ret); } + else + { + log_msg(&cfg, 4, 0, "Config reloaded successfully..."); + + // Make sure we set doing_stats properly. + if (!cfg.no_stats && !doing_stats) + { + doing_stats = 1; + } + else if (cfg.no_stats && doing_stats) + { + doing_stats = 0; + } #ifdef ENABLE_FILTERS - // Update filters. - update_filters(map_filters, &cfg); + // Update filters. + update_filters(map_filters, &cfg); #endif + } // Update last check timer last_config_check = time(NULL); - - // Make sure we set doing stats if needed. - if (!cfg.no_stats && !doing_stats) - { - doing_stats = 1; - } } // Update last updated variable. @@ -452,12 +477,22 @@ int main(int argc, char *argv[]) } #endif - // Detach XDP program. - if (attach_xdp(prog, &mode_used, ifidx, 1, cli.skb, cli.offload)) + // Detach XDP program from interfaces. + for (int i = 0; i < MAX_INTERFACES; i++) { - log_msg(&cfg, 0, 1, "[ERROR] Failed to detach XDP program from interface '%s'.\n", cfg.interface); + const char* interface = cfg.interfaces[i]; + + if (!interface) + { + continue; + } - return EXIT_FAILURE; + char* mode_used = NULL; + + if (attach_xdp(prog, &mode_used, if_idx[i], 1, cli.skb, cli.offload)) + { + log_msg(&cfg, 0, 0, "[WARNING] Failed to detach XDP program from interface '%s'.\n", interface); + } } // Unpin maps from file system. diff --git a/src/loader/utils/config.c b/src/loader/utils/config.c index 06a7870..710d2bc 100644 --- a/src/loader/utils/config.c +++ b/src/loader/utils/config.c @@ -5,13 +5,19 @@ * * @param cfg A pointer to the config structure. * @param cfg_file The path to the config file. + * @param load_defaults Whether to load defaults or not. * @param overrides Overrides to use instead of config values. * * @return 0 on success or 1 on error. */ -int load_cfg(config__t *cfg, const char* cfg_file, config_overrides_t* overrides) +int load_cfg(config__t *cfg, const char* cfg_file, int load_defaults, config_overrides_t* overrides) { int ret; + + if (load_defaults) + { + set_cfg_defaults(cfg); + } FILE *file = NULL; @@ -23,8 +29,6 @@ int load_cfg(config__t *cfg, const char* cfg_file, config_overrides_t* overrides return ret; } - set_cfg_defaults(cfg); - char* buffer = NULL; // Read config. @@ -205,25 +209,68 @@ int parse_cfg(config__t *cfg, const char* data, config_overrides_t* overrides) } } - // Get interface. - const char *interface; + // Get interface(s). + config_setting_t* interfaces = config_lookup(&conf, "interfaces"); - if (config_lookup_string(&conf, "interface", &interface) == CONFIG_TRUE || (overrides && overrides->interface != NULL)) + if (interfaces) { - // We must free previous value to prevent memory leak. - if (cfg->interface != NULL) + if (config_setting_is_list(interfaces)) { - free(cfg->interface); - cfg->interface = NULL; - } + for (int i = 0; i < config_setting_length(interfaces); i++) + { + if (i >= MAX_INTERFACES) + { + break; + } - if (overrides && overrides->interface != NULL) - { - cfg->interface = strdup(overrides->interface); + const char* interface = config_setting_get_string_elem(interfaces, i); + + if (!interface) + { + continue; + } + + if (cfg->interfaces[i]) + { + free(cfg->interfaces[i]); + cfg->interfaces[i] = NULL; + } + + if (i == 0 && overrides && overrides->interface) + { + cfg->interfaces[i] = strdup(overrides->interface); + } + else + { + cfg->interfaces[i] = strdup(interface); + } + + cfg->interfaces_cnt++; + } } else { - cfg->interface = strdup(interface); + const char* interface; + + if (config_lookup_string(&conf, "interfaces", &interface) == CONFIG_TRUE) + { + if (cfg->interfaces[0]) + { + free(cfg->interfaces[0]); + cfg->interfaces[0] = NULL; + } + + if (overrides && overrides->interface) + { + cfg->interfaces[0] = strdup(overrides->interface); + } + else + { + cfg->interfaces[0] = strdup(interface); + } + + cfg->interfaces_cnt = 1; + } } } @@ -320,6 +367,8 @@ int parse_cfg(config__t *cfg, const char* data, config_overrides_t* overrides) continue; } + cfg->filters_cnt++; + // Make sure filter is set. filter->set = 1; @@ -562,6 +611,7 @@ int parse_cfg(config__t *cfg, const char* data, config_overrides_t* overrides) } /* ICMP options */ + // Enabled. int icmp_enabled; @@ -599,16 +649,20 @@ int parse_cfg(config__t *cfg, const char* data, config_overrides_t* overrides) if (cfg->drop_ranges[i]) { - free((void*)cfg->drop_ranges[i]); + free(cfg->drop_ranges[i]); cfg->drop_ranges[i] = NULL; } const char* new_range = config_setting_get_string_elem(setting, i); - if (new_range) + if (!new_range) { - cfg->drop_ranges[i] = strdup(new_range); + continue; } + + cfg->drop_ranges[i] = strdup(new_range); + + cfg->drop_ranges_cnt++; } } @@ -646,11 +700,36 @@ int save_cfg(config__t* cfg, const char* file_path) config_setting_set_string(setting, cfg->log_file); } - // Add interface. - if (cfg->interface) + // Add interface(s). + if (cfg->interfaces_cnt > 0) { - setting = config_setting_add(root, "interface", CONFIG_TYPE_STRING); - config_setting_set_string(setting, cfg->interface); + if (cfg->interfaces_cnt > 1) + { + setting = config_setting_add(root, "interfaces", CONFIG_TYPE_LIST); + + for (int i = 0; i < cfg->interfaces_cnt; i++) + { + const char* interface = cfg->interfaces[i]; + + if (!interface) + { + continue; + } + + config_setting_t* setting_interface = config_setting_add(setting, NULL, CONFIG_TYPE_STRING); + config_setting_set_string(setting_interface, interface); + } + } + else + { + const char* interface = cfg->interfaces[0]; + + if (interface) + { + setting = config_setting_add(root, "interfaces", CONFIG_TYPE_STRING); + config_setting_set_string(setting, interface); + } + } } // Add pin maps. @@ -980,28 +1059,28 @@ void set_filter_defaults(filter_rule_cfg_t* filter) if (filter->ip.src_ip) { - free((void*)filter->ip.src_ip); + free(filter->ip.src_ip); filter->ip.src_ip = NULL; } if (filter->ip.dst_ip) { - free((void*)filter->ip.dst_ip); + free(filter->ip.dst_ip); filter->ip.dst_ip = NULL; } if (filter->ip.src_ip6) { - free((void*)filter->ip.src_ip6); + free(filter->ip.src_ip6); filter->ip.src_ip6 = NULL; } if (filter->ip.dst_ip6) { - free((void*)filter->ip.dst_ip6); + free(filter->ip.dst_ip6); filter->ip.dst_ip6 = NULL; } @@ -1047,14 +1126,39 @@ void set_filter_defaults(filter_rule_cfg_t* filter) void set_cfg_defaults(config__t* cfg) { cfg->verbose = 2; - cfg->log_file = strdup("/var/log/xdpfw.log"); cfg->update_time = 0; - cfg->interface = NULL; cfg->pin_maps = 1; cfg->no_stats = 0; cfg->stats_per_second = 0; cfg->stdout_update_time = 1000; + if (cfg->log_file) + { + free(cfg->log_file); + + cfg->log_file = NULL; + } + + cfg->log_file = strdup("/var/log/xdpfw.log"); + + cfg->interfaces_cnt = 0; + + for (int i = 0; i < MAX_INTERFACES; i++) + { + char* interface = cfg->interfaces[i]; + + if (!interface) + { + continue; + } + + free(interface); + + cfg->interfaces[i] = NULL; + } + + cfg->filters_cnt = 0; + for (int i = 0; i < MAX_FILTERS; i++) { filter_rule_cfg_t* filter = &cfg->filters[i]; @@ -1062,7 +1166,21 @@ void set_cfg_defaults(config__t* cfg) set_filter_defaults(filter); } - memset(cfg->drop_ranges, 0, sizeof(cfg->drop_ranges)); + cfg->drop_ranges_cnt = 0; + + for (int i = 0; i < MAX_IP_RANGES; i++) + { + char* drop_range = cfg->drop_ranges[i]; + + if (!drop_range) + { + continue; + } + + free(drop_range); + + cfg->drop_ranges[i] = NULL; + } } /** @@ -1169,14 +1287,7 @@ void print_filter(filter_rule_cfg_t* filter, int idx) */ void print_cfg(config__t* cfg) { - char* interface = "N/A"; - - if (cfg->interface != NULL) - { - interface = cfg->interface; - } - - char* log_file = "N/A"; + const char* log_file = "N/A"; if (cfg->log_file != NULL) { @@ -1185,45 +1296,82 @@ void print_cfg(config__t* cfg) printf("Printing config...\n"); printf("General Settings\n"); - printf("\tVerbose => %d\n", cfg->verbose); printf("\tLog File => %s\n", log_file); - printf("\tInterface Name => %s\n", interface); printf("\tPin BPF Maps => %d\n", cfg->pin_maps); printf("\tUpdate Time => %d\n", cfg->update_time); printf("\tNo Stats => %d\n", cfg->no_stats); printf("\tStats Per Second => %d\n", cfg->stats_per_second); printf("\tStdout Update Time => %d\n\n", cfg->stdout_update_time); - printf("Filters\n"); - - for (int i = 0; i < MAX_FILTERS; i++) + printf("Interfaces\n"); + + if (cfg->interfaces_cnt > 0) { - filter_rule_cfg_t *filter = &cfg->filters[i]; - - if (!filter->set) + for (int i = 0; i < cfg->interfaces_cnt; i++) { - break; + const char* interface = cfg->interfaces[i]; + + if (!interface) + { + continue; + } + + printf("\t- %s\n", interface); } - print_filter(filter, i + 1); - - printf("\n\n"); + printf("\n"); + } + else + { + printf("\t- None\n\n"); } - printf("\n"); + printf("Filters\n"); + if (cfg->filters_cnt > 0) + { + for (int i = 0; i < cfg->filters_cnt; i++) + { + filter_rule_cfg_t *filter = &cfg->filters[i]; + + if (!filter->set) + { + break; + } + + print_filter(filter, i + 1); + + printf("\n\n"); + } + + printf("\n"); + } + else + { + printf("\t- None\n\n"); + } + printf("IP Drop Ranges\n"); - for (int i = 0; i < MAX_IP_RANGES; i++) + if (cfg->drop_ranges_cnt > 0) { - const char* range = cfg->drop_ranges[i]; - - if (range) + for (int i = 0; i < cfg->drop_ranges_cnt; i++) { + const char* range = cfg->drop_ranges[i]; + + if (!range) + { + continue; + } + printf("\t- %s\n", range); } } + else + { + printf("\t- None\n"); + } } /** diff --git a/src/loader/utils/config.h b/src/loader/utils/config.h index 9f280ba..c1b7cee 100644 --- a/src/loader/utils/config.h +++ b/src/loader/utils/config.h @@ -15,11 +15,11 @@ struct filter_rule_ip_opts { - const char* src_ip; - const char* dst_ip; + char* src_ip; + char* dst_ip; - const char* src_ip6; - const char* dst_ip6; + char* src_ip6; + char* dst_ip6; int min_ttl; int max_ttl; @@ -86,15 +86,20 @@ struct config { int verbose; char *log_file; - char *interface; unsigned int pin_maps : 1; int update_time; unsigned int no_stats : 1; unsigned int stats_per_second : 1; int stdout_update_time; + int interfaces_cnt; + char* interfaces[MAX_INTERFACES]; + + int filters_cnt; filter_rule_cfg_t filters[MAX_FILTERS]; - const char* drop_ranges[MAX_IP_RANGES]; + + int drop_ranges_cnt; + char* drop_ranges[MAX_IP_RANGES]; } typedef config__t; // config_t is taken by libconfig -.- struct config_overrides @@ -115,7 +120,7 @@ void set_filter_defaults(filter_rule_cfg_t* filter); void print_cfg(config__t* cfg); void print_filter(filter_rule_cfg_t* filter, int idx); -int load_cfg(config__t *cfg, const char* cfg_file, config_overrides_t* overrides); +int load_cfg(config__t *cfg, const char* cfg_file, int load_defaults, config_overrides_t* overrides); int save_cfg(config__t* cfg, const char* file_path); int open_cfg(FILE** file, const char *file_name); diff --git a/src/loader/utils/xdp.c b/src/loader/utils/xdp.c index 3a66c3d..f892a7b 100644 --- a/src/loader/utils/xdp.c +++ b/src/loader/utils/xdp.c @@ -473,7 +473,7 @@ void update_filters(int map_filters, config__t *cfg) int cur_idx = 0; // Add a filter to the filter maps. - for (int i = 0; i < MAX_FILTERS; i++) + for (int i = 0; i < cfg->filters_cnt; i++) { // Delete previous rule from BPF 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. diff --git a/src/rule_add/prog.c b/src/rule_add/prog.c index 9112cd0..057fac2 100644 --- a/src/rule_add/prog.c +++ b/src/rule_add/prog.c @@ -130,7 +130,7 @@ int main(int argc, char *argv[]) if (cli.save || cli.mode == 0) { - if ((ret = load_cfg(&cfg, cli.cfg_file, NULL)) != 0) + if ((ret = load_cfg(&cfg, cli.cfg_file, 1, NULL)) != 0) { fprintf(stderr, "[ERROR] Failed to load config at '%s' (%d)\n", cli.cfg_file, ret); diff --git a/src/rule_add/utils/cli.h b/src/rule_add/utils/cli.h index d4977f3..3e9eb62 100644 --- a/src/rule_add/utils/cli.h +++ b/src/rule_add/utils/cli.h @@ -10,7 +10,7 @@ struct cli { - const char* cfg_file; + char* cfg_file; int help; @@ -20,7 +20,7 @@ struct cli int idx; - const char* ip; + char* ip; int v6; s64 expires; @@ -30,11 +30,11 @@ struct cli int action; s64 block_time; - const char* src_ip; - const char* dst_ip; + char* src_ip; + char* dst_ip; - const char* src_ip6; - const char* dst_ip6; + char* src_ip6; + char* dst_ip6; s64 pps; s64 bps; diff --git a/src/rule_del/prog.c b/src/rule_del/prog.c index 625adda..2e83efe 100644 --- a/src/rule_del/prog.c +++ b/src/rule_del/prog.c @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) if (cli.save || cli.mode == 0) { - if ((ret = load_cfg(&cfg, cli.cfg_file, NULL)) != 0) + if ((ret = load_cfg(&cfg, cli.cfg_file, 1, NULL)) != 0) { fprintf(stderr, "[ERROR] Failed to load config at '%s' (%d)\n", cli.cfg_file, ret);