/* SPDX-License-Identifier: LGPL-2.1-only */ #include "nl-default.h" #include "nl-test-util.h" #include #include #include #include #include #include #include #include #include #include "lib/route/nl-route.h" #include "nl-aux-route/nl-route.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 >= 0 && ((size_t)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(const struct nl_object *obj) { size_t L = 1024; size_t l; char *s; struct nl_dump_params dp; char canary; if (!obj) return strdup("(null)"); s = malloc(L); ck_assert_ptr_nonnull(s); canary = _nltst_rand_u32(); s[L - 1u] = canary; dp = (struct nl_dump_params){ .dp_type = NL_DUMP_LINE, .dp_buf = s, .dp_buflen = L - 1u, }; nl_object_dump((struct nl_object *)obj, &dp); l = strlen(s); ck_assert_int_ge(l, 2); ck_assert_int_lt(l, L); ck_assert(canary == s[L - 1u]); s = realloc(s, l + 1); ck_assert_ptr_nonnull(s); ck_assert_msg(s[l - 1u] == '\n', "expects newline after dump. Got \"%s\"", s); s[l - 1u] = '\0'; ck_assert_msg(!strchr(s, '\n'), "no further newline expected. Got \"%s\"", 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); */ } /*****************************************************************************/ bool _nltst_in_ci(void) { return _nl_streq0(getenv("NLTST_IN_CI"), "1"); } /*****************************************************************************/ bool _nltst_has_iproute2(void) { static int has = -1; if (has == -1) has = (system("ip link &>/dev/null") == 0); return has; } bool _nltst_skip_no_iproute2(const char *msg) { if (_nltst_has_iproute2()) return false; ck_assert_msg( !_nltst_in_ci(), "We seem to not have iproute2, but we are in NLTST_IN_CI=1. This is fatal."); printf("skip test due to missing iproute2%s%s%s\n", msg ? " (" : "", msg ?: "", msg ? ")" : ""); return true; } /*****************************************************************************/ void _nltst_select_route_clear(NLTstSelectRoute *select_route) { _nltst_assert_select_route(select_route); _nl_clear_free(&select_route->addr); _nl_clear_free(&select_route->addr_pattern); } int _nltst_select_route_cmp(const NLTstSelectRoute *select_route1, const NLTstSelectRoute *select_route2) { _NL_CMP_SELF(select_route1, select_route2); _NL_CMP_FIELD_STR0(select_route1, select_route2, addr); _NL_CMP_FIELD_STR0(select_route1, select_route2, addr_pattern); _NL_CMP_FIELD(select_route1, select_route2, addr_family); _NL_CMP_FIELD(select_route1, select_route2, ifindex); _NL_CMP_FIELD(select_route1, select_route2, plen); return 0; } char *_nltst_select_route_to_string(const NLTstSelectRoute *select_route) { char buf[1024]; const char *family; char b_plen[100]; _nltst_assert_select_route(select_route); if (select_route->addr_family == AF_INET) family = "4 "; else if (select_route->addr_family == AF_INET6) family = "6 "; else family = ""; b_plen[0] = '\0'; if (select_route->plen != -1) _nltst_sprintf_arr(b_plen, "/%d", select_route->plen); _nltst_sprintf_arr(buf, "%s" "%s" "%s" "", family, select_route->addr_pattern ?: select_route->addr, b_plen); return _nltst_strdup(buf); } void _nltst_select_route_parse(const char *str, NLTstSelectRoute *out_select_route) { _nltst_auto_strfreev char **tokens0 = NULL; const char *const *tokens; int addr_family = AF_UNSPEC; int addr_family2 = AF_UNSPEC; NLTstIPAddr addr; int plen = -1; _nl_auto_free char *addr_free = NULL; const char *s_addr_pattern; const char *s_addr = NULL; const char *s; const char *s1; ck_assert_ptr_nonnull(str); _nltst_assert_select_route(out_select_route); tokens0 = _nltst_strtokv(str); tokens = (const char *const *)tokens0; s = tokens[0]; if (!s) ck_abort_msg("invalid empty route pattern \"%s\"", str); if (_nl_streq(s, "4") || _nl_streq(s, "inet") || _nl_streq(s, "inet4")) { addr_family = AF_INET; tokens++; } else if (_nl_streq(s, "6") || _nl_streq(s, "inet6")) { addr_family = AF_INET6; tokens++; } s_addr_pattern = tokens[0]; if (!s_addr_pattern) { ck_abort_msg( "the route pattern \"%s\" is invalid and contains no destination address", str); } tokens++; s = strchr(s_addr_pattern, '/'); if (s) { long int plen2; if (s == s_addr_pattern) { ck_abort_msg( "the route pattern \"%s\" contains no valid destination address", str); } addr_free = strndup(s_addr_pattern, s - s_addr_pattern); s_addr_pattern = addr_free; s++; errno = 0; plen2 = strtol(s, (char **)&s1, 10); if (errno != 0 || s1[0] != '\0' || plen2 < 0 || plen2 > 128 || ((_nltst_str_find_first_not_from_charset( s, "0123456789"))[0] != '\0')) { ck_abort_msg( "the route pattern \"%s\" contains no valid destination address", str); } plen = plen2; } if ((_nltst_str_find_first_not_from_charset( s_addr_pattern, "abcdefABCDEF0123456789:.?*"))[0] != '\0') { ck_abort_msg( "the route pattern \"%s\" contains no valid destination address", str); } if (_nltst_inet_pton(addr_family, s_addr_pattern, &addr_family2, &addr)) { free(addr_free); addr_free = _nltst_inet_ntop_dup(addr_family2, &addr); s_addr_pattern = addr_free; addr_family = addr_family2; } else { if (addr_family == AF_UNSPEC) { ck_abort_msg( "the route pattern \"%s\" contains a wild card address, it requires the address family", str); } } ck_assert(addr_family == AF_INET || addr_family == AF_INET6); if (plen > (addr_family == AF_INET ? 32 : 128)) { ck_abort_msg( "the route pattern \"%s\" contains no valid destination address (prefix length too large)", str); } ck_assert_int_ge(plen, -1); s = tokens[0]; if (s) { ck_abort_msg("the route pattern \"%s\" contains extra tokens", str); } if (_nltst_inet_valid(addr_family, s_addr_pattern)) _NL_SWAP(&s_addr, &s_addr_pattern); _nltst_select_route_clear(out_select_route); memset(out_select_route, 0, sizeof(*out_select_route)); *out_select_route = (NLTstSelectRoute){ .addr_family = addr_family, .plen = plen, .ifindex = 0, .addr = s_addr ? strdup(s_addr) : NULL, .addr_pattern = s_addr_pattern ? strdup(s_addr_pattern) : NULL, }; ck_assert(!s_addr || out_select_route->addr); ck_assert(!s_addr_pattern || out_select_route->addr_pattern); _nltst_assert_select_route(out_select_route); } bool _nltst_select_route_match(struct nl_object *route, const NLTstSelectRoute *select_route, bool do_assert) { struct nl_addr *addr; struct rtnl_route *route_; int i; char sbuf1[200]; ck_assert_ptr_nonnull(route); ck_assert_str_eq(nl_object_get_type(route), "route/route"); if (!select_route) return true; route_ = (struct rtnl_route *)route; _nltst_assert_select_route(select_route); #define _check(cond, msg, ...) \ do { \ if (do_assert) { \ _nl_auto_free char *s1 = NULL; \ _nl_auto_free char *s2 = NULL; \ \ ck_assert_msg( \ (cond), \ "Checking condition \"%s\" for route \"%s\" (expected \"%s\") failed (msg: " msg \ ")", \ #cond, (s1 = _nltst_object_to_string(route)), \ (s2 = _nltst_select_route_to_string( \ select_route)), \ ##__VA_ARGS__); \ } else if (cond) { \ } else { \ return false; \ } \ } while (0) if (select_route->addr_family != AF_UNSPEC) { _check(rtnl_route_get_family(route_) == select_route->addr_family, "mismatching address family"); } if (select_route->ifindex != 0) { struct nl_list_head *list; struct rtnl_nexthop *nh; size_t n; struct rtnl_nexthop *nh2; list = rtnl_route_get_nexthops(route_); _check(list, "no nexthops for ifindex"); n = 0; nl_list_for_each_entry(nh, list, rtnh_list) { nh2 = nh; n++; } _check(n == 1, "expects one nexthop for ifindex but got %zu", n); ck_assert_ptr_nonnull(nh2); i = rtnl_route_nh_get_ifindex(nh2); _check(i == select_route->ifindex, "route has unexpected ifindex %d for next hop", i); } addr = rtnl_route_get_dst(route_); if (addr) { if (select_route->addr_family != AF_UNSPEC) { _check(nl_addr_get_family(addr) == select_route->addr_family, "unexecpted address family of dst"); } } if (select_route->plen != -1) { _check(addr, "missing address"); _check(nl_addr_get_prefixlen(addr) == (unsigned)select_route->plen, "unexpected prefix length"); } if (select_route->addr || select_route->addr_pattern) { _check(addr, "missing address"); _nl_inet_ntop(nl_addr_get_family(addr), nl_addr_get_binary_addr(addr), sbuf1); ck_assert(strlen(sbuf1) > 0); ck_assert(strlen(sbuf1) < sizeof(sbuf1)); if (select_route->addr) { _check(_nl_streq(sbuf1, select_route->addr), "unexpected address, \"%s\" does not match \"%s\"", sbuf1, select_route->addr); } if (select_route->addr_pattern) { _check(fnmatch(select_route->addr_pattern, sbuf1, 0) == 0, "unexpected address, \"%s\" does not match pattern \"%s\"", sbuf1, select_route->addr_pattern); } } #undef _check return false; } /*****************************************************************************/ void _nltst_assert_route_list(struct nl_object *const *objs, ssize_t len, const char *const *expected_routes) { size_t l; size_t i; if (len < 0) { l = 0; if (objs) { while (objs[l]) l++; } } else l = len; for (i = 0; i < l; i++) { struct nl_object *route = objs[i]; _nltst_auto_clear_select_route NLTstSelectRoute select_route = { 0 }; _nl_auto_free char *s = _nltst_object_to_string(route); if (!expected_routes[i]) { ck_abort_msg( "No more expected route, but have route %zu (of %zu) as %s", i + 1, l, s); } _nltst_select_route_parse(expected_routes[i], &select_route); _nltst_select_route_match(route, &select_route, true); } } void _nltst_assert_route_cache_v(struct nl_cache *cache, const char *const *expected_routes) { _nl_auto_free struct nl_object **objs = NULL; size_t len; ck_assert(cache); ck_assert(expected_routes); objs = _nltst_cache_get_all(cache, &len); _nltst_assert_route_list(objs, len, expected_routes); }