diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/meson.build | 1 | ||||
-rw-r--r-- | src/nm-netns.c | 16 | ||||
-rw-r--r-- | src/nm-netns.h | 2 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 3 | ||||
-rw-r--r-- | src/platform/nmp-rules-manager.c | 660 | ||||
-rw-r--r-- | src/platform/nmp-rules-manager.h | 61 | ||||
-rw-r--r-- | src/platform/tests/test-route.c | 225 |
8 files changed, 895 insertions, 75 deletions
diff --git a/Makefile.am b/Makefile.am index b180466adc..7064eb15c0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1795,6 +1795,8 @@ src_libNetworkManagerBase_la_SOURCES = \ src/platform/nm-platform-private.h \ src/platform/nm-linux-platform.c \ src/platform/nm-linux-platform.h \ + src/platform/nmp-rules-manager.c \ + src/platform/nmp-rules-manager.h \ src/platform/wifi/nm-wifi-utils-nl80211.c \ src/platform/wifi/nm-wifi-utils-nl80211.h \ src/platform/wifi/nm-wifi-utils-private.h \ diff --git a/src/meson.build b/src/meson.build index 06a0dc57da..20a14f9f00 100644 --- a/src/meson.build +++ b/src/meson.build @@ -35,6 +35,7 @@ sources = files( 'platform/nm-platform-utils.c', 'platform/nmp-netns.c', 'platform/nmp-object.c', + 'platform/nmp-rules-manager.c', 'main-utils.c', 'NetworkManagerUtils.c', 'nm-core-utils.c', diff --git a/src/nm-netns.c b/src/nm-netns.c index 5952a490f4..ce32ac6b6f 100644 --- a/src/nm-netns.c +++ b/src/nm-netns.c @@ -24,10 +24,11 @@ #include "nm-utils/nm-dedup-multi.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" #include "platform/nm-platform.h" #include "platform/nmp-netns.h" -#include "nm-core-internal.h" -#include "NetworkManagerUtils.h" +#include "platform/nmp-rules-manager.h" /*****************************************************************************/ @@ -38,6 +39,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE ( typedef struct { NMPlatform *platform; NMPNetns *platform_netns; + NMPRulesManager *rules_manager; } NMNetnsPrivate; struct _NMNetns { @@ -71,6 +73,12 @@ nm_netns_get_platform (NMNetns *self) return NM_NETNS_GET_PRIVATE (self)->platform; } +NMPRulesManager * +nm_netns_get_rules_manager (NMNetns *self) +{ + return NM_NETNS_GET_PRIVATE (self)->rules_manager; +} + NMDedupMultiIndex * nm_netns_get_multi_idx (NMNetns *self) { @@ -118,6 +126,8 @@ constructed (GObject *object) priv->platform_netns = nm_platform_netns_get (priv->platform); + priv->rules_manager = nmp_rules_manager_new (priv->platform, TRUE); + G_OBJECT_CLASS (nm_netns_parent_class)->constructed (object); } @@ -137,6 +147,8 @@ dispose (GObject *object) g_clear_object (&priv->platform); + nm_clear_pointer (&priv->rules_manager, nmp_rules_manager_unref); + G_OBJECT_CLASS (nm_netns_parent_class)->dispose (object); } diff --git a/src/nm-netns.h b/src/nm-netns.h index ae343ccebf..250d114921 100644 --- a/src/nm-netns.h +++ b/src/nm-netns.h @@ -40,6 +40,8 @@ NMNetns *nm_netns_new (NMPlatform *platform); NMPlatform *nm_netns_get_platform (NMNetns *self); NMPNetns *nm_netns_get_platform_netns (NMNetns *self); +struct _NMPRulesManager *nm_netns_get_rules_manager (NMNetns *self); + struct _NMDedupMultiIndex *nm_netns_get_multi_idx (NMNetns *self); #define NM_NETNS_GET (nm_netns_get ()) diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 2f27c861f3..8500c2faa4 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -7787,8 +7787,9 @@ constructor (GType type, priv->multi_idx = nm_dedup_multi_index_new (); - priv->cache = nmp_cache_new (nm_platform_get_multi_idx (self), + priv->cache = nmp_cache_new (priv->multi_idx, priv->use_udev); + return object; } diff --git a/src/platform/nmp-rules-manager.c b/src/platform/nmp-rules-manager.c new file mode 100644 index 0000000000..2c486fdaa6 --- /dev/null +++ b/src/platform/nmp-rules-manager.c @@ -0,0 +1,660 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#include "nm-default.h" + +#include "nmp-rules-manager.h" + +#include <linux/fib_rules.h> +#include <linux/rtnetlink.h> + +#include "nm-utils/c-list-util.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +struct _NMPRulesManager { + NMPlatform *platform; + GHashTable *by_obj; + GHashTable *by_user_tag; + GHashTable *by_data; + guint ref_count; + bool track_default:1; +}; + +/*****************************************************************************/ + +static void _rules_init (NMPRulesManager *self); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "rules-manager" + +#define _NMLOG(level, ...) \ + G_STMT_START { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \ + _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ + _NMLOG_PREFIX_NAME \ + _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +static gboolean +NMP_IS_RULES_MANAGER (gpointer self) +{ + return self + && ((NMPRulesManager *) self)->ref_count > 0 + && NM_IS_PLATFORM (((NMPRulesManager *) self)->platform); +} + +#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr (1240261787u, (user_tag)) + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gconstpointer user_tag; + CList obj_lst; + CList user_tag_lst; + + guint32 priority_val; + bool priority_present; + + bool dirty:1; +} RulesData; + +typedef struct { + const NMPObject *obj; + CList obj_lst_head; + + /* indicates that we configured the rule (during sync()). We need that, so + * if the rule gets untracked, that we know to remove it on the next + * sync(). + * + * This makes NMPRulesManager stateful (beyond the configuration that indicates + * which rules are tracked). + * After a restart, NetworkManager would no longer remember which rules were added + * by us. That would need to be fixed by persisting the state and reloading it after + * restart. */ + bool added_by_us:1; +} RulesObjData; + +typedef struct { + gconstpointer user_tag; + CList user_tag_lst_head; +} RulesUserTagData; + +static void +_rules_data_assert (const RulesData *rules_data, gboolean linked) +{ + nm_assert (rules_data); + nm_assert (NMP_OBJECT_GET_TYPE (rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + nm_assert (nmp_object_is_visible (rules_data->obj)); + nm_assert (rules_data->user_tag); + nm_assert (!linked || !c_list_is_empty (&rules_data->obj_lst)); + nm_assert (!linked || !c_list_is_empty (&rules_data->user_tag_lst)); +} + +static guint +_rules_data_hash (gconstpointer data) +{ + const RulesData *rules_data = data; + NMHashState h; + + _rules_data_assert (rules_data, FALSE); + + nm_hash_init (&h, 269297543u); + nm_platform_routing_rule_hash_update (NMP_OBJECT_CAST_ROUTING_RULE (rules_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + nm_hash_update_val (&h, rules_data->user_tag); + return nm_hash_complete (&h); +} + +static gboolean +_rules_data_equal (gconstpointer data_a, gconstpointer data_b) +{ + const RulesData *rules_data_a = data_a; + const RulesData *rules_data_b = data_b; + + _rules_data_assert (rules_data_a, FALSE); + _rules_data_assert (rules_data_b, FALSE); + + return rules_data_a->user_tag == rules_data_b->user_tag + && (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (rules_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE (rules_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0); +} + +static void +_rules_data_destroy (gpointer data) +{ + RulesData *rules_data = data; + + _rules_data_assert (rules_data, FALSE); + + c_list_unlink_stale (&rules_data->obj_lst); + c_list_unlink_stale (&rules_data->user_tag_lst); + nmp_object_unref (rules_data->obj); + g_slice_free (RulesData, rules_data); +} + +static const RulesData * +_rules_obj_get_best_data (RulesObjData *obj_data) +{ + RulesData *rules_data; + const RulesData *rd_best = NULL; + + nm_assert (!c_list_is_empty (&obj_data->obj_lst_head)); + + c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) { + + _rules_data_assert (rules_data, TRUE); + + if (rd_best) { + if (rd_best->priority_val > rules_data->priority_val) + continue; + if (rd_best->priority_val == rules_data->priority_val) { + if ( rd_best->priority_present + || !rules_data->priority_present) + continue; + } + } + + rd_best = rules_data; + } + + return rd_best; +} + +static guint +_rules_obj_hash (gconstpointer data) +{ + const RulesObjData *obj_data = data; + NMHashState h; + + nm_hash_init (&h, 432817559u); + nm_platform_routing_rule_hash_update (NMP_OBJECT_CAST_ROUTING_RULE (obj_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + return nm_hash_complete (&h); +} + +static gboolean +_rules_obj_equal (gconstpointer data_a, gconstpointer data_b) +{ + const RulesObjData *obj_data_a = data_a; + const RulesObjData *obj_data_b = data_b; + + return (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (obj_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE (obj_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0); +} + +static void +_rules_obj_destroy (gpointer data) +{ + RulesObjData *obj_data = data; + + c_list_unlink_stale (&obj_data->obj_lst_head); + nmp_object_unref (obj_data->obj); + g_slice_free (RulesObjData, obj_data); +} + +static guint +_rules_user_tag_hash (gconstpointer data) +{ + const RulesUserTagData *user_tag_data = data; + + return nm_hash_val (644693447u, user_tag_data->user_tag); +} + +static gboolean +_rules_user_tag_equal (gconstpointer data_a, gconstpointer data_b) +{ + const RulesUserTagData *user_tag_data_a = data_a; + const RulesUserTagData *user_tag_data_b = data_b; + + return user_tag_data_a->user_tag == user_tag_data_b->user_tag; +} + +static void +_rules_user_tag_destroy (gpointer data) +{ + RulesUserTagData *user_tag_data = data; + + c_list_unlink_stale (&user_tag_data->user_tag_lst_head); + g_slice_free (RulesUserTagData, user_tag_data); +} + +static RulesData * +_rules_data_lookup (GHashTable *by_data, + const NMPObject *obj, + gconstpointer user_tag) +{ + RulesData rules_data_needle = { + .obj = obj, + .user_tag = user_tag, + }; + + return g_hash_table_lookup (by_data, &rules_data_needle); +} + +void +nmp_rules_manager_track (NMPRulesManager *self, + const NMPlatformRoutingRule *routing_rule, + gint32 priority, + gconstpointer user_tag) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + RulesData *rules_data; + RulesObjData *obj_data; + RulesUserTagData *user_tag_data; + gboolean changed = FALSE; + guint32 priority_val; + gboolean priority_present; + + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + g_return_if_fail (routing_rule); + g_return_if_fail (user_tag); + nm_assert (priority != G_MININT32); + + _rules_init (self); + + p_obj_stack = nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert (nmp_object_is_visible (p_obj_stack)); + + if (priority >= 0) { + priority_val = priority; + priority_present = TRUE; + } else { + priority_val = -priority; + priority_present = FALSE; + } + + rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag); + + if (!rules_data) { + rules_data = g_slice_new (RulesData); + *rules_data = (RulesData) { + .obj = nm_dedup_multi_index_obj_intern (nm_platform_get_multi_idx (self->platform), + p_obj_stack), + .user_tag = user_tag, + .priority_val = priority_val, + .priority_present = priority_present, + .dirty = FALSE, + }; + g_hash_table_add (self->by_data, rules_data); + + obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj); + if (!obj_data) { + obj_data = g_slice_new (RulesObjData); + *obj_data = (RulesObjData) { + .obj = nmp_object_ref (rules_data->obj), + .obj_lst_head = C_LIST_INIT (obj_data->obj_lst_head), + .added_by_us = FALSE, + }; + g_hash_table_add (self->by_obj, obj_data); + } + c_list_link_tail (&obj_data->obj_lst_head, &rules_data->obj_lst); + + user_tag_data = g_hash_table_lookup (self->by_user_tag, &rules_data->user_tag); + if (!user_tag_data) { + user_tag_data = g_slice_new (RulesUserTagData); + *user_tag_data = (RulesUserTagData) { + .user_tag = user_tag, + .user_tag_lst_head = C_LIST_INIT (user_tag_data->user_tag_lst_head), + }; + g_hash_table_add (self->by_user_tag, user_tag_data); + } + c_list_link_tail (&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst); + changed = TRUE; + } else { + rules_data->dirty = FALSE; + if ( rules_data->priority_val != priority_val + || rules_data->priority_present != priority_present) { + rules_data->priority_val = priority_val; + rules_data->priority_present = priority_present; + changed = TRUE; + } + } + + _rules_data_assert (rules_data, TRUE); + + if (changed) { + _LOGD ("routing-rule: track ["NM_HASH_OBFUSCATE_PTR_FMT",%c%u] \"%s\")", + _USER_TAG_LOG (rules_data->user_tag), + rules_data->priority_present ? '+' : '-', + (guint) rules_data->priority_val, + nmp_object_to_string (rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + } +} + +static void +_rules_data_untrack (NMPRulesManager *self, + RulesData *rules_data, + gboolean remove_user_tag_data) +{ + RulesObjData *obj_data; + + nm_assert (NMP_IS_RULES_MANAGER (self)); + _rules_data_assert (rules_data, TRUE); + nm_assert (self->by_data); + nm_assert (g_hash_table_lookup (self->by_data, rules_data) == rules_data); + + _LOGD ("routing-rule: untrack ["NM_HASH_OBFUSCATE_PTR_FMT"] \"%s\"", + _USER_TAG_LOG (rules_data->user_tag), + nmp_object_to_string (rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + +#if NM_MORE_ASSERTS + { + RulesUserTagData *user_tag_data; + + user_tag_data = g_hash_table_lookup (self->by_user_tag, &rules_data->user_tag); + nm_assert (user_tag_data); + nm_assert (c_list_contains (&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst)); + } +#endif + + nm_assert (!c_list_is_empty (&rules_data->user_tag_lst)); + if ( remove_user_tag_data + && c_list_length_is (&rules_data->user_tag_lst, 1)) + g_hash_table_remove (self->by_user_tag, &rules_data->user_tag); + + obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj); + nm_assert (obj_data); + nm_assert (c_list_contains (&obj_data->obj_lst_head, &rules_data->obj_lst)); + nm_assert (obj_data == g_hash_table_lookup (self->by_obj, &rules_data->obj)); + + /* if obj_data is marked to be "added_by_us", we need to keep this entry around + * for the next sync -- so that we can remove the rule that was added. */ + if ( !obj_data->added_by_us + && c_list_length_is (&rules_data->obj_lst, 1)) + g_hash_table_remove (self->by_obj, &rules_data->obj); + + g_hash_table_remove (self->by_data, rules_data); +} + +void +nmp_rules_manager_untrack (NMPRulesManager *self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + RulesData *rules_data; + + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + g_return_if_fail (routing_rule); + g_return_if_fail (user_tag); + + _rules_init (self); + + p_obj_stack = nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert (nmp_object_is_visible (p_obj_stack)); + + rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag); + if (rules_data) + _rules_data_untrack (self, rules_data, TRUE); +} + +void +nmp_rules_manager_set_dirty (NMPRulesManager *self, + gconstpointer user_tag) +{ + RulesData *rules_data; + RulesUserTagData *user_tag_data; + + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + g_return_if_fail (user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup (self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst) + rules_data->dirty = TRUE; +} + +void +nmp_rules_manager_untrack_all (NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */) +{ + RulesData *rules_data; + RulesData *rules_data_safe; + RulesUserTagData *user_tag_data; + + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + g_return_if_fail (user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup (self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry_safe (rules_data, rules_data_safe, &user_tag_data->user_tag_lst_head, user_tag_lst) { + if ( all + || rules_data->dirty) + _rules_data_untrack (self, rules_data, FALSE); + } + if (c_list_is_empty (&user_tag_data->user_tag_lst_head)) + g_hash_table_remove (self->by_user_tag, user_tag_data); +} + +void +nmp_rules_manager_sync (NMPRulesManager *self) +{ + const NMDedupMultiHeadEntry *pl_head_entry; + NMDedupMultiIter pl_iter; + const NMPObject *plobj; + gs_unref_ptrarray GPtrArray *rules_to_delete = NULL; + RulesObjData *obj_data; + GHashTableIter h_iter; + guint i; + + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + + if (!self->by_data) + return; + + _LOGD ("sync"); + + pl_head_entry = nm_platform_lookup_obj_type (self->platform, NMP_OBJECT_TYPE_ROUTING_RULE); + if (pl_head_entry) { + nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) { + obj_data = g_hash_table_lookup (self->by_obj, &plobj); + + if (!obj_data) { + /* this rule is not tracked. It was externally added, hence we + * ignore it. */ + continue; + } + + if (c_list_is_empty (&obj_data->obj_lst_head)) { + nm_assert (obj_data->added_by_us); + g_hash_table_remove (self->by_obj, obj_data); + } else { + if (_rules_obj_get_best_data (obj_data)->priority_present) + continue; + obj_data->added_by_us = FALSE; + } + + if (!rules_to_delete) + rules_to_delete = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add (rules_to_delete, (gpointer) nmp_object_ref (plobj)); + } + } + + if (rules_to_delete) { + for (i = 0; i < rules_to_delete->len; i++) + nm_platform_object_delete (self->platform, rules_to_delete->pdata[i]); + } + + g_hash_table_iter_init (&h_iter, self->by_obj); + while (g_hash_table_iter_next (&h_iter, (gpointer *) &obj_data, NULL)) { + + if (c_list_is_empty (&obj_data->obj_lst_head)) { + nm_assert (obj_data->added_by_us); + g_hash_table_iter_remove (&h_iter); + continue; + } + + if (!_rules_obj_get_best_data (obj_data)->priority_present) + continue; + + plobj = nm_platform_lookup_obj (self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj); + if (plobj) + continue; + + obj_data->added_by_us = TRUE; + nm_platform_routing_rule_add (self->platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj_data->obj)); + } +} + +/*****************************************************************************/ + +void +nmp_rules_manager_track_default (NMPRulesManager *self, + int addr_family, + int priority, + gconstpointer user_tag) +{ + /* track the default rules. See also `man ip-rule`. */ + + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) { + nmp_rules_manager_track (self, + &((NMPlatformRoutingRule) { + .addr_family = AF_INET, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + }), + priority, + user_tag); + nmp_rules_manager_track (self, + &((NMPlatformRoutingRule) { + .addr_family = AF_INET, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + }), + priority, + user_tag); + nmp_rules_manager_track (self, + &((NMPlatformRoutingRule) { + .addr_family = AF_INET, + .priority = 32767, + .table = RT_TABLE_DEFAULT, + .action = FR_ACT_TO_TBL, + }), + priority, + user_tag); + } + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) { + nmp_rules_manager_track (self, + &((NMPlatformRoutingRule) { + .addr_family = AF_INET6, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + }), + priority, + user_tag); + nmp_rules_manager_track (self, + &((NMPlatformRoutingRule) { + .addr_family = AF_INET6, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + }), + priority, + user_tag); + } +} + +static void +_rules_init (NMPRulesManager *self) +{ + if (self->by_data) + return; + + self->by_data = g_hash_table_new_full (_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy); + self->by_obj = g_hash_table_new_full (_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy); + self->by_user_tag = g_hash_table_new_full (_rules_user_tag_hash, _rules_user_tag_equal, NULL, _rules_user_tag_destroy); + + if (self->track_default) + nmp_rules_manager_track_default (self, AF_UNSPEC, 0, &self->by_data); +} + +/*****************************************************************************/ + +NMPRulesManager * +nmp_rules_manager_new (NMPlatform *platform, + gboolean track_default) +{ + NMPRulesManager *self; + + g_return_val_if_fail (NM_IS_PLATFORM (platform), NULL); + + self = g_slice_new (NMPRulesManager); + *self = (NMPRulesManager) { + .ref_count = 1, + .platform = g_object_ref (platform), + .track_default = track_default, + }; + return self; +} + +void +nmp_rules_manager_ref (NMPRulesManager *self) +{ + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + + self->ref_count++; +} + +void nmp_rules_manager_unref (NMPRulesManager *self) +{ + g_return_if_fail (NMP_IS_RULES_MANAGER (self)); + + if (--self->ref_count > 0) + return; + + if (self->by_data) { + g_hash_table_destroy (self->by_user_tag); + g_hash_table_destroy (self->by_obj); + g_hash_table_destroy (self->by_data); + } + g_object_unref (self->platform); + g_slice_free (NMPRulesManager, self); +} diff --git a/src/platform/nmp-rules-manager.h b/src/platform/nmp-rules-manager.h new file mode 100644 index 0000000000..491df31d4a --- /dev/null +++ b/src/platform/nmp-rules-manager.h @@ -0,0 +1,61 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#ifndef __NMP_RULES_MANAGER_H__ +#define __NMP_RULES_MANAGER_H__ + +#include "nm-platform.h" + +/*****************************************************************************/ + +typedef struct _NMPRulesManager NMPRulesManager; + +NMPRulesManager *nmp_rules_manager_new (NMPlatform *platform, + gboolean track_default); + +void nmp_rules_manager_ref (NMPRulesManager *self); +void nmp_rules_manager_unref (NMPRulesManager *self); + +#define nm_auto_unref_rules_manager nm_auto (_nmp_rules_manager_unref) +NM_AUTO_DEFINE_FCN0 (NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref) + +void nmp_rules_manager_track (NMPRulesManager *self, + const NMPlatformRoutingRule *routing_rule, + gint32 priority, + gconstpointer user_tag); + +void nmp_rules_manager_track_default (NMPRulesManager *self, + int addr_family, + int priority, + gconstpointer user_tag); + +void nmp_rules_manager_untrack (NMPRulesManager *self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag); + +void nmp_rules_manager_set_dirty (NMPRulesManager *self, + gconstpointer user_tag); + +void nmp_rules_manager_untrack_all (NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */); + +void nmp_rules_manager_sync (NMPRulesManager *self); + +/*****************************************************************************/ + +#endif /* __NMP_RULES_MANAGER_H__ */ diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c index 1be6e87a97..c164ef120e 100644 --- a/src/platform/tests/test-route.c +++ b/src/platform/tests/test-route.c @@ -24,6 +24,7 @@ #include "nm-core-utils.h" #include "platform/nm-platform-utils.h" +#include "platform/nmp-rules-manager.h" #include "test-common.h" @@ -1362,6 +1363,7 @@ static void test_rule (gconstpointer test_data) { const int TEST_IDX = GPOINTER_TO_INT (test_data); + const gboolean TEST_SYNC = (TEST_IDX == 4); gs_unref_ptrarray GPtrArray *objs = NULL; gs_unref_ptrarray GPtrArray *objs_initial = NULL; NMPlatform *platform = NM_PLATFORM_GET; @@ -1493,104 +1495,182 @@ again: if (TEST_IDX != 1) nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len); - for (i = 0; i < objs->len;) { - const NMPObject *obj = objs->pdata[i]; + if (TEST_SYNC) { + gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new (NULL, NULL); + nm_auto_unref_rules_manager NMPRulesManager *rules_manager = nmp_rules_manager_new (platform, FALSE); + gs_unref_ptrarray GPtrArray *objs_sync = NULL; + gconstpointer USER_TAG_1 = &platform; + gconstpointer USER_TAG_2 = &unique_priorities; + + objs_sync = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref); + + /* ensure that priorities are unique. Otherwise it confuses the test, because + * kernel may wrongly be unable to add/delete routes based on a wrong match + * (rh#1685816, rh#1685816). */ + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + guint32 prio = NMP_OBJECT_CAST_ROUTING_RULE (obj)->priority; + + if ( !NM_IN_SET (prio, 0, 32766, 32767) + && !g_hash_table_contains (unique_priorities, GUINT_TO_POINTER (prio))) { + g_hash_table_add (unique_priorities, GUINT_TO_POINTER (prio)); + g_ptr_array_add (objs_sync, (gpointer) nmp_object_ref (obj)); + } + } - for (j = 0; j < objs->len; j++) - g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs->pdata[j]))); + for (i = 0; i < objs_sync->len; i++) { + nmp_rules_manager_track (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + 1, + USER_TAG_1); + if (nmtst_get_rand_bool ()) { + /* this has no effect, because a negative priority (of same absolute value) + * has lower priority than the positive priority above. */ + nmp_rules_manager_track (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + -1, + USER_TAG_2); + } + if (nmtst_get_rand_int () % objs_sync->len == 0) { + nmp_rules_manager_sync (rules_manager); + g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1); + } + } - r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj)); + nmp_rules_manager_sync (rules_manager); + g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len); + + for (i = 0; i < objs_sync->len; i++) { + switch (nmtst_get_rand_int () % 3) { + case 0: + nmp_rules_manager_untrack (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + USER_TAG_1); + nmp_rules_manager_untrack (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + USER_TAG_1); + break; + case 1: + nmp_rules_manager_track (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + -1, + USER_TAG_1); + break; + case 2: + nmp_rules_manager_track (rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]), + -2, + USER_TAG_2); + break; + } + if (nmtst_get_rand_int () % objs_sync->len == 0) { + nmp_rules_manager_sync (rules_manager); + g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len - i - 1); + } + } + + nmp_rules_manager_sync (rules_manager); + + } else { + for (i = 0; i < objs->len;) { + const NMPObject *obj = objs->pdata[i]; + + for (j = 0; j < objs->len; j++) + g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs->pdata[j]))); + + r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj)); - if (r == -EEXIST) { - g_assert (!_platform_has_routing_rule (platform, obj)); - /* this should not happen, but there are bugs in kernel (rh#1686075). */ - for (j = 0; j < i; j++) { - const NMPObject *obj2 = objs->pdata[j]; + if (r == -EEXIST) { + g_assert (!_platform_has_routing_rule (platform, obj)); + /* this should not happen, but there are bugs in kernel (rh#1686075). */ + for (j = 0; j < i; j++) { + const NMPObject *obj2 = objs->pdata[j]; - g_assert (_platform_has_routing_rule (platform, obj2)); + g_assert (_platform_has_routing_rule (platform, obj2)); - if (_rule_fuzzy_equal (obj, obj2, RTM_NEWRULE)) { - r = 0; - break; + if (_rule_fuzzy_equal (obj, obj2, RTM_NEWRULE)) { + r = 0; + break; + } + } + if (r == 0) { + /* OK, the rule is shadowed by another rule, and kernel does not allow + * us to add this one (rh#1686075). Drop this from the test. */ + g_ptr_array_remove_index (objs, i); + continue; } } - if (r == 0) { - /* OK, the rule is shadowed by another rule, and kernel does not allow - * us to add this one (rh#1686075). Drop this from the test. */ - g_ptr_array_remove_index (objs, i); - continue; + + if (r != 0) { + g_print (">>> failing...\n"); + nmtstp_run_command_check ("ip rule"); + nmtstp_run_command_check ("ip -6 rule"); + g_assert_cmpint (r, ==, 0); } - } - if (r != 0) { - g_print (">>> failing...\n"); - nmtstp_run_command_check ("ip rule"); - nmtstp_run_command_check ("ip -6 rule"); - g_assert_cmpint (r, ==, 0); - } + g_assert (_platform_has_routing_rule (platform, obj)); - g_assert (_platform_has_routing_rule (platform, obj)); + g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1); - g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1); + i++; + } - i++; - } + if (TEST_IDX != 1) + nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len); - if (TEST_IDX != 1) - nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len); + if (_LOGD_ENABLED ()) { + nmtstp_run_command_check ("ip rule"); + nmtstp_run_command_check ("ip -6 rule"); + } - if (_LOGD_ENABLED ()) { - nmtstp_run_command_check ("ip rule"); - nmtstp_run_command_check ("ip -6 rule"); - } + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + const NMPObject *obj2; - for (i = 0; i < objs->len; i++) { - const NMPObject *obj = objs->pdata[i]; - const NMPObject *obj2; + for (j = 0; j < objs->len; j++) + g_assert ((j < i) == (!_platform_has_routing_rule (platform, objs->pdata[j]))); - for (j = 0; j < objs->len; j++) - g_assert ((j < i) == (!_platform_has_routing_rule (platform, objs->pdata[j]))); + g_assert (_platform_has_routing_rule (platform, obj)); - g_assert (_platform_has_routing_rule (platform, obj)); + r = nm_platform_object_delete (platform, obj); + g_assert_cmpint (r, ==, TRUE); - r = nm_platform_object_delete (platform, obj); - g_assert_cmpint (r, ==, TRUE); + obj2 = _platform_has_routing_rule (platform, obj); - obj2 = _platform_has_routing_rule (platform, obj); + if (obj2) { + guint k; - if (obj2) { - guint k; + /* When deleting a rule, kernel does a fuzzy match, ignoring for example: + * - action, if it is FR_ACT_UNSPEC + * - iifname,oifname if it is unspecified + * rh#1685816 + * + * That means, we may have deleted the wrong rule. Which one? */ + k = i; + for (j = i + 1; j < objs->len; j++) { + if (!_platform_has_routing_rule (platform, objs->pdata[j])) { + g_assert_cmpint (k, ==, i); + k = j; + } + } + g_assert_cmpint (k, >, i); - /* When deleting a rule, kernel does a fuzzy match, ignoring for example: - * - action, if it is FR_ACT_UNSPEC - * - iifname,oifname if it is unspecified - * rh#1685816 - * - * That means, we may have deleted the wrong rule. Which one? */ - k = i; - for (j = i + 1; j < objs->len; j++) { - if (!_platform_has_routing_rule (platform, objs->pdata[j])) { - g_assert_cmpint (k, ==, i); - k = j; + if (!_rule_fuzzy_equal (obj, objs->pdata[k], RTM_DELRULE)) { + g_print (">>> failing...\n"); + g_print (">>> no fuzzy match between: %s\n", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_print (">>> and: %s\n", nmp_object_to_string (objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_assert_not_reached (); } - } - g_assert_cmpint (k, >, i); - if (!_rule_fuzzy_equal (obj, objs->pdata[k], RTM_DELRULE)) { - g_print (">>> failing...\n"); - g_print (">>> no fuzzy match between: %s\n", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); - g_print (">>> and: %s\n", nmp_object_to_string (objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0)); - g_assert_not_reached (); + objs->pdata[i] = objs->pdata[k]; + objs->pdata[k] = (gpointer) obj; + obj2 = NULL; } - objs->pdata[i] = objs->pdata[k]; - objs->pdata[k] = (gpointer) obj; - obj2 = NULL; - } + g_assert (!obj2); - g_assert (!obj2); - - g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs->len -i - 1); + g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs->len -i - 1); + } } g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, 0); @@ -1645,5 +1725,6 @@ _nmtstp_setup_tests (void) add_test_func_data ("/route/rule/1", test_rule, GINT_TO_POINTER (1)); add_test_func_data ("/route/rule/2", test_rule, GINT_TO_POINTER (2)); add_test_func_data ("/route/rule/3", test_rule, GINT_TO_POINTER (3)); + add_test_func_data ("/route/rule/4", test_rule, GINT_TO_POINTER (4)); } } |