Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions api/api.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// defines
#define TOOL_NAME "traffico"
#define NUM_PROGRAMS ${PROGRAMS_COUNT}
#define MAX_MULTI_VALUES 8
#define PROGRAMS_DESCRIPTION ${PROGRAMS_DESCRIPTION}

/// globals
Expand All @@ -33,8 +34,16 @@ struct config

bool has_input;
union {
__u32 ip;
__u16 port;
__u32 ip; // allow_ipv4, allow_dns, block_ipv4
__u16 port; // allow_port, block_port
struct {
__u16 values[MAX_MULTI_VALUES];
__u8 count;
} ethertypes; // allow_ethertype (future)
struct {
__u8 values[MAX_MULTI_VALUES];
__u8 count;
} protos; // allow_proto (future)
} input;
};

Expand Down
15 changes: 13 additions & 2 deletions api/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@
#define MAX_CHAIN_LEN 8
#define BPFFS_BASE "/sys/fs/bpf/traffico"

/// Maximum number of values in a multi-value input (ethertypes, protos).
#define MAX_MULTI_VALUES 8

/// A single entry in a chain: program type + input value.
struct chain_entry
{
program_t program;
bool has_input;
union {
__u32 ip;
__u16 port;
__u32 ip; // allow_ipv4, allow_dns, block_ipv4
__u16 port; // allow_port, block_port
struct {
__u16 values[MAX_MULTI_VALUES];
__u8 count;
} ethertypes; // allow_ethertype (future)
struct {
__u8 values[MAX_MULTI_VALUES];
__u8 count;
} protos; // allow_proto (future)
} input;
};

Expand Down
232 changes: 232 additions & 0 deletions api/input_parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,241 @@

#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "api.h"

// Symbolic EtherType names for parse_ethertypes().
struct ethertype_name
{
const char *name;
__u16 value;
};

static const struct ethertype_name g_ethertype_names[] = {
{"ipv4", 0x0800},
{"ipv6", 0x86DD},
{"arp", 0x0806},
{NULL, 0},
};

// Symbolic IP protocol names for parse_protos().
struct proto_name
{
const char *name;
__u8 value;
};

static const struct proto_name g_proto_names[] = {
{"tcp", 6},
{"udp", 17},
{"icmp", 1},
{NULL, 0},
};

// Validate that a '+'-delimited input string has no empty tokens.
// Rejects leading '+', trailing '+', and consecutive '++'.
static int validate_delimited_input(const char *input_str, const char **err_msg)
{
size_t len = strlen(input_str);
if (len == 0)
return 0; // Empty string handled by caller's count==0 check

if (input_str[0] == '+')
{
*err_msg = "input must not start with '+'";
return -1;
}
if (input_str[len - 1] == '+')
{
*err_msg = "input must not end with '+'";
return -1;
}
if (strstr(input_str, "++"))
{
*err_msg = "input contains empty value between '+' delimiters";
return -1;
}
return 0;
}

// Parse a '+'-delimited list of EtherTypes into conf->input.ethertypes.
// Each token is a symbolic name (ipv4, arp, ipv6) or a 0x-prefixed hex value.
// Returns 0 on success, -1 on error (with err_msg set).
static int parse_ethertypes(struct config *conf, const char *input_str, const char **err_msg)
{
if (validate_delimited_input(input_str, err_msg) != 0)
return -1;

// Work on a mutable copy since strtok_r modifies the string
char buf[256];
if (strlen(input_str) >= sizeof(buf))
{
*err_msg = "EtherType list too long";
return -1;
}
memcpy(buf, input_str, strlen(input_str) + 1);

__u8 count = 0;
char *saveptr = NULL;
char *token = strtok_r(buf, "+", &saveptr);

while (token)
{
if (count >= MAX_MULTI_VALUES)
{
*err_msg = "too many EtherType values";
return -1;
}

__u16 value = 0;
bool found = false;

// Try symbolic name first
for (const struct ethertype_name *e = g_ethertype_names; e->name; e++)
{
if (strcasecmp(token, e->name) == 0)
{
value = e->value;
found = true;
break;
}
}

// Try 0x hex value
if (!found)
{
if (token[0] == '0' && (token[1] == 'x' || token[1] == 'X'))
{
char *endptr;
unsigned long v = strtoul(token, &endptr, 16);
if (*endptr != '\0' || v == 0 || v > 0xFFFF)
{
*err_msg = "invalid EtherType hex value";
return -1;
}
value = (__u16)v;
found = true;
}
}

if (!found)
{
*err_msg = "unknown EtherType name";
return -1;
}

// Check for duplicates
for (__u8 i = 0; i < count; i++)
{
if (conf->input.ethertypes.values[i] == value)
{
*err_msg = "duplicate EtherType value";
return -1;
}
}

conf->input.ethertypes.values[count] = value;
count++;
token = strtok_r(NULL, "+", &saveptr);
}

if (count == 0)
{
*err_msg = "empty EtherType list";
return -1;
}

conf->input.ethertypes.count = count;
conf->has_input = true;
return 0;
}

// Parse a '+'-delimited list of IP protocol numbers into conf->input.protos.
// Each token is a symbolic name (tcp, udp, icmp) or a decimal number (0-255).
// Returns 0 on success, -1 on error (with err_msg set).
static int parse_protos(struct config *conf, const char *input_str, const char **err_msg)
{
if (validate_delimited_input(input_str, err_msg) != 0)
return -1;

char buf[256];
if (strlen(input_str) >= sizeof(buf))
{
*err_msg = "protocol list too long";
return -1;
}
memcpy(buf, input_str, strlen(input_str) + 1);

__u8 count = 0;
char *saveptr = NULL;
char *token = strtok_r(buf, "+", &saveptr);

while (token)
{
if (count >= MAX_MULTI_VALUES)
{
*err_msg = "too many protocol values";
return -1;
}

__u8 value = 0;
bool found = false;

// Try symbolic name first
for (const struct proto_name *p = g_proto_names; p->name; p++)
{
if (strcasecmp(token, p->name) == 0)
{
value = p->value;
found = true;
break;
}
}

// Try decimal number
if (!found)
{
char *endptr;
unsigned long v = strtoul(token, &endptr, 10);
if (*endptr != '\0' || v > 255)
{
*err_msg = "invalid protocol number";
return -1;
}
// Protocol 0 (HOPOPT) is technically valid but unlikely intended;
// allow it since the BPF program can handle any __u8 value.
value = (__u8)v;
found = true;
}

// Check for duplicates
for (__u8 i = 0; i < count; i++)
{
if (conf->input.protos.values[i] == value)
{
*err_msg = "duplicate protocol value";
return -1;
}
}

conf->input.protos.values[count] = value;
count++;
token = strtok_r(NULL, "+", &saveptr);
}

if (count == 0)
{
*err_msg = "empty protocol list";
return -1;
}

conf->input.protos.count = count;
conf->has_input = true;
return 0;
}

// Parse the input string and populate conf->input based on the selected program.
// Returns 0 on success, -1 on error (with err_msg set to a static string).
static int parse_input(struct config *conf, const char *input_str, const char **err_msg)
Expand Down
16 changes: 15 additions & 1 deletion xmake/modules/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import("core.project.project")
-- Map program names to their input union field in struct config.
-- Programs in this table have const volatile rodata that can be
-- configured at runtime via the input union in struct config.
--
-- Scalar programs use a plain string: "ip", "port".
-- Multi-value programs use a table: { field = "ethertypes", multi = true }.
-- The template uses PROGNAME_INPUT_FIELD for the union member name and
-- PROGNAME_IS_MULTI_VALUE to distinguish array inputs from scalars.
local input_fields = {
allow_dns = "ip",
allow_ipv4 = "ip",
Expand Down Expand Up @@ -53,7 +58,15 @@ function gen(target, source_target)
local tempconf = path.join(os.tmpdir(), confname)
os.tryrm(tempconf)
os.cp(configfile_template_path, tempconf)
local input_field = input_fields[progname]
local raw = input_fields[progname]
local input_field = nil
local is_multi = false
if type(raw) == "string" then
input_field = raw
elseif type(raw) == "table" then
input_field = raw.field
is_multi = raw.multi or false
end
local has_rodata = input_field and 1 or 0

target:add("configfiles", tempconf, {
Expand All @@ -62,6 +75,7 @@ function gen(target, source_target)
OPERATION = "attach__",
PROGNAME_WITH_RODATA = has_rodata,
PROGNAME_INPUT_FIELD = input_field or "",
PROGNAME_IS_MULTI_VALUE = is_multi and 1 or 0,
}
})
end
Expand Down