Cleanup and organize a lot of Loader code.
This commit is contained in:
@@ -1,440 +1,82 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <linux/types.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <getopt.h>
|
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/sysinfo.h>
|
#include <sys/sysinfo.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#include <net/if.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/cmdline.h>
|
||||||
#include <loader/utils/config.h>
|
#include <loader/utils/config.h>
|
||||||
|
#include <loader/utils/xdp.h>
|
||||||
|
#include <loader/utils/stats.h>
|
||||||
#include <loader/utils/helpers.h>
|
#include <loader/utils/helpers.h>
|
||||||
|
|
||||||
// Other variables.
|
int cont = 1;
|
||||||
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;
|
struct stat conf_stat;
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
// Parse the command line.
|
// Parse the command line.
|
||||||
cmdline_t cmd =
|
cmdline_t cmd = {0};
|
||||||
{
|
cmd.cfgfile = CONFIG_DEFAULT_PATH;
|
||||||
.cfgfile = "/etc/xdpfw/xdpfw.conf",
|
|
||||||
.help = 0,
|
|
||||||
.list = 0,
|
|
||||||
.offload = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
ParseCommandLine(&cmd, argc, argv);
|
ParseCommandLine(&cmd, argc, argv);
|
||||||
|
|
||||||
// Check for help menu.
|
// Check for help.
|
||||||
if (cmd.help)
|
if (cmd.help)
|
||||||
{
|
{
|
||||||
fprintf(stdout, "Usage:\n" \
|
PrintHelpMenu();
|
||||||
"--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");
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise RLimit.
|
// Raise RLimit.
|
||||||
struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
|
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
|
||||||
|
|
||||||
if (setrlimit(RLIMIT_MEMLOCK, &rl))
|
if (setrlimit(RLIMIT_MEMLOCK, &rl))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error setting rlimit.\n");
|
fprintf(stderr, "Error setting rlimit. Please make sure this program is ran as root!\n");
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for --config argument.
|
|
||||||
if (cmd.cfgfile == NULL)
|
|
||||||
{
|
|
||||||
// Assign default.
|
|
||||||
cmd.cfgfile = "/etc/xdpfw/xdpfw.conf";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize config.
|
// Initialize config.
|
||||||
config__t cfg = {0};
|
config__t cfg = {0};
|
||||||
|
|
||||||
SetCfgDefaults(&cfg);
|
SetCfgDefaults(&cfg);
|
||||||
|
|
||||||
// Update config.
|
// Load config.
|
||||||
LoadConfig(&cfg, cmd.cfgfile);
|
LoadConfig(&cfg, cmd.cfgfile);
|
||||||
|
|
||||||
// Check for list option.
|
// Check for list option.
|
||||||
if (cmd.list)
|
if (cmd.list)
|
||||||
{
|
{
|
||||||
fprintf(stdout, "Current Settings:\n");
|
PrintConfig(&cfg);
|
||||||
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;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get device.
|
// Get interface index.
|
||||||
int ifidx;
|
int ifidx = if_nametoindex(cfg.interface);
|
||||||
|
|
||||||
if ((ifidx = if_nametoindex(cfg.interface)) < 0)
|
if (ifidx < 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error finding device %s.\n", cfg.interface);
|
fprintf(stderr, "Error finding device %s.\n", cfg.interface);
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XDP variables.
|
|
||||||
const char *filename = "/etc/xdpfw/xdp_prog.o";
|
|
||||||
|
|
||||||
// Load BPF object.
|
// Load BPF object.
|
||||||
struct xdp_program *prog = LoadBpfObj(filename);
|
struct xdp_program *prog = LoadBpfObj(XDP_OBJ_PATH);
|
||||||
|
|
||||||
if (prog == NULL)
|
if (prog == NULL)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error loading eBPF object file. File name => %s.\n", filename);
|
fprintf(stderr, "Error loading eBPF object file. File name => %s.\n", XDP_OBJ_PATH);
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -442,22 +84,25 @@ int main(int argc, char *argv[])
|
|||||||
// Attach XDP program.
|
// Attach XDP program.
|
||||||
if (AttachXdp(prog, ifidx, 0, &cmd))
|
if (AttachXdp(prog, ifidx, 0, &cmd))
|
||||||
{
|
{
|
||||||
|
fprintf(stderr, "Error attaching XDP program.\n");
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve BPF maps.
|
// Retrieve BPF maps.
|
||||||
filtersmap = FindMapFd(prog, "filters_map");
|
int filters_map = FindMapFd(prog, "filters_map");
|
||||||
statsmap = FindMapFd(prog, "stats_map");
|
|
||||||
|
|
||||||
// Check for valid maps.
|
// Check for valid maps.
|
||||||
if (filtersmap < 0)
|
if (filters_map < 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error finding 'filters_map' BPF map.\n");
|
fprintf(stderr, "Error finding 'filters_map' BPF map.\n");
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
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 finding 'stats_map' BPF map.\n");
|
||||||
|
|
||||||
@@ -465,7 +110,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update BPF maps.
|
// Update BPF maps.
|
||||||
UpdateFilters(&cfg);
|
UpdateFilters(filters_map, &cfg);
|
||||||
|
|
||||||
// Signal.
|
// Signal.
|
||||||
signal(SIGINT, SignalHndl);
|
signal(SIGINT, SignalHndl);
|
||||||
@@ -473,30 +118,30 @@ int main(int argc, char *argv[])
|
|||||||
// Receive CPU count for stats map parsing.
|
// Receive CPU count for stats map parsing.
|
||||||
int cpus = get_nprocs_conf();
|
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.
|
// Create last updated variables.
|
||||||
time_t lastupdatecheck = time(NULL);
|
time_t last_update_check = time(NULL);
|
||||||
time_t lastupdated = time(NULL);
|
time_t last_config_check = time(NULL);
|
||||||
|
|
||||||
unsigned int sleep_time = cfg.stdout_update_time * 1000;
|
unsigned int sleep_time = cfg.stdout_update_time * 1000;
|
||||||
|
|
||||||
while (cont)
|
while (cont)
|
||||||
{
|
{
|
||||||
// Get current time.
|
// Get current time.
|
||||||
time_t curTime = time(NULL);
|
time_t cur_time = time(NULL);
|
||||||
|
|
||||||
// Check if we should end the program.
|
// Check if we should end the program.
|
||||||
if (endTime > 0 && curTime >= endTime)
|
if (end_time > 0 && cur_time >= end_time)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for auto-update.
|
// 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
|
// 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()
|
// Memleak fix for strdup() in LoadConfig()
|
||||||
// Before updating it again, we need to free the old return value
|
// Before updating it again, we need to free the old return value
|
||||||
free(cfg.interface);
|
free(cfg.interface);
|
||||||
@@ -505,54 +150,23 @@ int main(int argc, char *argv[])
|
|||||||
LoadConfig(&cfg, cmd.cfgfile);
|
LoadConfig(&cfg, cmd.cfgfile);
|
||||||
|
|
||||||
// Update BPF maps.
|
// Update BPF maps.
|
||||||
UpdateFilters(&cfg);
|
UpdateFilters(filters_map, &cfg);
|
||||||
|
|
||||||
// Update timer
|
// Update timer
|
||||||
lastupdated = time(NULL);
|
last_config_check = time(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last updated variable.
|
// Update last updated variable.
|
||||||
lastupdatecheck = time(NULL);
|
last_update_check = time(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats.
|
// Calculate and display stats if enabled.
|
||||||
if (!cfg.nostats)
|
if (!cfg.nostats)
|
||||||
{
|
{
|
||||||
u32 key = 0;
|
if (CalculateStats(stats_map, cpus))
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error performing stats map lookup. Stats map FD => %d.\n", statsmap);
|
fprintf(stderr, "Error calculating packet stats. Stats map FD => %d.\n", stats_map);
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
usleep(sleep_time);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const struct option opts[] =
|
|||||||
* @param cmd A pointer to the cmdline structure.
|
* @param cmd A pointer to the cmdline structure.
|
||||||
*
|
*
|
||||||
* @return Void
|
* @return Void
|
||||||
*/
|
*/
|
||||||
void ParseCommandLine(cmdline_t *cmd, int argc, char *argv[])
|
void ParseCommandLine(cmdline_t *cmd, int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int c;
|
int c;
|
||||||
|
|||||||
@@ -2,7 +2,40 @@
|
|||||||
|
|
||||||
#include <loader/utils/helpers.h>
|
#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 filters file: %s\n", cfg_file);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the config structure's default values.
|
* Sets the config structure's default values.
|
||||||
@@ -10,7 +43,7 @@ FILE *file;
|
|||||||
* @param cfg A pointer to the config structure.
|
* @param cfg A pointer to the config structure.
|
||||||
*
|
*
|
||||||
* @return Void
|
* @return Void
|
||||||
*/
|
*/
|
||||||
void SetCfgDefaults(config__t *cfg)
|
void SetCfgDefaults(config__t *cfg)
|
||||||
{
|
{
|
||||||
cfg->updatetime = 0;
|
cfg->updatetime = 0;
|
||||||
@@ -18,7 +51,7 @@ void SetCfgDefaults(config__t *cfg)
|
|||||||
cfg->nostats = 0;
|
cfg->nostats = 0;
|
||||||
cfg->stdout_update_time = 1000;
|
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].id = 0;
|
||||||
cfg->filters[i].enabled = 0;
|
cfg->filters[i].enabled = 0;
|
||||||
@@ -26,7 +59,7 @@ void SetCfgDefaults(config__t *cfg)
|
|||||||
cfg->filters[i].src_ip = 0;
|
cfg->filters[i].src_ip = 0;
|
||||||
cfg->filters[i].dst_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].src_ip6[j] = 0;
|
||||||
cfg->filters[i].dst_ip6[j] = 0;
|
cfg->filters[i].dst_ip6[j] = 0;
|
||||||
@@ -80,11 +113,11 @@ void SetCfgDefaults(config__t *cfg)
|
|||||||
/**
|
/**
|
||||||
* Opens the config file.
|
* 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.
|
* @return 0 on success or 1 on error.
|
||||||
*/
|
*/
|
||||||
int OpenCfg(const char *filename)
|
int OpenCfg(const char *file_name)
|
||||||
{
|
{
|
||||||
// Close any existing files.
|
// Close any existing files.
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
@@ -94,7 +127,7 @@ int OpenCfg(const char *filename)
|
|||||||
file = NULL;
|
file = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
file = fopen(filename, "r");
|
file = fopen(file_name, "r");
|
||||||
|
|
||||||
if (file == NULL)
|
if (file == NULL)
|
||||||
{
|
{
|
||||||
@@ -110,7 +143,7 @@ int OpenCfg(const char *filename)
|
|||||||
* @param cfg A pointer to the config structure.
|
* @param cfg A pointer to the config structure.
|
||||||
*
|
*
|
||||||
* @return 0 on success or 1/-1 on error.
|
* @return 0 on success or 1/-1 on error.
|
||||||
*/
|
*/
|
||||||
int ReadCfg(config__t *cfg)
|
int ReadCfg(config__t *cfg)
|
||||||
{
|
{
|
||||||
// Not sure why this would be set to NULL after checking for it in OpenConfig(), but just for safety.
|
// Not sure why this would be set to NULL after checking for it in OpenConfig(), but just for safety.
|
||||||
@@ -189,7 +222,7 @@ int ReadCfg(config__t *cfg)
|
|||||||
// Set filter count.
|
// Set filter count.
|
||||||
int filters = 0;
|
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);
|
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);
|
inet_pton(AF_INET6, sip6, &in);
|
||||||
|
|
||||||
for (u8 j = 0; j < 4; j++)
|
memcpy(cfg->filters[i].src_ip6, in.__in6_u.__u6_addr32, 4);
|
||||||
{
|
|
||||||
cfg->filters[i].src_ip6[j] = in.__in6_u.__u6_addr32[j];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destination IP (IPv6) (not required).
|
// Destination IP (IPv6) (not required).
|
||||||
@@ -266,10 +296,7 @@ int ReadCfg(config__t *cfg)
|
|||||||
|
|
||||||
inet_pton(AF_INET6, dip6, &in);
|
inet_pton(AF_INET6, dip6, &in);
|
||||||
|
|
||||||
for (u8 j = 0; j < 4; j++)
|
memcpy(cfg->filters[i].dst_ip6, in.__in6_u.__u6_addr32, 4);
|
||||||
{
|
|
||||||
cfg->filters[i].dst_ip6[j] = in.__in6_u.__u6_addr32[j];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum TTL (not required).
|
// Minimum TTL (not required).
|
||||||
@@ -509,3 +536,103 @@ int ReadCfg(config__t *cfg)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints config settings.
|
||||||
|
*
|
||||||
|
* @param cfg A pointer to the config structure.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void PrintConfig(config__t* cfg)
|
||||||
|
{
|
||||||
|
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 (int 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#define CONFIG_DEFAULT_PATH "/etc/xdpfw/xdpfw.conf"
|
||||||
|
|
||||||
struct config
|
struct config
|
||||||
{
|
{
|
||||||
char *interface;
|
char *interface;
|
||||||
@@ -19,6 +21,9 @@ struct config
|
|||||||
filter_t filters[MAX_FILTERS];
|
filter_t filters[MAX_FILTERS];
|
||||||
} typedef config__t; // config_t is taken by libconfig -.-
|
} typedef config__t; // config_t is taken by libconfig -.-
|
||||||
|
|
||||||
|
int LoadConfig(config__t *cfg, char *cfg_file);
|
||||||
void SetCfgDefaults(config__t *cfg);
|
void SetCfgDefaults(config__t *cfg);
|
||||||
|
void PrintConfig(config__t* cfg);
|
||||||
|
|
||||||
int OpenCfg(const char *filename);
|
int OpenCfg(const char *filename);
|
||||||
int ReadCfg(config__t *cfg);
|
int ReadCfg(config__t *cfg);
|
||||||
@@ -1,12 +1,40 @@
|
|||||||
#include <loader/utils/helpers.h>
|
#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.
|
* 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.
|
* @param ip The IP string.
|
||||||
*
|
*
|
||||||
* @return Returns an IP structure with IP and CIDR.
|
* @return Returns an IP structure with IP and CIDR.
|
||||||
*/
|
*/
|
||||||
ip_range_t ParseIpCidr(const char *ip)
|
ip_range_t ParseIpCidr(const char *ip)
|
||||||
{
|
{
|
||||||
ip_range_t ret = {0};
|
ip_range_t ret = {0};
|
||||||
@@ -22,7 +50,7 @@ ip_range_t ParseIpCidr(const char *ip)
|
|||||||
|
|
||||||
if (token)
|
if (token)
|
||||||
{
|
{
|
||||||
ret.cidr = (unsigned int) strtoul(token, NULL, 10);
|
ret.cidr = (u8) strtoul(token, NULL, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,11 @@
|
|||||||
struct ip_range
|
struct ip_range
|
||||||
{
|
{
|
||||||
u32 ip;
|
u32 ip;
|
||||||
u32 cidr;
|
u8 cidr;
|
||||||
} typedef ip_range_t;
|
} 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);
|
||||||
Reference in New Issue
Block a user