| /* |
| * 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)); |
| } |