From 8f38b9cabf03979588a05eb09b4a0e26a84573f0 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Thu, 12 Dec 2024 00:39:12 +0100 Subject: [PATCH] rtnl: eliminate usage of global variables Utilize the VM registry to store the netlink connection context and error information and refactor the related code to obtain all resources from the VM pointer in order to properly support loading the library in multiple concurrent threads or VM instances. Signed-off-by: Jo-Philipp Wich --- lib/rtnl.c | 260 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 154 insertions(+), 106 deletions(-) diff --git a/lib/rtnl.c b/lib/rtnl.c index 6d2c8192..8ef34720 100644 --- a/lib/rtnl.c +++ b/lib/rtnl.c @@ -55,7 +55,7 @@ limitations under the License. #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) -#define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) +#define err_return(code, ...) do { set_error(vm, code, __VA_ARGS__); return NULL; } while(0) #define NLM_F_STRICT_CHK (1 << 15) @@ -68,34 +68,39 @@ limitations under the License. extern unsigned int if_nametoindex (const char *); extern char *if_indextoname (unsigned int ifindex, char *ifname); -static struct { - int code; - char *msg; -} last_error; - -__attribute__((format(printf, 2, 3))) static void -set_error(int errcode, const char *fmt, ...) { +__attribute__((format(printf, 3, 4))) static void +set_error(uc_vm_t *vm, int errcode, const char *fmt, ...) +{ + uc_value_t *last_error; va_list ap; + char *s; + + last_error = uc_vm_registry_get(vm, "rtnl.error"); - free(last_error.msg); + if (!last_error) { + last_error = ucv_array_new_length(vm, 2); + uc_vm_registry_set(vm, "rtnl.error", last_error); + } - last_error.code = errcode; - last_error.msg = NULL; + ucv_array_set(last_error, 0, ucv_int64_new(errcode)); if (fmt) { va_start(ap, fmt); - xvasprintf(&last_error.msg, fmt, ap); + xvasprintf(&s, fmt, ap); va_end(ap); + + ucv_array_set(last_error, 1, ucv_string_new(s)); + free(s); + } + else { + ucv_array_set(last_error, 1, NULL); } } -static uc_resource_type_t *listener_type; -static uc_value_t *listener_registry; -static uc_vm_t *listener_vm; - typedef struct { + uc_resource_t resource; uint32_t cmds[RTNL_CMDS_BITMAP_SIZE]; - size_t index; + uc_value_t *callback; } uc_nl_listener_t; typedef struct { @@ -1285,7 +1290,7 @@ nla_parse_error(const uc_nl_attr_spec_t *spec, uc_vm_t *vm, uc_value_t *v, const s = ucv_to_string(vm, v); - set_error(NLE_INVAL, "%s `%s` has invalid value `%s`: %s", + set_error(vm, NLE_INVAL, "%s `%s` has invalid value `%s`: %s", spec->attr ? "attribute" : "field", spec->key, s, @@ -3220,12 +3225,13 @@ uc_nl_convert_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base } -static struct nl_sock *sock = NULL; -static struct { +typedef struct { + struct nl_sock *sock; struct nl_sock *evsock; struct uloop_fd evsock_fd; + struct nl_cb *evsock_cb; uint32_t groups[RTNL_GRPS_BITMAP_SIZE]; -} nl_conn; +} nl_conn_t; typedef enum { STATE_UNREPLIED, @@ -3243,30 +3249,61 @@ typedef struct { } request_state_t; +static nl_conn_t * +uc_nl_conn_ctx(uc_vm_t *vm) +{ + struct { + uc_resource_t resource; + nl_conn_t conn; + } *ctx; + + ctx = (void *)uc_vm_registry_get(vm, "rtnl.connection"); + + if (ucv_type((uc_value_t *)ctx) != UC_RESOURCE) { + ctx = xalloc(sizeof(*ctx)); + ctx->resource.header.type = UC_RESOURCE; + ctx->resource.header.refcount = 1; + ctx->resource.data = &ctx->conn; + + uc_vm_registry_set(vm, "rtnl.connection", &ctx->resource.header); + } + + return &ctx->conn; +} + static uc_value_t * uc_nl_error(uc_vm_t *vm, size_t nargs) { + uc_value_t *last_error, *msg; uc_stringbuf_t *buf; const char *s; + int code; + + last_error = uc_vm_registry_get(vm, "rtnl.error"); + code = last_error ? ucv_int64_get(ucv_array_get(last_error, 0)) : 0; + msg = ucv_array_get(last_error, 1); - if (last_error.code == 0) + if (code == 0) return NULL; buf = ucv_stringbuf_new(); - if (last_error.code == NLE_FAILURE && last_error.msg) { - ucv_stringbuf_addstr(buf, last_error.msg, strlen(last_error.msg)); + if (code == NLE_FAILURE && msg) { + ucv_stringbuf_addstr(buf, ucv_string_get(msg), ucv_string_length(msg)); } else { - s = nl_geterror(last_error.code); + s = nl_geterror(code); ucv_stringbuf_addstr(buf, s, strlen(s)); - if (last_error.msg) - ucv_stringbuf_printf(buf, ": %s", last_error.msg); + if (msg) { + ucv_stringbuf_append(buf, ": "); + ucv_stringbuf_addstr(buf, + ucv_string_get(msg), ucv_string_length(msg)); + } } - set_error(0, NULL); + set_error(vm, 0, NULL); return ucv_stringbuf_finish(buf); } @@ -3291,7 +3328,7 @@ cb_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) request_state_t *s = arg; int errnum = err->error; - set_error(NLE_FAILURE, "RTNETLINK answers: %s", + set_error(s->vm, NLE_FAILURE, "RTNETLINK answers: %s", strerror(errnum < 0 ? -errnum : errnum)); s->state = STATE_ERROR; @@ -3361,6 +3398,7 @@ static const struct { static uc_value_t * uc_nl_request(uc_vm_t *vm, size_t nargs) { + nl_conn_t *conn = uc_nl_conn_ctx(vm); uc_value_t *cmd = uc_fn_arg(0); uc_value_t *flags = uc_fn_arg(1); uc_value_t *payload = uc_fn_arg(2); @@ -3393,13 +3431,13 @@ uc_nl_request(uc_vm_t *vm, size_t nargs) } } - if (!sock) { - sock = nl_socket_alloc(); + if (!conn->sock) { + conn->sock = nl_socket_alloc(); - if (!sock) + if (!conn->sock) err_return(NLE_NOMEM, NULL); - err = nl_connect(sock, NETLINK_ROUTE); + err = nl_connect(conn->sock, NETLINK_ROUTE); if (err != 0) err_return(err, NULL); @@ -3407,13 +3445,13 @@ uc_nl_request(uc_vm_t *vm, size_t nargs) optlen = sizeof(enable); - if (getsockopt(sock->s_fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &enable, &optlen) < 0) + if (getsockopt(conn->sock->s_fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &enable, &optlen) < 0) enable = 0; if (!!(flagval & NLM_F_STRICT_CHK) != enable) { enable = !!(flagval & NLM_F_STRICT_CHK); - if (setsockopt(sock->s_fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &enable, sizeof(enable)) < 0) + if (setsockopt(conn->sock->s_fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &enable, sizeof(enable)) < 0) err_return(nl_syserr2nlerr(errno), "Unable to toggle NETLINK_GET_STRICT_CHK"); } @@ -3454,13 +3492,13 @@ uc_nl_request(uc_vm_t *vm, size_t nargs) nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_done, &st); nl_cb_err(cb, NL_CB_CUSTOM, cb_error, &st); - nl_send_auto_complete(sock, msg); + nl_send_auto_complete(conn->sock, msg); do { - err = nl_recvmsgs(sock, cb); + err = nl_recvmsgs(conn->sock, cb); if (err && st.state != STATE_ERROR) { - set_error(err, NULL); + set_error(vm, err, NULL); st.state = STATE_ERROR; } @@ -3481,7 +3519,7 @@ uc_nl_request(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(false); default: - set_error(NLE_FAILURE, "Interrupted reply"); + set_error(vm, NLE_FAILURE, "Interrupted reply"); return ucv_boolean_new(false); } @@ -3576,38 +3614,45 @@ uc_nl_fill_cmds(uint32_t *cmd_bits, uc_value_t *cmds) return true; } +#define uc_nl_listener_foreach(vm, r_, i_, l_) \ + for (uc_value_t *r_ = uc_vm_registry_get(vm, "rtnl.registry"); \ + r_ != NULL; \ + r_ = NULL) \ + for (size_t i_ = 0, i_##_length_ = ucv_array_length(r_); \ + i_##_length_ > 0; \ + i_##_length_ = 0) \ + for (uc_nl_listener_t *l_ = ucv_resource_data(ucv_array_get(r_, 0), \ + "rtnl.listener"); \ + i_ < i_##_length_; \ + l_ = ucv_resource_data(ucv_array_get(r_, ++i_), "rtnl.listener")) + static int cb_listener_event(struct nl_msg *msg, void *arg) { struct nlmsghdr *hdr = nlmsg_hdr(msg); - uc_vm_t *vm = listener_vm; + uc_vm_t *vm = arg; + nl_conn_t *conn = uc_nl_conn_ctx(vm); int cmd = hdr->nlmsg_type; - if (!nl_conn.evsock_fd.registered || !vm) + if (!conn->evsock_fd.registered) return NL_SKIP; - for (size_t i = 0; i < ucv_array_length(listener_registry); i += 2) { - uc_value_t *this = ucv_array_get(listener_registry, i); - uc_value_t *func = ucv_array_get(listener_registry, i + 1); - uc_nl_listener_t *l; - uc_value_t *o; - - l = ucv_resource_data(this, "rtnl.listener"); - if (!l) + uc_nl_listener_foreach(vm, registry, i, listener) { + if (!listener) continue; - if (cmd > __RTM_MAX || !(l->cmds[cmd / 32] & (1 << (cmd % 32)))) + if (cmd > __RTM_MAX || !(listener->cmds[cmd / 32] & (1 << (cmd % 32)))) continue; - if (!ucv_is_callable(func)) + if (!ucv_is_callable(listener->callback)) continue; - o = ucv_object_new(vm); + uc_value_t *o = ucv_object_new(vm); uc_nl_prepare_event(vm, o, msg); ucv_object_add(o, "cmd", ucv_int64_new(cmd)); - uc_vm_stack_push(vm, ucv_get(this)); - uc_vm_stack_push(vm, ucv_get(func)); + uc_vm_stack_push(vm, ucv_get(&listener->resource.header)); + uc_vm_stack_push(vm, ucv_get(listener->callback)); uc_vm_stack_push(vm, o); if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) { @@ -3626,40 +3671,37 @@ cb_listener_event(struct nl_msg *msg, void *arg) static void uc_nl_listener_cb(struct uloop_fd *fd, unsigned int events) { + nl_conn_t *conn = container_of(fd, nl_conn_t, evsock_fd); + while (true) { errno = 0; + nl_recvmsgs_default(conn->evsock); - nl_recvmsgs_default(nl_conn.evsock); - - if (errno != 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) - set_error(errno, NULL); - + if (errno != 0) break; - } } } static void -uc_nl_add_group(unsigned int idx) +uc_nl_add_group(nl_conn_t *conn, unsigned int idx) { if (idx >= __RTNLGRP_MAX) return; - if (nl_conn.groups[idx / 32] & (1 << (idx % 32))) + if (conn->groups[idx / 32] & (1 << (idx % 32))) return; - nl_conn.groups[idx / 32] |= (1 << (idx % 32)); - nl_socket_add_membership(nl_conn.evsock, idx); + conn->groups[idx / 32] |= (1 << (idx % 32)); + nl_socket_add_membership(conn->evsock, idx); } static bool -uc_nl_evsock_init(void) +uc_nl_evsock_init(uc_vm_t *vm, nl_conn_t *conn) { - struct uloop_fd *fd = &nl_conn.evsock_fd; + struct uloop_fd *fd = &conn->evsock_fd; struct nl_sock *sock; - if (nl_conn.evsock) + if (conn->evsock) return true; sock = nl_socket_alloc(); @@ -3673,9 +3715,9 @@ uc_nl_evsock_init(void) nl_socket_set_buffer_size(sock, 1024 * 1024, 0); nl_socket_disable_seq_check(sock); - nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, cb_listener_event, NULL); + nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, cb_listener_event, vm); - nl_conn.evsock = sock; + conn->evsock = sock; return true; @@ -3687,66 +3729,71 @@ uc_nl_evsock_init(void) static uc_value_t * uc_nl_listener(uc_vm_t *vm, size_t nargs) { - uc_nl_listener_t *l; + nl_conn_t *conn = uc_nl_conn_ctx(vm); uc_value_t *cb_func = uc_fn_arg(0); uc_value_t *cmds = uc_fn_arg(1); uc_value_t *groups = uc_fn_arg(2); - uc_value_t *rv; - size_t i; if (!ucv_is_callable(cb_func)) { uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid callback"); return NULL; } - if (!uc_nl_evsock_init()) + if (!uc_nl_evsock_init(vm, conn)) return NULL; if (ucv_type(groups) == UC_ARRAY) { - for (i = 0; i < ucv_array_length(groups); i++) { + for (size_t i = 0; i < ucv_array_length(groups); i++) { int64_t n = ucv_int64_get(ucv_array_get(groups, i)); if (errno || n < 0 || n >= __RTNLGRP_MAX) err_return(NLE_INVAL, NULL); - uc_nl_add_group(n); + uc_nl_add_group(conn, n); } - } else { - uc_nl_add_group(RTNLGRP_LINK); } - - for (i = 0; i < ucv_array_length(listener_registry); i += 2) { - if (!ucv_array_get(listener_registry, i)) - break; + else { + uc_nl_add_group(conn, RTNLGRP_LINK); } - l = xalloc(sizeof(*l)); - l->index = i; + uc_nl_listener_t *listener = xalloc(sizeof(*listener)); + + listener->resource.header.type = UC_RESOURCE; + listener->resource.header.refcount = 1; + listener->resource.type = ucv_resource_type_lookup(vm, "rtnl.listener"); + listener->resource.data = listener; + listener->callback = ucv_get(cb_func); - if (!uc_nl_fill_cmds(l->cmds, cmds)) { + if (!uc_nl_fill_cmds(listener->cmds, cmds)) { uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid command ID"); - free(l); + ucv_put(&listener->resource.header); + return NULL; } - rv = uc_resource_new(listener_type, l); - - ucv_array_set(listener_registry, i, ucv_get(rv)); - ucv_array_set(listener_registry, i + 1, ucv_get(cb_func)); + uc_nl_listener_foreach(vm, registry, i, listener) { + if (listener == NULL) { + ucv_array_set(registry, i, ucv_get(&listener->resource.header)); + goto out; + } + } - listener_vm = vm; + ucv_array_push(uc_vm_registry_get(vm, "rtnl.registry"), + ucv_get(&listener->resource.header)); - return rv; +out: + return &listener->resource.header; } static void uc_nl_listener_free(void *arg) { - uc_nl_listener_t *l = arg; + uc_nl_listener_t *listener = arg; - ucv_array_set(listener_registry, l->index, NULL); - ucv_array_set(listener_registry, l->index + 1, NULL); - free(l); + if (!listener) + return; + + ucv_put(listener->callback); } static uc_value_t * @@ -3769,17 +3816,20 @@ static uc_value_t * uc_nl_listener_close(uc_vm_t *vm, size_t nargs) { uc_nl_listener_t **lptr = uc_fn_this("rtnl.listener"); - uc_nl_listener_t *l; - if (!lptr) + if (!lptr || !*lptr) return NULL; - l = *lptr; - if (!l) - return NULL; + uc_nl_listener_foreach(vm, registry, i, listener) { + if (listener == *lptr) { + ucv_array_set(registry, i, NULL); + break; + } + } + + uc_nl_listener_free(*lptr); *lptr = NULL; - uc_nl_listener_free(l); return NULL; } @@ -4166,10 +4216,8 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); - listener_type = uc_type_declare(vm, "rtnl.listener", listener_fns, uc_nl_listener_free); - listener_registry = ucv_array_new(vm); - - uc_vm_registry_set(vm, "rtnl.registry", listener_registry); + uc_type_declare(vm, "rtnl.listener", listener_fns, uc_nl_listener_free); + uc_vm_registry_set(vm, "rtnl.registry", ucv_array_new(vm)); register_constants(vm, scope); }