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.
298 lines
7.8 KiB
298 lines
7.8 KiB
4 months ago
|
/*
|
||
|
* 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
|
||
|
|
||
|
#include <linux/magic.h>
|
||
|
#include <linux/unistd.h>
|
||
|
|
||
|
#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_readonly(const char *filepath)
|
||
|
{
|
||
|
#if defined HAVE_LINUX_BPF_H && defined __NR_bpf && defined BPF_FS_MAGIC
|
||
|
/* union bpf_attr includes this in an anonymous struct, but the
|
||
|
* file_flags field and the BPF_F_RDONLY constant are only present
|
||
|
* in Linux 4.15+ kernel headers (include/uapi/linux/bpf.h)
|
||
|
*/
|
||
|
struct { // this part of union bpf_attr is for BPF_OBJ_* commands
|
||
|
__aligned_u64 pathname;
|
||
|
__u32 bpf_fd;
|
||
|
__u32 file_flags;
|
||
|
} attr = {
|
||
|
.pathname = (__u64)filepath,
|
||
|
.file_flags = (1U << 3), // BPF_F_RDONLY
|
||
|
};
|
||
|
int fd = syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
|
||
|
if (fd >= 0) return fd;
|
||
|
|
||
|
/* on any error fallback to default R/W access for pre-4.15-rc1 kernels */
|
||
|
attr.file_flags = 0;
|
||
|
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_readonly(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));
|
||
|
}
|