#include #include #include #include #include #include #include #include #include #include #include "ucode/module.h" #define err_return_int(err, ...) do { set_error(err, __VA_ARGS__); return -1; } while(0) #define err_return(err, ...) do { set_error(err, __VA_ARGS__); return NULL; } while(0) #define TRUE ucv_boolean_new(true) static uc_resource_type_t *module_type, *map_type, *map_iter_type, *program_type; static uc_value_t *registry; static uc_vm_t *debug_vm; static struct { int code; char *msg; } last_error; struct uc_bpf_fd { int fd; bool close; }; struct uc_bpf_map { struct uc_bpf_fd fd; /* must be first */ unsigned int key_size, val_size; }; struct uc_bpf_map_iter { int fd; unsigned int key_size; bool has_next; uint8_t key[]; }; __attribute__((format(printf, 2, 3))) static void set_error(int errcode, const char *fmt, ...) { va_list ap; free(last_error.msg); last_error.code = errcode; last_error.msg = NULL; if (fmt) { va_start(ap, fmt); xvasprintf(&last_error.msg, fmt, ap); va_end(ap); } } static void init_env(void) { static bool init_done = false; struct rlimit limit = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY, }; if (init_done) return; setrlimit(RLIMIT_MEMLOCK, &limit); init_done = true; } static uc_value_t * uc_bpf_error(uc_vm_t *vm, size_t nargs) { uc_value_t *numeric = uc_fn_arg(0); const char *msg = last_error.msg; int code = last_error.code; uc_stringbuf_t *buf; const char *s; if (last_error.code == 0) return NULL; set_error(0, NULL); if (ucv_is_truish(numeric)) return ucv_int64_new(code); buf = ucv_stringbuf_new(); if (code < 0 && msg) { ucv_stringbuf_addstr(buf, msg, strlen(msg)); } else { s = strerror(code); ucv_stringbuf_addstr(buf, s, strlen(s)); if (msg) ucv_stringbuf_printf(buf, ": %s", msg); } return ucv_stringbuf_finish(buf); } static int uc_bpf_module_set_opts(struct bpf_object *obj, uc_value_t *opts) { uc_value_t *val; if (!opts) return 0; if (ucv_type(opts) != UC_OBJECT) err_return_int(EINVAL, "options argument"); if ((val = ucv_object_get(opts, "rodata", NULL)) != NULL) { struct bpf_map *map = NULL; if (ucv_type(val) != UC_STRING) err_return_int(EINVAL, "rodata type"); while ((map = bpf_object__next_map(obj, map)) != NULL) { if (!strstr(bpf_map__name(map), ".rodata")) continue; break; } if (!map) err_return_int(errno, "rodata map"); if (bpf_map__set_initial_value(map, ucv_string_get(val), ucv_string_length(val))) err_return_int(errno, "rodata"); } if ((val = ucv_object_get(opts, "program-type", NULL)) != NULL) { if (ucv_type(val) != UC_OBJECT) err_return_int(EINVAL, "prog_types argument"); ucv_object_foreach(val, name, type) { struct bpf_program *prog; if (ucv_type(type) != UC_INTEGER) err_return_int(EINVAL, "program %s type", name); prog = bpf_object__find_program_by_name(obj, name); if (!prog) err_return_int(-1, "program %s not found", name); bpf_program__set_type(prog, ucv_int64_get(type)); } } return 0; } static uc_value_t * uc_bpf_open_module(uc_vm_t *vm, size_t nargs) { DECLARE_LIBBPF_OPTS(bpf_object_open_opts, bpf_opts); uc_value_t *path = uc_fn_arg(0); uc_value_t *opts = uc_fn_arg(1); struct bpf_object *obj; if (ucv_type(path) != UC_STRING) err_return(EINVAL, "module path"); init_env(); obj = bpf_object__open_file(ucv_string_get(path), &bpf_opts); if (libbpf_get_error(obj)) err_return(errno, NULL); if (uc_bpf_module_set_opts(obj, opts)) { bpf_object__close(obj); return NULL; } if (bpf_object__load(obj)) { bpf_object__close(obj); err_return(errno, NULL); } return uc_resource_new(module_type, obj); } static uc_value_t * uc_bpf_map_create(int fd, unsigned int key_size, unsigned int val_size, bool close) { struct uc_bpf_map *uc_map; uc_map = xalloc(sizeof(*uc_map)); uc_map->fd.fd = fd; uc_map->key_size = key_size; uc_map->val_size = val_size; uc_map->fd.close = close; return uc_resource_new(map_type, uc_map); } static uc_value_t * uc_bpf_open_map(uc_vm_t *vm, size_t nargs) { struct bpf_map_info info; uc_value_t *path = uc_fn_arg(0); __u32 len = sizeof(info); int err; int fd; if (ucv_type(path) != UC_STRING) err_return(EINVAL, "module path"); fd = bpf_obj_get(ucv_string_get(path)); if (fd < 0) err_return(errno, NULL); err = bpf_obj_get_info_by_fd(fd, &info, &len); if (err) { close(fd); err_return(errno, NULL); } return uc_bpf_map_create(fd, info.key_size, info.value_size, true); } static uc_value_t * uc_bpf_open_program(uc_vm_t *vm, size_t nargs) { uc_value_t *path = uc_fn_arg(0); struct uc_bpf_fd *f; int fd; if (ucv_type(path) != UC_STRING) err_return(EINVAL, "module path"); fd = bpf_obj_get(ucv_string_get(path)); if (fd < 0) err_return(errno, NULL); f = xalloc(sizeof(*f)); f->fd = fd; f->close = true; return uc_resource_new(program_type, f); } static uc_value_t * uc_bpf_module_get_maps(uc_vm_t *vm, size_t nargs) { struct bpf_object *obj = uc_fn_thisval("bpf.module"); struct bpf_map *map = NULL; uc_value_t *rv; int i = 0; if (!obj) err_return(EINVAL, NULL); rv = ucv_array_new(vm); bpf_object__for_each_map(map, obj) ucv_array_set(rv, i++, ucv_string_new(bpf_map__name(map))); return rv; } static uc_value_t * uc_bpf_module_get_map(uc_vm_t *vm, size_t nargs) { struct bpf_object *obj = uc_fn_thisval("bpf.module"); struct bpf_map *map; uc_value_t *name = uc_fn_arg(0); int fd; if (!obj || ucv_type(name) != UC_STRING) err_return(EINVAL, NULL); map = bpf_object__find_map_by_name(obj, ucv_string_get(name)); if (!map) err_return(errno, NULL); fd = bpf_map__fd(map); if (fd < 0) err_return(EINVAL, NULL); return uc_bpf_map_create(fd, bpf_map__key_size(map), bpf_map__value_size(map), false); } static uc_value_t * uc_bpf_module_get_programs(uc_vm_t *vm, size_t nargs) { struct bpf_object *obj = uc_fn_thisval("bpf.module"); struct bpf_program *prog = NULL; uc_value_t *rv; int i = 0; if (!obj) err_return(EINVAL, NULL); rv = ucv_array_new(vm); bpf_object__for_each_program(prog, obj) ucv_array_set(rv, i++, ucv_string_new(bpf_program__name(prog))); return rv; } static uc_value_t * uc_bpf_module_get_program(uc_vm_t *vm, size_t nargs) { struct bpf_object *obj = uc_fn_thisval("bpf.module"); struct bpf_program *prog; uc_value_t *name = uc_fn_arg(0); struct uc_bpf_fd *f; int fd; if (!obj || !name || ucv_type(name) != UC_STRING) err_return(EINVAL, NULL); prog = bpf_object__find_program_by_name(obj, ucv_string_get(name)); if (!prog) err_return(errno, NULL); fd = bpf_program__fd(prog); if (fd < 0) err_return(EINVAL, NULL); f = xalloc(sizeof(*f)); f->fd = fd; return uc_resource_new(program_type, f); } static void * uc_bpf_map_arg(uc_value_t *val, const char *kind, unsigned int size) { static union { uint32_t u32; uint64_t u64; } val_int; switch (ucv_type(val)) { case UC_INTEGER: if (size == 4) val_int.u32 = ucv_int64_get(val); else if (size == 8) val_int.u64 = ucv_int64_get(val); else break; return &val_int; case UC_STRING: if (size != ucv_string_length(val)) break; return ucv_string_get(val); default: err_return(EINVAL, "%s type", kind); } err_return(EINVAL, "%s size mismatch (expected: %d)", kind, size); } static uc_value_t * uc_bpf_map_get(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); uc_value_t *a_key = uc_fn_arg(0); void *key, *val; if (!map) err_return(EINVAL, NULL); key = uc_bpf_map_arg(a_key, "key", map->key_size); if (!key) return NULL; val = alloca(map->val_size); if (bpf_map_lookup_elem(map->fd.fd, key, val)) return NULL; return ucv_string_new_length(val, map->val_size); } static uc_value_t * uc_bpf_map_set(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); uc_value_t *a_key = uc_fn_arg(0); uc_value_t *a_val = uc_fn_arg(1); uc_value_t *a_flags = uc_fn_arg(2); uint64_t flags; void *key, *val; if (!map) err_return(EINVAL, NULL); key = uc_bpf_map_arg(a_key, "key", map->key_size); if (!key) return NULL; val = uc_bpf_map_arg(a_val, "value", map->val_size); if (!val) return NULL; if (!a_flags) flags = BPF_ANY; else if (ucv_type(a_flags) != UC_INTEGER) err_return(EINVAL, "flags"); else flags = ucv_int64_get(a_flags); if (bpf_map_update_elem(map->fd.fd, key, val, flags)) return NULL; return ucv_string_new_length(val, map->val_size); } static uc_value_t * uc_bpf_map_delete(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); uc_value_t *a_key = uc_fn_arg(0); uc_value_t *a_return = uc_fn_arg(1); void *key, *val = NULL; int ret; if (!map) err_return(EINVAL, NULL); key = uc_bpf_map_arg(a_key, "key", map->key_size); if (!key) return NULL; if (!ucv_is_truish(a_return)) { ret = bpf_map_delete_elem(map->fd.fd, key); return ucv_boolean_new(ret == 0); } val = alloca(map->val_size); if (bpf_map_lookup_and_delete_elem(map->fd.fd, key, val)) return NULL; return ucv_string_new_length(val, map->val_size); } static uc_value_t * uc_bpf_map_delete_all(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); uc_value_t *filter = uc_fn_arg(0); bool has_next; void *key, *next; if (!map) err_return(EINVAL, NULL); key = alloca(map->key_size); next = alloca(map->key_size); has_next = !bpf_map_get_next_key(map->fd.fd, NULL, next); while (has_next) { bool skip = false; memcpy(key, next, map->key_size); has_next = !bpf_map_get_next_key(map->fd.fd, next, next); if (ucv_is_callable(filter)) { uc_value_t *rv; uc_value_push(ucv_get(filter)); uc_value_push(ucv_string_new_length((const char *)key, map->key_size)); if (uc_call(1) != EXCEPTION_NONE) break; rv = uc_vm_stack_pop(vm); if (!rv) break; skip = !ucv_is_truish(rv); ucv_put(rv); } if (!skip) bpf_map_delete_elem(map->fd.fd, key); } return TRUE; } static uc_value_t * uc_bpf_map_iterator(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); struct uc_bpf_map_iter *iter; if (!map) err_return(EINVAL, NULL); iter = xalloc(sizeof(*iter) + map->key_size); iter->fd = map->fd.fd; iter->key_size = map->key_size; iter->has_next = !bpf_map_get_next_key(iter->fd, NULL, &iter->key); return uc_resource_new(map_iter_type, iter); } static uc_value_t * uc_bpf_map_iter_next(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map_iter *iter = uc_fn_thisval("bpf.map_iter"); uc_value_t *rv; if (!iter->has_next) return NULL; rv = ucv_string_new_length((const char *)iter->key, iter->key_size); iter->has_next = !bpf_map_get_next_key(iter->fd, &iter->key, &iter->key); return rv; } static uc_value_t * uc_bpf_map_iter_next_int(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map_iter *iter = uc_fn_thisval("bpf.map_iter"); uint64_t intval; uc_value_t *rv; if (!iter->has_next) return NULL; if (iter->key_size == 4) intval = *(uint32_t *)iter->key; else if (iter->key_size == 8) intval = *(uint64_t *)iter->key; else return NULL; rv = ucv_int64_new(intval); iter->has_next = !bpf_map_get_next_key(iter->fd, &iter->key, &iter->key); return rv; } static uc_value_t * uc_bpf_map_foreach(uc_vm_t *vm, size_t nargs) { struct uc_bpf_map *map = uc_fn_thisval("bpf.map"); uc_value_t *func = uc_fn_arg(0); bool has_next; void *key, *next; bool ret = false; key = alloca(map->key_size); next = alloca(map->key_size); has_next = !bpf_map_get_next_key(map->fd.fd, NULL, next); while (has_next) { uc_value_t *rv; bool stop; memcpy(key, next, map->key_size); has_next = !bpf_map_get_next_key(map->fd.fd, next, next); uc_value_push(ucv_get(func)); uc_value_push(ucv_string_new_length((const char *)key, map->key_size)); if (uc_call(1) != EXCEPTION_NONE) break; rv = uc_vm_stack_pop(vm); stop = (ucv_type(rv) == UC_BOOLEAN && !ucv_boolean_get(rv)); ucv_put(rv); if (stop) break; ret = true; } return ucv_boolean_new(ret); } static uc_value_t * uc_bpf_obj_pin(uc_vm_t *vm, size_t nargs, const char *type) { struct uc_bpf_fd *f = uc_fn_thisval(type); uc_value_t *path = uc_fn_arg(0); if (ucv_type(path) != UC_STRING) err_return(EINVAL, NULL); if (bpf_obj_pin(f->fd, ucv_string_get(path))) err_return(errno, NULL); return TRUE; } static uc_value_t * uc_bpf_program_pin(uc_vm_t *vm, size_t nargs) { return uc_bpf_obj_pin(vm, nargs, "bpf.program"); } static uc_value_t * uc_bpf_map_pin(uc_vm_t *vm, size_t nargs) { return uc_bpf_obj_pin(vm, nargs, "bpf.map"); } static uc_value_t * uc_bpf_set_tc_hook(uc_value_t *ifname, uc_value_t *type, uc_value_t *prio, int fd) { DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook); DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_tc, .handle = 1); const char *type_str; uint64_t prio_val; if (ucv_type(ifname) != UC_STRING || ucv_type(type) != UC_STRING || ucv_type(prio) != UC_INTEGER) err_return(EINVAL, NULL); prio_val = ucv_int64_get(prio); if (prio_val > 0xffff) err_return(EINVAL, NULL); type_str = ucv_string_get(type); if (!strcmp(type_str, "ingress")) hook.attach_point = BPF_TC_INGRESS; else if (!strcmp(type_str, "egress")) hook.attach_point = BPF_TC_EGRESS; else err_return(EINVAL, NULL); hook.ifindex = if_nametoindex(ucv_string_get(ifname)); if (!hook.ifindex) goto error; bpf_tc_hook_create(&hook); attach_tc.priority = prio_val; if (bpf_tc_detach(&hook, &attach_tc) < 0 && fd < 0) goto error; if (fd < 0) goto out; attach_tc.prog_fd = fd; if (bpf_tc_attach(&hook, &attach_tc) < 0) goto error; out: return TRUE; error: if (fd >= 0) err_return(ENOENT, NULL); return NULL; } static uc_value_t * uc_bpf_program_tc_attach(uc_vm_t *vm, size_t nargs) { struct uc_bpf_fd *f = uc_fn_thisval("bpf.program"); uc_value_t *ifname = uc_fn_arg(0); uc_value_t *type = uc_fn_arg(1); uc_value_t *prio = uc_fn_arg(2); if (!f) err_return(EINVAL, NULL); return uc_bpf_set_tc_hook(ifname, type, prio, f->fd); } static uc_value_t * uc_bpf_tc_detach(uc_vm_t *vm, size_t nargs) { uc_value_t *ifname = uc_fn_arg(0); uc_value_t *type = uc_fn_arg(1); uc_value_t *prio = uc_fn_arg(2); return uc_bpf_set_tc_hook(ifname, type, prio, -1); } static int uc_bpf_debug_print(enum libbpf_print_level level, const char *format, va_list args) { char buf[256], *str = NULL; uc_value_t *val; va_list ap; int size; va_copy(ap, args); size = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); if (size > 0 && (unsigned long)size < ARRAY_SIZE(buf) - 1) { val = ucv_string_new(buf); goto out; } if (vasprintf(&str, format, args) < 0) return 0; val = ucv_string_new(str); free(str); out: uc_vm_stack_push(debug_vm, ucv_get(ucv_array_get(registry, 0))); uc_vm_stack_push(debug_vm, ucv_int64_new(level)); uc_vm_stack_push(debug_vm, val); if (uc_vm_call(debug_vm, false, 2) == EXCEPTION_NONE) ucv_put(uc_vm_stack_pop(debug_vm)); return 0; } static uc_value_t * uc_bpf_set_debug_handler(uc_vm_t *vm, size_t nargs) { uc_value_t *handler = uc_fn_arg(0); if (handler && !ucv_is_callable(handler)) err_return(EINVAL, NULL); debug_vm = vm; libbpf_set_print(handler ? uc_bpf_debug_print : NULL); ucv_array_set(registry, 0, ucv_get(handler)); return NULL; } static void register_constants(uc_vm_t *vm, uc_value_t *scope) { #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) ADD_CONST(BPF_PROG_TYPE_SCHED_CLS); ADD_CONST(BPF_PROG_TYPE_SCHED_ACT); ADD_CONST(BPF_ANY); ADD_CONST(BPF_NOEXIST); ADD_CONST(BPF_EXIST); ADD_CONST(BPF_F_LOCK); } static const uc_function_list_t module_fns[] = { { "get_map", uc_bpf_module_get_map }, { "get_maps", uc_bpf_module_get_maps }, { "get_programs", uc_bpf_module_get_programs }, { "get_program", uc_bpf_module_get_program }, }; static void module_free(void *ptr) { struct bpf_object *obj = ptr; bpf_object__close(obj); } static const uc_function_list_t map_fns[] = { { "pin", uc_bpf_map_pin }, { "get", uc_bpf_map_get }, { "set", uc_bpf_map_set }, { "delete", uc_bpf_map_delete }, { "delete_all", uc_bpf_map_delete_all }, { "foreach", uc_bpf_map_foreach }, { "iterator", uc_bpf_map_iterator }, }; static void uc_bpf_fd_free(void *ptr) { struct uc_bpf_fd *f = ptr; if (f->close) close(f->fd); free(f); } static const uc_function_list_t map_iter_fns[] = { { "next", uc_bpf_map_iter_next }, { "next_int", uc_bpf_map_iter_next_int }, }; static const uc_function_list_t prog_fns[] = { { "pin", uc_bpf_program_pin }, { "tc_attach", uc_bpf_program_tc_attach }, }; static const uc_function_list_t global_fns[] = { { "error", uc_bpf_error }, { "set_debug_handler", uc_bpf_set_debug_handler }, { "open_module", uc_bpf_open_module }, { "open_map", uc_bpf_open_map }, { "open_program", uc_bpf_open_program }, { "tc_detach", uc_bpf_tc_detach }, }; void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); register_constants(vm, scope); registry = ucv_array_new(vm); uc_vm_registry_set(vm, "bpf.registry", registry); module_type = uc_type_declare(vm, "bpf.module", module_fns, module_free); map_type = uc_type_declare(vm, "bpf.map", map_fns, uc_bpf_fd_free); map_iter_type = uc_type_declare(vm, "bpf.map_iter", map_iter_fns, free); program_type = uc_type_declare(vm, "bpf.program", prog_fns, uc_bpf_fd_free); }