diff options
Diffstat (limited to 'tests/nl-test-util.c')
-rw-r--r-- | tests/nl-test-util.c | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/tests/nl-test-util.c b/tests/nl-test-util.c new file mode 100644 index 00000000..1f67ac88 --- /dev/null +++ b/tests/nl-test-util.c @@ -0,0 +1,657 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ + +#include "nl-test-util.h" + +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <sched.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <unistd.h> + +#include "netlink-private/utils.h" +#include "netlink/netlink.h" +#include "netlink/route/link.h" +#include "netlink/route/route.h" +#include "netlink/socket.h" + +/*****************************************************************************/ + +void _nltst_get_urandom(void *ptr, size_t len) +{ + int fd; + ssize_t nread; + + ck_assert_int_gt(len, 0); + ck_assert_ptr_nonnull(ptr); + + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); + _nltst_assert_errno(fd >= 0); + + nread = read(fd, ptr, len); + _nltst_assert_errno(nread == len); + + _nltst_close(fd); +} + +uint32_t _nltst_rand_u32(void) +{ + _nl_thread_local static unsigned short entropy[3]; + _nl_thread_local static bool has_entropy = false; + + if (!has_entropy) { + unsigned long long seed; + uint64_t seed64; + const char *s; + char *s_end; + + memset(entropy, 0, sizeof(entropy)); + s = getenv("NLTST_SEED_RAND"); + if (!s) + seed = 0; + else if (s[0] != '\0') { + errno = 0; + seed = strtoull(s, &s_end, 10); + if (errno != 0 || s_end[0] != '\0') { + ck_assert_msg( + 0, + "invalid NLTST_SEED_RAND=\"%s\". Must be an integer to seed the random numbers", + s); + } + } else + _nltst_get_urandom(&seed, sizeof(seed)); + + seed64 = seed; + printf("runs with NLTST_SEED_RAND=%" PRIu64 "\n", seed64); + + entropy[0] = (seed64 >> 0) ^ (seed64 >> 48); + entropy[1] = (seed64 >> 16) ^ (seed64 >> 0); + entropy[2] = (seed64 >> 32) ^ (seed64 >> 16); + has_entropy = true; + } + + _NL_STATIC_ASSERT(sizeof(long) >= sizeof(uint32_t)); + return jrand48(entropy); +} + +/*****************************************************************************/ + +#define _CANARY 539339 + +struct nltst_netns { + int canary; +}; + +/*****************************************************************************/ + +#define _assert_nltst_netns(nsdata) \ + do { \ + const struct nltst_netns *_nsdata = (nsdata); \ + \ + ck_assert_ptr_nonnull(_nsdata); \ + ck_assert_int_eq(_nsdata->canary, _CANARY); \ + } while (0) + +static struct { + struct nltst_netns *nsdata; +} _netns_fixture_global; + +void nltst_netns_fixture_setup(void) +{ + ck_assert(!_netns_fixture_global.nsdata); + + _netns_fixture_global.nsdata = nltst_netns_enter(); + _assert_nltst_netns(_netns_fixture_global.nsdata); +} + +void nltst_netns_fixture_teardown(void) +{ + _assert_nltst_netns(_netns_fixture_global.nsdata); + _nl_clear_pointer(&_netns_fixture_global.nsdata, nltst_netns_leave); +} + +/*****************************************************************************/ + +static void unshare_user(void) +{ + const uid_t uid = geteuid(); + const gid_t gid = getegid(); + FILE *f; + int r; + + /* Become a root in new user NS. */ + r = unshare(CLONE_NEWUSER); + _nltst_assert_errno(r == 0); + + /* Since Linux 3.19 we have to disable setgroups() in order to map users. + * Just proceed if the file is not there. */ + f = fopen("/proc/self/setgroups", "we"); + if (f) { + r = fprintf(f, "deny"); + _nltst_assert_errno(r > 0); + _nltst_fclose(f); + } + + /* Map current UID to root in NS to be created. */ + f = fopen("/proc/self/uid_map", "we"); + if (!f) { + if (errno == EACCES) { + /* Accessing uid_map might fail due to sandboxing. + * We ignore that error and proceed with the test. It will probably + * still work. */ + return; + } + _nltst_assert_errno(f); + } + r = fprintf(f, "0 %d 1", uid); + _nltst_assert_errno(r > 0); + _nltst_fclose(f); + + /* Map current GID to root in NS to be created. */ + f = fopen("/proc/self/gid_map", "we"); + _nltst_assert_errno(f); + r = fprintf(f, "0 %d 1", gid); + _nltst_assert_errno(r > 0); + _nltst_fclose(f); +} + +struct nltst_netns *nltst_netns_enter(void) +{ + struct nltst_netns *nsdata; + int r; + + nsdata = calloc(1, sizeof(struct nltst_netns)); + ck_assert(nsdata); + + nsdata->canary = _CANARY; + + unshare_user(); + + r = unshare(CLONE_NEWNET | CLONE_NEWNS); + _nltst_assert_errno(r == 0); + + /* We need a read-only /sys so that the platform knows there's no udev. */ + mount(NULL, "/sys", "sysfs", MS_SLAVE, NULL); + r = mount("sys", "/sys", "sysfs", MS_RDONLY, NULL); + _nltst_assert_errno(r == 0); + + return nsdata; +} + +void nltst_netns_leave(struct nltst_netns *nsdata) +{ + ck_assert(nsdata); + ck_assert_int_eq(nsdata->canary, _CANARY); + + /* nltst_netns_leave() was supposed to enter the original namespaces again + * and undo enter. + * + * However, I could get it to work (setns() always fails with EPERM) + * and valgrind on current Ubuntu seems not to support setns() call. + * + * So, do nothing. It's not really a problem, because the next test + * either should unshare yet another namespace, or not care about + * such things. */ + + free(nsdata); +} + +/*****************************************************************************/ + +void _nltst_object_identical(const void *a, const void *b) +{ + struct nl_object *o_a = (void *)a; + struct nl_object *o_b = (void *)b; + + ck_assert(a); + ck_assert(b); + + ck_assert_int_eq(nl_object_identical(o_a, o_b), 1); + ck_assert_int_eq(nl_object_identical(o_b, o_a), 1); + ck_assert_int_eq(nl_object_diff64(o_b, o_a), 0); + ck_assert_int_eq(nl_object_diff64(o_a, o_b), 0); + ck_assert_int_eq(nl_object_diff(o_a, o_b), 0); + ck_assert_int_eq(nl_object_diff(o_b, o_a), 0); +} + +/*****************************************************************************/ + +char *_nltst_object_to_string(struct nl_object *obj) +{ + size_t L = 1024; + size_t l; + char *s; + + if (!obj) + return strdup("(null)"); + + s = malloc(L); + ck_assert_ptr_nonnull(s); + + nl_object_dump_buf(obj, s, L); + l = strlen(s); + ck_assert_int_lt(l, L); + s = realloc(s, l + 1); + ck_assert_ptr_nonnull(s); + return s; +} + +struct cache_get_all_data { + struct nl_object **arr; + size_t len; + size_t idx; +}; + +static void _cache_get_all_fcn(struct nl_object *obj, void *user_data) +{ + struct cache_get_all_data *data = user_data; + size_t i; + + ck_assert(obj); + ck_assert_int_lt(data->idx, data->len); + + for (i = 0; i < data->idx; i++) + ck_assert_ptr_ne(data->arr[i], obj); + + data->arr[data->idx++] = obj; +} + +struct nl_object **_nltst_cache_get_all(struct nl_cache *cache, size_t *out_len) +{ + int nitems; + struct cache_get_all_data data = { + .idx = 0, + .len = 0, + }; + size_t len2 = 0; + size_t i; + size_t j; + + ck_assert(cache); + + nitems = nl_cache_nitems(cache); + ck_assert_int_ge(nitems, 0); + + data.len = nitems; + data.arr = malloc(sizeof(struct nl_object *) * (data.len + 1)); + ck_assert_ptr_nonnull(data.arr); + + nl_cache_foreach(cache, _cache_get_all_fcn, &data); + + ck_assert_int_eq(data.idx, data.len); + + ck_assert_int_le(data.len, SSIZE_MAX); + + data.arr[data.len] = NULL; + if (out_len) + *out_len = data.len; + + /* double check the result. */ + for (struct nl_object *obj = nl_cache_get_first(cache); obj; + obj = nl_cache_get_next(obj)) { + ck_assert_ptr_eq(data.arr[len2], obj); + len2++; + } + ck_assert_ptr_null(data.arr[len2]); + + for (i = 0; i < data.len; i++) { + ck_assert_ptr_nonnull(data.arr[i]); + for (j = i + 1; j < data.len; j++) + ck_assert_ptr_ne(data.arr[i], data.arr[j]); + } + + return data.arr; +} + +struct rtnl_link *_nltst_cache_get_link(struct nl_cache *cache, + const char *ifname) +{ + _nl_auto_free struct nl_object **objs = NULL; + struct rtnl_link *link = NULL; + size_t i; + + ck_assert_ptr_nonnull(cache); + ck_assert_ptr_nonnull(ifname); + + objs = _nltst_cache_get_all(cache, NULL); + for (i = 0; objs[i]; i++) { + if (_nl_streq(rtnl_link_get_name((struct rtnl_link *)objs[i]), + ifname)) { + ck_assert_ptr_null(link); + link = (struct rtnl_link *)objs[i]; + } + } + + if (_nltst_rand_u32_range(5) == 0) { + _nl_auto_rtnl_link struct rtnl_link *link2 = NULL; + + link2 = rtnl_link_get_by_name(cache, ifname); + ck_assert_ptr_eq(link2, link); + } + + return link; +} + +/*****************************************************************************/ + +struct nl_sock *_nltst_socket(int protocol) +{ + struct nl_sock *sk; + int r; + + sk = nl_socket_alloc(); + ck_assert(sk); + + r = nl_connect(sk, protocol); + ck_assert_int_eq(r, 0); + + if (_nltst_rand_u32_range(5) == 0) + nl_cache_free(_nltst_rtnl_link_alloc_cache(sk, AF_UNSPEC, 0)); + + if (_nltst_rand_u32_range(5) == 0) + nl_cache_free(_nltst_rtnl_route_alloc_cache( + sk, _nltst_rand_select(AF_UNSPEC, AF_INET, AF_INET6))); + + return sk; +} + +void _nltst_add_link(struct nl_sock *sk, const char *ifname, const char *kind, + int *out_ifindex) +{ + _nl_auto_nl_socket struct nl_sock *sk_free = NULL; + _nl_auto_rtnl_link struct rtnl_link *link = NULL; + _nl_auto_nl_cache struct nl_cache *cache = NULL; + struct rtnl_link *link2; + int ifindex; + int r; + + ck_assert(ifname); + ck_assert(kind); + + if (_nltst_rand_u32_range(5) == 0) + _nltst_assert_link_not_exists(ifname); + + if (!sk) { + sk = _nltst_socket(NETLINK_ROUTE); + sk_free = sk; + } + + link = rtnl_link_alloc(); + ck_assert(link); + + r = rtnl_link_set_type(link, kind); + ck_assert_int_eq(r, 0); + + rtnl_link_set_name(link, ifname); + + r = rtnl_link_add(sk, link, NLM_F_CREATE); + ck_assert_int_eq(r, 0); + + if (!out_ifindex && _nltst_rand_u32_range(5) != 0) + return; + + cache = _nltst_rtnl_link_alloc_cache(sk, AF_UNSPEC, 0); + + link2 = _nltst_cache_get_link(cache, ifname); + ck_assert_ptr_nonnull(link2); + + ifindex = rtnl_link_get_ifindex(link2); + ck_assert_int_gt(ifindex, 0); + + if (out_ifindex) + *out_ifindex = ifindex; +} + +void _nltst_delete_link(struct nl_sock *sk, const char *ifname) +{ + _nl_auto_nl_socket struct nl_sock *sk_free = NULL; + _nl_auto_rtnl_link struct rtnl_link *link = NULL; + + _nltst_assert_link_exists(ifname); + + if (!sk) { + sk = _nltst_socket(NETLINK_ROUTE); + sk_free = sk; + } + + if (_nltst_rand_u32_range(5) == 0) { + _nl_auto_nl_cache struct nl_cache *cache = NULL; + + cache = _nltst_rtnl_link_alloc_cache(sk, AF_UNSPEC, 0); + ck_assert_ptr_nonnull(_nltst_cache_get_link(cache, ifname)); + } + + link = rtnl_link_alloc(); + ck_assert_ptr_nonnull(link); + + rtnl_link_set_name(link, ifname); + + _nltst_assert_retcode(rtnl_link_delete(sk, link)); + + _nltst_assert_link_not_exists(ifname); +} + +void _nltst_get_link(struct nl_sock *sk, const char *ifname, int *out_ifindex, + struct rtnl_link **out_link) +{ + _nl_auto_nl_cache struct nl_cache *cache = NULL; + struct rtnl_link *link; + + if (_nltst_rand_u32_range(5) == 0) + _nltst_assert_link_exists(ifname); + + cache = _nltst_rtnl_link_alloc_cache(sk, AF_UNSPEC, 0); + + link = _nltst_cache_get_link(cache, ifname); + ck_assert(link); + + if (out_ifindex) + *out_ifindex = rtnl_link_get_ifindex(link); + + if (out_link) { + nl_object_get((struct nl_object *)link); + *out_link = link; + } +} + +struct nl_cache *_nltst_rtnl_link_alloc_cache(struct nl_sock *sk, + int addr_family, unsigned flags) +{ + _nl_auto_nl_socket struct nl_sock *sk_free = NULL; + struct nl_cache *cache; + int r; + + if (!sk) { + sk = _nltst_socket(NETLINK_ROUTE); + sk_free = sk; + } + + if (flags == 0 && _nltst_rand_bool()) + r = rtnl_link_alloc_cache(sk, addr_family, &cache); + else + r = rtnl_link_alloc_cache_flags(sk, addr_family, &cache, flags); + + _nltst_assert_retcode(r); + + if (_nltst_rand_u32_range(5) == 0) + free(_nltst_cache_get_all(cache, NULL)); + + return _nltst_assert(cache); +} + +struct nl_cache *_nltst_rtnl_route_alloc_cache(struct nl_sock *sk, + int addr_family) +{ + struct nl_cache *cache; + + ck_assert_ptr_nonnull(sk); + ck_assert(addr_family == AF_UNSPEC || addr_family == AF_INET || + addr_family == AF_INET6); + + _nltst_assert_retcode( + rtnl_route_alloc_cache(sk, addr_family, 0, &cache)); + + if (_nltst_rand_u32_range(5) == 0) + free(_nltst_cache_get_all(cache, NULL)); + + return _nltst_assert(cache); +} + +/*****************************************************************************/ + +char *_nltst_strtok(const char **p_str) +{ + const char *str; + _nl_auto_free char *dst = NULL; + size_t dst_len = 0; + size_t dst_alloc = 0; + size_t i; + + ck_assert_ptr_nonnull(p_str); + + str = _nltst_str_skip_space(*p_str); + + if (str[0] == '\0') { + *p_str = str; + return NULL; + } + + dst_len = 0; + dst_alloc = 10; + dst = malloc(dst_alloc); + ck_assert_ptr_nonnull(dst); + + i = 0; + while (true) { + char ch1 = '\0'; + char ch2 = '\0'; + + /* We take the first word, up until whitespace. Note that backslash + * escape is honored, so you can backslash escape spaces. The returned + * string will NOT have backslashes removed. */ + + if (str[i] == '\0') { + *p_str = &str[i]; + break; + } + if (_nltst_char_is_space(str[i])) { + *p_str = _nltst_str_skip_space(&str[i + 1]); + break; + } + ch1 = str[i]; + if (str[i] == '\\') { + if (str[i + 1] != '\0') { + ch2 = str[i + 1]; + i += 2; + } else + i += 1; + } else + i += 1; + + if (dst_len + 3 >= dst_alloc) { + dst_alloc *= 2; + dst = realloc(dst, dst_alloc); + ck_assert_ptr_nonnull(dst); + } + dst[dst_len++] = ch1; + if (ch2 != '\0') + dst[dst_len++] = ch2; + } + + ck_assert_int_gt(dst_len, 0); + return strndup(dst, dst_len); +} + +char **_nltst_strtokv(const char *str) +{ + _nl_auto_free char *s = NULL; + _nltst_auto_strfreev char **result = NULL; + size_t r_len = 0; + size_t r_alloc = 0; + + if (!str) + return NULL; + + r_alloc = 4; + result = malloc(sizeof(char *) * r_alloc); + ck_assert_ptr_nonnull(result); + + while ((s = _nltst_strtok(&str))) { + if (r_len + 2 >= r_alloc) { + r_alloc *= 2; + result = realloc(result, sizeof(char *) * r_alloc); + ck_assert_ptr_nonnull(result); + } + result[r_len++] = _nl_steal_pointer(&s); + } + ck_assert_int_lt(r_len, r_alloc); + result[r_len] = NULL; + return _nl_steal_pointer(&result); +} + +/*****************************************************************************/ + +void _nltst_assert_link_exists_full(const char *ifname, bool exists) +{ + _nl_auto_nl_cache struct nl_cache *cache = NULL; + _nl_auto_rtnl_link struct rtnl_link *link_clone = NULL; + struct rtnl_link *link; + char path[100]; + struct stat st; + int rnd; + int r; + + ck_assert_pstr_ne(ifname, NULL); + ck_assert_int_lt(strlen(ifname), IFNAMSIZ); + + strcpy(path, "/sys/class/net/"); + strcat(path, ifname); + ck_assert_int_lt(strlen(path), sizeof(path)); + + r = stat(path, &st); + if (exists) { + if (r != 0) { + const int errsv = (errno); + + ck_assert_msg( + 0, + "link(%s) does not exist (stat(%s) failed with r=%d, errno=%d (%s)", + ifname, path, r, errsv, strerror(errsv)); + } + } else { + if (r != -1 && errno != ENOENT) { + const int errsv = (errno); + + ck_assert_msg( + 0, + "link(%s) should not exist but stat(%s) gave r=%d, errno=%d (%s)", + ifname, path, r, errsv, strerror(errsv)); + } + } + + rnd = _nltst_rand_u32_range(3); + + if (rnd == 0) + return; + + cache = _nltst_rtnl_link_alloc_cache(NULL, AF_UNSPEC, 0); + + link = _nltst_cache_get_link(cache, ifname); + if (exists) + ck_assert_ptr_nonnull(link); + else + ck_assert_ptr_null(link); + + if (!link || rnd == 1) + return; + + link_clone = + (struct rtnl_link *)nl_object_clone((struct nl_object *)link); + ck_assert(link_clone); + + /* FIXME: we would expect that the cloned object is identical. It is not. */ + /* _nltst_object_identical(link, link_clone); */ +} |