You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
7.2 KiB
282 lines
7.2 KiB
/* |
|
* Xtables BPF extension |
|
* |
|
* Written by Willem de Bruijn (willemb@google.com) |
|
* Copyright Google, Inc. 2013 |
|
* Licensed under the GNU General Public License version 2 (GPLv2) |
|
*/ |
|
|
|
#include <linux/netfilter/xt_bpf.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <unistd.h> |
|
#include <xtables.h> |
|
#include "config.h" |
|
|
|
#ifdef HAVE_LINUX_BPF_H |
|
#include <linux/bpf.h> |
|
#endif |
|
|
|
#define BCODE_FILE_MAX_LEN_B 1024 |
|
|
|
enum { |
|
O_BCODE_STDIN = 0, |
|
O_OBJ_PINNED = 1, |
|
}; |
|
|
|
static void bpf_help(void) |
|
{ |
|
printf( |
|
"bpf match options:\n" |
|
"--bytecode <program> : a bpf program as generated by\n" |
|
" $(nfbpf_compile RAW '<filter>')\n"); |
|
} |
|
|
|
static void bpf_help_v1(void) |
|
{ |
|
printf( |
|
"bpf match options:\n" |
|
"--bytecode <program> : a bpf program as generated by\n" |
|
" $(nfbpf_compile RAW '<filter>')\n" |
|
"--object-pinned <bpf object> : a path to a pinned BPF object in bpf fs\n"); |
|
} |
|
|
|
static const struct xt_option_entry bpf_opts[] = { |
|
{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING}, |
|
XTOPT_TABLEEND, |
|
}; |
|
|
|
static const struct xt_option_entry bpf_opts_v1[] = { |
|
{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING}, |
|
{.name = "object-pinned" , .id = O_OBJ_PINNED, .type = XTTYPE_STRING, |
|
.flags = XTOPT_PUT, XTOPT_POINTER(struct xt_bpf_info_v1, path)}, |
|
XTOPT_TABLEEND, |
|
}; |
|
|
|
static int bpf_obj_get(const char *filepath) |
|
{ |
|
#if defined HAVE_LINUX_BPF_H && defined __NR_bpf |
|
union bpf_attr attr; |
|
|
|
memset(&attr, 0, sizeof(attr)); |
|
attr.pathname = (__u64) filepath; |
|
|
|
return syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr)); |
|
#else |
|
xtables_error(OTHER_PROBLEM, |
|
"No bpf header, kernel headers too old?\n"); |
|
return -EINVAL; |
|
#endif |
|
} |
|
|
|
static void bpf_parse_string(struct sock_filter *pc, __u16 *lenp, __u16 len_max, |
|
const char *bpf_program) |
|
{ |
|
const char separator = ','; |
|
const char *token; |
|
char sp; |
|
int i; |
|
__u16 len; |
|
|
|
/* parse head: length. */ |
|
if (sscanf(bpf_program, "%hu%c", &len, &sp) != 2 || |
|
sp != separator) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: error parsing program length"); |
|
if (!len) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: illegal zero length program"); |
|
if (len > len_max) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: number of instructions exceeds maximum"); |
|
|
|
/* parse instructions. */ |
|
i = 0; |
|
token = bpf_program; |
|
while ((token = strchr(token, separator)) && (++token)[0]) { |
|
if (i >= len) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: real program length exceeds" |
|
" the encoded length parameter"); |
|
if (sscanf(token, "%hu %hhu %hhu %u,", |
|
&pc->code, &pc->jt, &pc->jf, &pc->k) != 4) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: error at instr %d", i); |
|
i++; |
|
pc++; |
|
} |
|
|
|
if (i != len) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: parsed program length is less than the" |
|
" encoded length parameter"); |
|
|
|
*lenp = len; |
|
} |
|
|
|
static void bpf_parse_obj_pinned(struct xt_bpf_info_v1 *bi, |
|
const char *filepath) |
|
{ |
|
bi->fd = bpf_obj_get(filepath); |
|
if (bi->fd < 0) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: failed to get bpf object"); |
|
|
|
/* Cannot close bi->fd explicitly. Rely on exit */ |
|
if (fcntl(bi->fd, F_SETFD, FD_CLOEXEC) == -1) { |
|
xtables_error(OTHER_PROBLEM, |
|
"Could not set close on exec: %s\n", |
|
strerror(errno)); |
|
} |
|
} |
|
|
|
static void bpf_parse(struct xt_option_call *cb) |
|
{ |
|
struct xt_bpf_info *bi = (void *) cb->data; |
|
|
|
xtables_option_parse(cb); |
|
switch (cb->entry->id) { |
|
case O_BCODE_STDIN: |
|
bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem, |
|
ARRAY_SIZE(bi->bpf_program), cb->arg); |
|
break; |
|
default: |
|
xtables_error(PARAMETER_PROBLEM, "bpf: unknown option"); |
|
} |
|
} |
|
|
|
static void bpf_parse_v1(struct xt_option_call *cb) |
|
{ |
|
struct xt_bpf_info_v1 *bi = (void *) cb->data; |
|
|
|
xtables_option_parse(cb); |
|
switch (cb->entry->id) { |
|
case O_BCODE_STDIN: |
|
bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem, |
|
ARRAY_SIZE(bi->bpf_program), cb->arg); |
|
bi->mode = XT_BPF_MODE_BYTECODE; |
|
break; |
|
case O_OBJ_PINNED: |
|
bpf_parse_obj_pinned(bi, cb->arg); |
|
bi->mode = XT_BPF_MODE_FD_PINNED; |
|
break; |
|
default: |
|
xtables_error(PARAMETER_PROBLEM, "bpf: unknown option"); |
|
} |
|
} |
|
|
|
static void bpf_print_code(const struct sock_filter *pc, __u16 len, char tail) |
|
{ |
|
for (; len; len--, pc++) |
|
printf("%hu %hhu %hhu %u%c", |
|
pc->code, pc->jt, pc->jf, pc->k, |
|
len > 1 ? ',' : tail); |
|
} |
|
|
|
static void bpf_save_code(const struct sock_filter *pc, __u16 len) |
|
{ |
|
printf(" --bytecode \"%hu,", len); |
|
bpf_print_code(pc, len, '\"'); |
|
} |
|
|
|
static void bpf_save(const void *ip, const struct xt_entry_match *match) |
|
{ |
|
const struct xt_bpf_info *info = (void *) match->data; |
|
|
|
bpf_save_code(info->bpf_program, info->bpf_program_num_elem); |
|
} |
|
|
|
static void bpf_save_v1(const void *ip, const struct xt_entry_match *match) |
|
{ |
|
const struct xt_bpf_info_v1 *info = (void *) match->data; |
|
|
|
if (info->mode == XT_BPF_MODE_BYTECODE) |
|
bpf_save_code(info->bpf_program, info->bpf_program_num_elem); |
|
else if (info->mode == XT_BPF_MODE_FD_PINNED) |
|
printf(" --object-pinned %s", info->path); |
|
else |
|
xtables_error(OTHER_PROBLEM, "unknown bpf mode"); |
|
} |
|
|
|
static void bpf_fcheck(struct xt_fcheck_call *cb) |
|
{ |
|
if (!(cb->xflags & (1 << O_BCODE_STDIN))) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: missing --bytecode parameter"); |
|
} |
|
|
|
static void bpf_fcheck_v1(struct xt_fcheck_call *cb) |
|
{ |
|
const unsigned int bit_bcode = 1 << O_BCODE_STDIN; |
|
const unsigned int bit_pinned = 1 << O_OBJ_PINNED; |
|
unsigned int flags; |
|
|
|
flags = cb->xflags & (bit_bcode | bit_pinned); |
|
if (flags != bit_bcode && flags != bit_pinned) |
|
xtables_error(PARAMETER_PROBLEM, |
|
"bpf: one of --bytecode or --pinned is required"); |
|
} |
|
|
|
static void bpf_print(const void *ip, const struct xt_entry_match *match, |
|
int numeric) |
|
{ |
|
const struct xt_bpf_info *info = (void *) match->data; |
|
|
|
printf("match bpf "); |
|
bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0'); |
|
} |
|
|
|
static void bpf_print_v1(const void *ip, const struct xt_entry_match *match, |
|
int numeric) |
|
{ |
|
const struct xt_bpf_info_v1 *info = (void *) match->data; |
|
|
|
printf("match bpf "); |
|
if (info->mode == XT_BPF_MODE_BYTECODE) |
|
bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0'); |
|
else if (info->mode == XT_BPF_MODE_FD_PINNED) |
|
printf("pinned %s", info->path); |
|
else |
|
printf("unknown"); |
|
} |
|
|
|
static struct xtables_match bpf_matches[] = { |
|
{ |
|
.family = NFPROTO_UNSPEC, |
|
.name = "bpf", |
|
.version = XTABLES_VERSION, |
|
.revision = 0, |
|
.size = XT_ALIGN(sizeof(struct xt_bpf_info)), |
|
.userspacesize = XT_ALIGN(offsetof(struct xt_bpf_info, filter)), |
|
.help = bpf_help, |
|
.print = bpf_print, |
|
.save = bpf_save, |
|
.x6_parse = bpf_parse, |
|
.x6_fcheck = bpf_fcheck, |
|
.x6_options = bpf_opts, |
|
}, |
|
{ |
|
.family = NFPROTO_UNSPEC, |
|
.name = "bpf", |
|
.version = XTABLES_VERSION, |
|
.revision = 1, |
|
.size = XT_ALIGN(sizeof(struct xt_bpf_info_v1)), |
|
.userspacesize = XT_ALIGN(offsetof(struct xt_bpf_info_v1, filter)), |
|
.help = bpf_help_v1, |
|
.print = bpf_print_v1, |
|
.save = bpf_save_v1, |
|
.x6_parse = bpf_parse_v1, |
|
.x6_fcheck = bpf_fcheck_v1, |
|
.x6_options = bpf_opts_v1, |
|
}, |
|
}; |
|
|
|
void _init(void) |
|
{ |
|
xtables_register_matches(bpf_matches, ARRAY_SIZE(bpf_matches)); |
|
}
|
|
|