/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 2015 - 2017 Red Hat, Inc. */ #include "nm-default.h" #include #include #include "platform/nmp-object.h" #include "nm-udev-aux/nm-udev-utils.h" #include "nm-test-utils-core.h" struct { GList *udev_devices; } global; /*****************************************************************************/ static void test_obj_base(void) { static const union { GObject g; NMPObject k; } x = {}; static const union { GTypeClass k; NMPClass c; } l = {}; static const GObject * g = &x.g; static const GTypeClass *k = &l.k; static const NMPObject * o = &x.k; static const NMPClass * c = &l.c; NMObjBaseInst * obj; gs_unref_object GCancellable *obj_cancellable = g_cancellable_new(); nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link(10); g_assert(&g->g_type_instance == (void *) &o->_class); g_assert(&g->g_type_instance.g_class == (void *) &o->_class); g_assert(sizeof(o->parent.parent) == sizeof(GTypeInstance)); g_assert(&c->parent == (void *) c); g_assert(&c->parent.parent.g_type_class == (void *) c); g_assert(&c->parent.parent.g_type == (void *) c); g_assert(&c->parent.parent.g_type == &k->g_type); g_assert(sizeof(c->parent.parent) == sizeof(GTypeClass)); g_assert(&o->parent == (void *) o); g_assert(&o->parent.klass == (void *) &o->_class); obj = (NMObjBaseInst *) obj_cancellable; g_assert(!NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); g_assert(G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); obj = (NMObjBaseInst *) obj_link; g_assert(NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); g_assert(!G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); } /*****************************************************************************/ static gboolean _nmp_object_id_equal(const NMPObject *a, const NMPObject *b) { gboolean a_b = nmp_object_id_equal(a, b); g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_id_equal(b, a)); return a_b; } #define nmp_object_id_equal _nmp_object_id_equal static gboolean _nmp_object_equal(const NMPObject *a, const NMPObject *b) { gboolean a_b = nmp_object_equal(a, b); g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_equal(b, a)); return a_b; } #define nmp_object_equal _nmp_object_equal /*****************************************************************************/ static void _assert_cache_multi_lookup_contains(const NMPCache * cache, const NMDedupMultiHeadEntry *head_entry, const NMPObject * obj, gboolean visible_only, gboolean contains) { NMDedupMultiIter iter; gboolean found; guint i, len; const NMPObject *o; g_assert(NMP_OBJECT_IS_VALID(obj)); g_assert(nmp_cache_lookup_obj(cache, obj) == obj); g_assert(!head_entry || (head_entry->len > 0 && c_list_length(&head_entry->lst_entries_head) == head_entry->len)); len = head_entry ? head_entry->len : 0; found = FALSE; i = 0; nmp_cache_iter_for_each (&iter, head_entry, &o) { g_assert(NMP_OBJECT_IS_VALID(o)); if (obj == o) { if (!visible_only || nmp_object_is_visible(o)) { g_assert(!found); found = TRUE; } } i++; } g_assert(len == i); g_assert(!!contains == found); } static void _assert_cache_multi_lookup_contains_link(const NMPCache * cache, gboolean visible_only, const NMPObject *obj, gboolean contains) { const NMDedupMultiHeadEntry *head_entry; NMPLookup lookup; g_assert(cache); nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); head_entry = nmp_cache_lookup(cache, &lookup); _assert_cache_multi_lookup_contains(cache, head_entry, obj, visible_only, contains); } /*****************************************************************************/ static void ops_post_check(NMPCache * cache, NMPCacheOpsType ops_type, const NMPObject *obj_old, const NMPObject *obj_new, const NMPObject *obj_new_expected, NMPCacheOpsType expected_ops_type) { g_assert(cache); g_assert_cmpint(expected_ops_type, ==, ops_type); switch (ops_type) { case NMP_CACHE_OPS_ADDED: g_assert(!obj_old); g_assert(NMP_OBJECT_IS_VALID(obj_new)); g_assert(nmp_object_is_alive(obj_new)); g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); g_assert(nmp_object_equal(obj_new_expected, obj_new)); break; case NMP_CACHE_OPS_UPDATED: g_assert(obj_old != obj_new); g_assert(NMP_OBJECT_IS_VALID(obj_old)); g_assert(NMP_OBJECT_IS_VALID(obj_new)); g_assert(nmp_object_is_alive(obj_old)); g_assert(nmp_object_is_alive(obj_new)); g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); g_assert(nmp_object_id_equal(obj_old, obj_new)); g_assert(nmp_object_equal(obj_new_expected, obj_new)); g_assert(!nmp_object_equal(obj_new_expected, obj_old)); g_assert(!nmp_object_equal(obj_old, obj_new)); break; case NMP_CACHE_OPS_REMOVED: g_assert(!obj_new); g_assert(NMP_OBJECT_IS_VALID(obj_old)); g_assert(nmp_object_is_alive(obj_old)); if (obj_new_expected) g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); break; case NMP_CACHE_OPS_UNCHANGED: g_assert(obj_old == obj_new); if (obj_old) { g_assert(NMP_OBJECT_IS_VALID(obj_old)); g_assert(nmp_object_is_alive(obj_old)); g_assert(nmp_object_equal(obj_old, obj_new)); g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); } else g_assert(!obj_new_expected); break; default: g_assert_not_reached(); } } static void _nmp_cache_update_netlink(NMPCache * cache, NMPObject * obj, const NMPObject **out_obj_old, const NMPObject **out_obj_new, NMPCacheOpsType expected_ops_type) { NMPCacheOpsType ops_type; const NMPObject *obj_prev; const NMPObject *obj_old; const NMPObject *obj_new; nm_auto_nmpobj NMPObject *obj_new_expected = NULL; g_assert(cache); g_assert(NMP_OBJECT_IS_VALID(obj)); obj_prev = nmp_cache_lookup_link(cache, NMP_OBJECT_CAST_LINK(obj)->ifindex); obj_new_expected = nmp_object_clone(obj, FALSE); if (obj_prev && obj_prev->_link.udev.device) obj_new_expected->_link.udev.device = udev_device_ref(obj_prev->_link.udev.device); _nmp_object_fixup_link_udev_fields(&obj_new_expected, NULL, nmp_cache_use_udev_get(cache)); ops_type = nmp_cache_update_netlink(cache, obj, FALSE, &obj_old, &obj_new); ops_post_check(cache, ops_type, obj_old, obj_new, nmp_object_is_alive(obj_new_expected) ? obj_new_expected : NULL, expected_ops_type); if (out_obj_new) *out_obj_new = obj_new; else nmp_object_unref(obj_new); if (out_obj_old) *out_obj_old = obj_old; else nmp_object_unref(obj_old); } static const NMPlatformLink pl_link_2 = { .ifindex = 2, .name = "eth0", .type = NM_LINK_TYPE_ETHERNET, }; static const NMPlatformLink pl_link_3 = { .ifindex = 3, .name = "wlan0", .type = NM_LINK_TYPE_WIFI, }; static void test_cache_link(void) { NMPCache * cache; NMPObject * objm1; const NMPObject * obj_old, *obj_new; NMPObject objs1; struct udev_device * udev_device_2 = g_list_nth_data(global.udev_devices, 0); struct udev_device * udev_device_3 = g_list_nth_data(global.udev_devices, 0); NMPCacheOpsType ops_type; nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; gboolean use_udev = nmtst_get_rand_uint32() % 2; multi_idx = nm_dedup_multi_index_new(); cache = nmp_cache_new(multi_idx, use_udev); /* if we have a link, and don't set is_in_netlink, adding it has no effect. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); g_assert(NMP_OBJECT_UP_CAST(&objm1->object) == objm1); g_assert(!nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(!obj_old); g_assert(!obj_new); g_assert(!nmp_cache_lookup_obj(cache, objm1)); g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); nmp_object_unref(objm1); /* Only when setting @is_in_netlink the link is added. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); objm1->_link.netlink.is_in_netlink = TRUE; g_assert(nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_ADDED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(!obj_old); g_assert(obj_new); g_assert(objm1 == obj_new); g_assert(nmp_object_equal(objm1, obj_new)); g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == obj_new); g_assert(nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); nmp_object_unref(objm1); nmp_object_unref(obj_new); /* updating the same link with identical value, has no effect. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); objm1->_link.netlink.is_in_netlink = TRUE; g_assert(nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(obj_old); g_assert(obj_new); g_assert(obj_new != objm1); g_assert(nmp_object_equal(objm1, obj_new)); g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == obj_new); nmp_object_unref(objm1); nmp_object_unref(obj_new); nmp_object_unref(obj_new); /* remove the link from netlink */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); g_assert(!nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_REMOVED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(obj_old); g_assert(!obj_new); g_assert(!nmp_cache_lookup_obj(cache, objm1)); g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); nmp_object_unref(objm1); nmp_object_unref(obj_old); nmp_object_unref(obj_new); if (udev_device_2) { /* now add the link only with aspect UDEV. */ ops_type = nmp_cache_update_link_udev(cache, pl_link_2.ifindex, udev_device_2, &obj_old, &obj_new); nmtst_assert_nmp_cache_is_consistent(cache); g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); g_assert(!obj_old); g_assert(obj_new); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == obj_new); g_assert(!nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); nmp_object_unref(obj_new); } /* add it in netlink too. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); objm1->_link.netlink.is_in_netlink = TRUE; g_assert(nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED); nmtst_assert_nmp_cache_is_consistent(cache); if (udev_device_2) { g_assert(obj_old); g_assert(!nmp_object_is_visible(obj_old)); } else g_assert(!obj_old); g_assert(nmp_object_equal(objm1, obj_new)); g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == obj_new); g_assert(nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); nmp_object_unref(objm1); nmp_object_unref(obj_old); nmp_object_unref(obj_new); /* remove again from netlink. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); objm1->_link.netlink.is_in_netlink = FALSE; g_assert(!nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED); nmtst_assert_nmp_cache_is_consistent(cache); if (udev_device_2) g_assert(obj_new == objm1); else g_assert(!obj_new); g_assert(obj_old); g_assert(nmp_object_is_alive(obj_old)); if (udev_device_2) { g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == obj_new); g_assert(!nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); } else { g_assert(nmp_cache_lookup_obj(cache, objm1) == NULL); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) == NULL); g_assert(!nmp_object_is_alive(obj_new)); g_assert(!nmp_object_is_visible(obj_new)); } nmp_object_unref(objm1); nmp_object_unref(obj_old); nmp_object_unref(obj_new); /* now another link only with aspect UDEV. */ if (udev_device_3) { /* now add the link only with aspect UDEV. */ ops_type = nmp_cache_update_link_udev(cache, pl_link_3.ifindex, udev_device_3, &obj_old, &obj_new); g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(NMP_OBJECT_IS_VALID(obj_new)); g_assert(!obj_old); g_assert(!nmp_object_is_visible(obj_new)); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) == obj_new); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, FALSE); g_assert_cmpint(obj_new->link.initialized, ==, FALSE); nmp_object_unref(obj_new); /* add it in netlink too. */ objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3); objm1->_link.netlink.is_in_netlink = TRUE; g_assert(nmp_object_is_alive(objm1)); _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UPDATED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(obj_old); g_assert(obj_new == objm1); g_assert(nmp_object_equal(objm1, obj_new)); g_assert(!obj_old || !nmp_object_is_visible(obj_old)); g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) == obj_new); g_assert(nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); g_assert_cmpint(obj_new->link.initialized, ==, TRUE); nmp_object_unref(objm1); nmp_object_unref(obj_old); nmp_object_unref(obj_new); /* remove UDEV. */ ops_type = nmp_cache_update_link_udev(cache, pl_link_3.ifindex, NULL, &obj_old, &obj_new); g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_UPDATED); nmtst_assert_nmp_cache_is_consistent(cache); g_assert(obj_old && nmp_object_is_visible(obj_old)); g_assert( nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) == obj_new); g_assert(nmp_object_is_visible(obj_new)); _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); g_assert_cmpint(obj_new->link.initialized, ==, !nmp_cache_use_udev_get(cache)); nmp_object_unref(obj_new); nmp_object_unref(obj_old); } nmp_cache_free(cache); } const char noqueue[] = "noqueue"; const char fq_codel[] = "fq_codel"; const char ingress[] = "ingress"; static const NMPlatformQdisc pl_qdisc_1a = { .ifindex = 1, .kind = noqueue, .addr_family = AF_UNSPEC, .handle = 0, .parent = TC_H_ROOT, .info = 0, }; static const NMPlatformQdisc pl_qdisc_1b = { .ifindex = 1, .kind = fq_codel, .addr_family = AF_UNSPEC, .handle = 0, .parent = TC_H_ROOT, .info = 0, }; static const NMPlatformQdisc pl_qdisc_1c = { .ifindex = 1, .kind = ingress, .addr_family = AF_UNSPEC, .handle = TC_H_MAKE(TC_H_INGRESS, 0), .parent = TC_H_INGRESS, .info = 0, }; static const NMPlatformQdisc pl_qdisc_2 = { .ifindex = 2, .kind = fq_codel, .addr_family = AF_UNSPEC, .handle = 0, .parent = TC_H_ROOT, .info = 0, }; static void test_cache_qdisc(void) { NMPCache * cache; nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; NMPLookup lookup; const NMDedupMultiHeadEntry * head_entry; nm_auto_nmpobj NMPObject *obj1a = nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1a); nm_auto_nmpobj NMPObject *obj1b = nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1b); nm_auto_nmpobj NMPObject *obj1c = nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1c); nm_auto_nmpobj NMPObject *obj2 = nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2); multi_idx = nm_dedup_multi_index_new(); cache = nmp_cache_new(multi_idx, nmtst_get_rand_uint32() % 2); g_assert(nmp_cache_lookup_obj(cache, obj1a) == NULL); g_assert(nmp_cache_update_netlink(cache, obj1a, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1a); g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1a); g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); g_assert(nmp_cache_update_netlink(cache, obj1b, FALSE, NULL, NULL) == NMP_CACHE_OPS_UPDATED); g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); g_assert(nmp_cache_update_netlink(cache, obj1c, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj1c) == obj1c); g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); g_assert(nmp_cache_update_netlink(cache, obj2, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); g_assert(nmp_cache_lookup_obj(cache, obj2) == obj2); head_entry = nmp_cache_lookup(cache, nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, 1)); g_assert(head_entry->len == 2); nmp_cache_free(cache); } /*****************************************************************************/ NMTST_DEFINE(); int main(int argc, char **argv) { int result; NMUdevClient *udev_client; nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); udev_client = nm_udev_client_new((const char *[]){"net", NULL}, NULL, NULL); { struct udev_enumerate * enumerator; struct udev_list_entry *devices, *l; enumerator = nm_udev_client_enumerate_new(udev_client); /* Demand that the device is initialized (udev rules ran, * device has a stable name now) in case udev is running * (not in a container). */ if (access("/sys", W_OK) == 0) udev_enumerate_add_match_is_initialized(enumerator); udev_enumerate_scan_devices(enumerator); devices = udev_enumerate_get_list_entry(enumerator); for (l = devices; l != NULL; l = udev_list_entry_get_next(l)) { struct udev_device *udevice; udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), udev_list_entry_get_name(l)); if (udevice == NULL) continue; global.udev_devices = g_list_prepend(global.udev_devices, udevice); } global.udev_devices = g_list_reverse(global.udev_devices); udev_enumerate_unref(enumerator); } g_test_add_func("/nmp-object/obj-base", test_obj_base); g_test_add_func("/nmp-object/cache_link", test_cache_link); g_test_add_func("/nmp-object/cache_qdisc", test_cache_qdisc); result = g_test_run(); while (global.udev_devices) { udev_device_unref(global.udev_devices->data); global.udev_devices = g_list_delete_link(global.udev_devices, global.udev_devices); } nm_udev_client_unref(udev_client); return result; }