658 lines
15 KiB
C
658 lines
15 KiB
C
/* 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); */
|
|
}
|