diff options
Diffstat (limited to 'src/core/nm-netns.c')
| -rw-r--r-- | src/core/nm-netns.c | 595 |
1 files changed, 536 insertions, 59 deletions
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index 859dbb21..12ca8508 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -7,6 +7,8 @@ #include "nm-netns.h" +#include <linux/rtnetlink.h> + #include "libnm-glib-aux/nm-dedup-multi.h" #include "libnm-glib-aux/nm-c-list.h" @@ -16,6 +18,7 @@ #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-netns.h" #include "libnm-platform/nmp-global-tracker.h" +#include "libnm-std-aux/c-list-util.h" /*****************************************************************************/ @@ -28,6 +31,8 @@ typedef struct { NMPGlobalTracker *global_tracker; GHashTable *l3cfgs; GHashTable *shared_ips; + GHashTable *ecmp_track_by_obj; + GHashTable *ecmp_track_by_ecmpid; CList l3cfg_signal_pending_lst_head; GSource *signal_pending_idle_source; } NMNetnsPrivate; @@ -67,6 +72,165 @@ NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS); /*****************************************************************************/ +#define nm_assert_l3cfg(self, l3cfg) \ + G_STMT_START \ + { \ + NMNetns *_self = (self); \ + NML3Cfg *_l3cfg = (l3cfg); \ + \ + nm_assert(NM_IS_NETNS(self)); \ + nm_assert(NM_IS_L3CFG(_l3cfg)); \ + if (NM_MORE_ASSERTS > 5) \ + nm_assert(_l3cfg == nm_netns_l3cfg_get(_self, nm_l3cfg_get_ifindex(_l3cfg))); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +typedef struct { + const NMPObject *representative_obj; + const NMPObject *merged_obj; + CList ecmpid_lst_head; + bool needs_update : 1; + bool already_visited : 1; +} EcmpTrackEcmpid; + +typedef struct { + const NMPObject *obj; + + NML3Cfg *l3cfg; + EcmpTrackEcmpid *parent_track_ecmpid; + + CList ifindex_lst; + CList ecmpid_lst; + + /* Calling nm_netns_ip_route_ecmp_register() will ensure that the tracked + * entry is non-dirty. This can be used to remove stale entries. */ + bool dirty : 1; + + /* This flag is set during nm_netns_ip_route_ecmp_register(), when first tracking the + * route. It is cleared on the next nm_netns_ip_route_ecmp_commit(). It thus only + * exists for a short time, to know during a commit that the route is new and + * we need to do something special. */ + bool is_new : 1; + + /* The entry is ready to be configured. This exists, because the nexthop of + * a route must be reachable directly (being onlink). That is, we may need + * to add a direct, single-hop route to the gateway, which is done by + * the NML3Cfg of that interface. Since the NML3Cfg calls nm_netns_ip_route_ecmp_commit() + * and only adds the direct route afterwards, the ECMP route may not be ready + * right away, but only upon seeing the entry a second time. */ + bool is_ready : 1; +} EcmpTrackObj; + +static int +_ecmp_track_sort_lst_cmp(const CList *a, const CList *b, const void *user_data) +{ + EcmpTrackObj *track_obj_a = c_list_entry(a, EcmpTrackObj, ecmpid_lst); + EcmpTrackObj *track_obj_b = c_list_entry(b, EcmpTrackObj, ecmpid_lst); + const NMPlatformIP4Route *route_a = NMP_OBJECT_CAST_IP4_ROUTE(track_obj_a->obj); + const NMPlatformIP4Route *route_b = NMP_OBJECT_CAST_IP4_ROUTE(track_obj_b->obj); + + nm_assert(route_a->ifindex > 0); + nm_assert(route_a->n_nexthops <= 1); + nm_assert(route_b->ifindex > 0); + nm_assert(route_b->n_nexthops <= 1); + + NM_CMP_FIELD(route_a, route_b, ifindex); + NM_CMP_FIELD(route_b, route_a, weight); + NM_CMP_DIRECT(htonl(route_a->gateway), htonl(route_b->gateway)); + + return nm_assert_unreachable_val( + nm_platform_ip4_route_cmp(route_a, route_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)); +} + +static gboolean +_ecmp_track_init_merged_obj(EcmpTrackEcmpid *track_ecmpid, const NMPObject **out_obj_del) +{ + EcmpTrackObj *track_obj; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + gsize n_nexthops; + gsize i; + + nm_assert(track_ecmpid); + nm_assert(!c_list_is_empty(&track_ecmpid->ecmpid_lst_head)); + nm_assert(track_ecmpid->representative_obj + == c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj); + nm_assert(out_obj_del && !*out_obj_del); + + if (!track_ecmpid->needs_update) { + /* Already up to date. Nothing to do. */ + return FALSE; + } + + track_ecmpid->needs_update = FALSE; + + n_nexthops = c_list_length(&track_ecmpid->ecmpid_lst_head); + + if (n_nexthops == 1) { + /* There is only a single entry. There is nothing to merge, just set + * the first entry. */ + obj_new = nmp_object_ref(track_ecmpid->representative_obj); + goto out; + } + + /* We want that the nexthop list is deterministic. We thus sort the list and update + * the representative_obj. */ + c_list_sort(&track_ecmpid->ecmpid_lst_head, _ecmp_track_sort_lst_cmp, NULL); + nmp_object_ref_set( + &track_ecmpid->representative_obj, + c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj); + + obj_new = nmp_object_clone(track_ecmpid->representative_obj, FALSE); + + nm_assert(obj_new->ip4_route.n_nexthops <= 1); + nm_assert(!obj_new->_ip4_route.extra_nexthops); + + /* Note that there actually cannot be duplicate (ifindex,gateway,weight) tuples, because + * NML3Cfg uses NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID to track the routes, and track_ecmpid + * groups them further by NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID. The comparison for + * ECMP_ID is a strict superset of ID, hence there are no dupliated. + * + * Also, kernel wouldn't care if there were duplicate nexthops anyway. + * + * This means, it's gonna be simple. We sorted the single-hop routes by next-hop, + * now just create a plain list of the nexthops (no check for duplciates, etc). */ + + ((NMPObject *) obj_new)->ip4_route.n_nexthops = n_nexthops; + ((NMPObject *) obj_new)->_ip4_route.extra_nexthops = + g_new(NMPlatformIP4RtNextHop, n_nexthops - 1u); + + i = 0; + c_list_for_each_entry (track_obj, &track_ecmpid->ecmpid_lst_head, ecmpid_lst) { + if (i > 0) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(track_obj->obj); + NMPlatformIP4RtNextHop *nh = (gpointer) &obj_new->_ip4_route.extra_nexthops[i - 1]; + + *nh = (NMPlatformIP4RtNextHop){ + .ifindex = r->ifindex, + .gateway = r->gateway, + .weight = r->weight, + }; + } + i++; + } + +out: + nm_assert(obj_new); + if (nmp_object_equal(track_ecmpid->merged_obj, obj_new)) + /* the objects are equal but the update was needed, for example if the + * routes were removed from kernel but not from our tracking + * dictionaries and therefore we tried to register them again. */ + return TRUE; + + if (track_ecmpid->merged_obj) + *out_obj_del = g_steal_pointer(&track_ecmpid->merged_obj); + track_ecmpid->merged_obj = g_steal_pointer(&obj_new); + return TRUE; +} + +/*****************************************************************************/ + NMPNetns * nm_netns_get_platform_netns(NMNetns *self) { @@ -93,21 +257,67 @@ nm_netns_get_multi_idx(NMNetns *self) /*****************************************************************************/ -typedef struct { - int ifindex; - guint32 signal_pending_obj_type_flags; - NML3Cfg *l3cfg; - CList signal_pending_lst; -} L3CfgData; +static guint +_ecmp_routes_by_ecmpid_hash(gconstpointer ptr) +{ + const NMPObject *const *p_obj = ptr; + + return nm_platform_ip4_route_hash(NMP_OBJECT_CAST_IP4_ROUTE(*p_obj), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID); +} + +static int +_ecmp_routes_by_ecmpid_equal(gconstpointer ptr_a, gconstpointer ptr_b) +{ + const NMPObject *const *p_obj_a = ptr_a; + const NMPObject *const *p_obj_b = ptr_b; + + return nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(*p_obj_a), + NMP_OBJECT_CAST_IP4_ROUTE(*p_obj_b), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID) + == 0; +} static void -_l3cfg_data_free(gpointer ptr) +_ecmp_routes_by_ecmpid_free(gpointer ptr) { - L3CfgData *l3cfg_data = ptr; + EcmpTrackEcmpid *track_ecmpid = ptr; + + c_list_unlink_stale(&track_ecmpid->ecmpid_lst_head); + nmp_object_unref(track_ecmpid->representative_obj); + nmp_object_unref(track_ecmpid->merged_obj); + nm_g_slice_free(track_ecmpid); +} - c_list_unlink_stale(&l3cfg_data->signal_pending_lst); +static void +_ecmp_routes_by_obj_free(gpointer ptr) +{ + EcmpTrackObj *track_obj = ptr; - nm_g_slice_free(l3cfg_data); + c_list_unlink_stale(&track_obj->ifindex_lst); + c_list_unlink_stale(&track_obj->ecmpid_lst); + nmp_object_unref(track_obj->obj); + nm_g_slice_free(track_obj); +} + +/*****************************************************************************/ + +static NML3Cfg * +_l3cfg_hashed_to_l3cfg(gpointer ptr) +{ + gpointer l3cfg; + + l3cfg = &(((char *) ptr)[-G_STRUCT_OFFSET(NML3Cfg, priv.ifindex)]); + nm_assert(NM_IS_L3CFG(l3cfg)); + return l3cfg; +} + +static void +_l3cfg_hashed_free(gpointer ptr) +{ + NML3Cfg *l3cfg = _l3cfg_hashed_to_l3cfg(ptr); + + c_list_unlink(&l3cfg->internal_netns.signal_pending_lst); } static void @@ -128,58 +338,48 @@ _l3cfg_weak_notify(gpointer data, GObject *where_the_object_was) NML3Cfg * nm_netns_l3cfg_get(NMNetns *self, int ifindex) { - NMNetnsPrivate *priv; - L3CfgData *l3cfg_data; - - g_return_val_if_fail(NM_IS_NETNS(self), NULL); - g_return_val_if_fail(ifindex > 0, NULL); - - priv = NM_NETNS_GET_PRIVATE(self); + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + gpointer ptr; - l3cfg_data = g_hash_table_lookup(priv->l3cfgs, &ifindex); + nm_assert(ifindex > 0); - return l3cfg_data ? l3cfg_data->l3cfg : NULL; + ptr = g_hash_table_lookup(priv->l3cfgs, &ifindex); + return ptr ? _l3cfg_hashed_to_l3cfg(ptr) : NULL; } NML3Cfg * nm_netns_l3cfg_acquire(NMNetns *self, int ifindex) { NMNetnsPrivate *priv; - L3CfgData *l3cfg_data; + NML3Cfg *l3cfg; g_return_val_if_fail(NM_IS_NETNS(self), NULL); g_return_val_if_fail(ifindex > 0, NULL); priv = NM_NETNS_GET_PRIVATE(self); - l3cfg_data = g_hash_table_lookup(priv->l3cfgs, &ifindex); - - if (l3cfg_data) { + l3cfg = nm_netns_l3cfg_get(self, ifindex); + if (l3cfg) { nm_log_trace(LOGD_CORE, "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT ",ifindex=%d] %s", - NM_HASH_OBFUSCATE_PTR(l3cfg_data->l3cfg), + NM_HASH_OBFUSCATE_PTR(l3cfg), ifindex, "referenced"); - return g_object_ref(l3cfg_data->l3cfg); + return g_object_ref(l3cfg); } - l3cfg_data = g_slice_new(L3CfgData); - *l3cfg_data = (L3CfgData){ - .ifindex = ifindex, - .l3cfg = nm_l3cfg_new(self, ifindex), - .signal_pending_lst = C_LIST_INIT(l3cfg_data->signal_pending_lst), - }; + l3cfg = nm_l3cfg_new(self, ifindex); - if (!g_hash_table_add(priv->l3cfgs, l3cfg_data)) + if (!g_hash_table_add(priv->l3cfgs, &l3cfg->priv.ifindex)) nm_assert_not_reached(); if (NM_UNLIKELY(g_hash_table_size(priv->l3cfgs) == 1)) g_object_ref(self); - g_object_weak_ref(G_OBJECT(l3cfg_data->l3cfg), _l3cfg_weak_notify, self); + g_object_weak_ref(G_OBJECT(l3cfg), _l3cfg_weak_notify, self); /* Transfer ownership! We keep only a weak ref. */ - return l3cfg_data->l3cfg; + return l3cfg; } /*****************************************************************************/ @@ -189,7 +389,7 @@ _platform_signal_on_idle_cb(gpointer user_data) { gs_unref_object NMNetns *self = g_object_ref(NM_NETNS(user_data)); NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); - L3CfgData *l3cfg_data; + NML3Cfg *l3cfg; CList work_list; nm_clear_g_source_inst(&priv->signal_pending_idle_source); @@ -205,12 +405,12 @@ _platform_signal_on_idle_cb(gpointer user_data) c_list_init(&work_list); c_list_splice(&work_list, &priv->l3cfg_signal_pending_lst_head); - while ((l3cfg_data = c_list_first_entry(&work_list, L3CfgData, signal_pending_lst))) { - nm_assert(NM_IS_L3CFG(l3cfg_data->l3cfg)); - c_list_unlink(&l3cfg_data->signal_pending_lst); + while ((l3cfg = c_list_first_entry(&work_list, NML3Cfg, internal_netns.signal_pending_lst))) { + nm_assert(NM_IS_L3CFG(l3cfg)); + c_list_unlink(&l3cfg->internal_netns.signal_pending_lst); _nm_l3cfg_notify_platform_change_on_idle( - l3cfg_data->l3cfg, - nm_steal_int(&l3cfg_data->signal_pending_obj_type_flags)); + l3cfg, + nm_steal_int(&l3cfg->internal_netns.signal_pending_obj_type_flags)); } return G_SOURCE_CONTINUE; @@ -228,24 +428,28 @@ _platform_signal_cb(NMPlatform *platform, NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); const NMPObjectType obj_type = obj_type_i; const NMPlatformSignalChangeType change_type = change_type_i; - L3CfgData *l3cfg_data; + NML3Cfg *l3cfg; - l3cfg_data = g_hash_table_lookup(priv->l3cfgs, &ifindex); - if (!l3cfg_data) + if (ifindex <= 0) { + /* platform signal callback could be triggered by nodev routes, skip them */ + return; + } + + l3cfg = nm_netns_l3cfg_get(self, ifindex); + if (!l3cfg) return; - l3cfg_data->signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type); + l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type); - if (c_list_is_empty(&l3cfg_data->signal_pending_lst)) { - c_list_link_tail(&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst); + if (c_list_is_empty(&l3cfg->internal_netns.signal_pending_lst)) { + c_list_link_tail(&priv->l3cfg_signal_pending_lst_head, + &l3cfg->internal_netns.signal_pending_lst); if (!priv->signal_pending_idle_source) priv->signal_pending_idle_source = nm_g_idle_add_source(_platform_signal_on_idle_cb, self); } - _nm_l3cfg_notify_platform_change(l3cfg_data->l3cfg, - change_type, - NMP_OBJECT_UP_CAST(platform_object)); + _nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object)); } /*****************************************************************************/ @@ -257,7 +461,7 @@ nm_netns_shared_ip_reserve(NMNetns *self) NMNetnsSharedIPHandle *handle; const in_addr_t addr_start = ntohl(0x0a2a0001u); /* 10.42.0.1 */ in_addr_t addr; - char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + char sbuf_addr[NM_INET_ADDRSTRLEN]; /* Find an unused address in the 10.42.x.x range */ @@ -287,10 +491,10 @@ nm_netns_shared_ip_reserve(NMNetns *self) if (count > 0xFFu) { if (handle->_ref_count == 1) { _LOGE("shared-ip4: ran out of shared IP addresses. Reuse %s/24", - _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + nm_inet4_ntop(handle->addr, sbuf_addr)); } else { _LOGD("shared-ip4: reserved IP address range %s/24 (duplicate)", - _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + nm_inet4_ntop(handle->addr, sbuf_addr)); } handle->_ref_count++; return handle; @@ -307,8 +511,7 @@ nm_netns_shared_ip_reserve(NMNetns *self) g_hash_table_add(priv->shared_ips, handle); - _LOGD("shared-ip4: reserved IP address range %s/24", - _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + _LOGD("shared-ip4: reserved IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr)); return handle; } @@ -317,7 +520,7 @@ nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) { NMNetns *self; NMNetnsPrivate *priv; - char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + char sbuf_addr[NM_INET_ADDRSTRLEN]; g_return_if_fail(handle); @@ -334,7 +537,7 @@ nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) nm_assert(handle->addr == ntohl(0x0A2AFF01u)); /* 10.42.255.1 */ handle->_ref_count--; _LOGD("shared-ip4: release IP address range %s/24 (%d more references held)", - _nm_utils_inet4_ntop(handle->addr, sbuf_addr), + nm_inet4_ntop(handle->addr, sbuf_addr), handle->_ref_count); return; } @@ -347,8 +550,7 @@ nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) g_object_unref(self); } - _LOGD("shared-ip4: release IP address range %s/24", - _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + _LOGD("shared-ip4: release IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr)); handle->_self = NULL; nm_g_slice_free(handle); @@ -356,6 +558,270 @@ nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) /*****************************************************************************/ +void +nm_netns_ip_route_ecmp_register(NMNetns *self, NML3Cfg *l3cfg, const NMPObject *obj) +{ + NMNetnsPrivate *priv; + EcmpTrackObj *track_obj; + const NMPlatformIP4Route *route; + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + + nm_assert_l3cfg(self, l3cfg); + + route = NMP_OBJECT_CAST_IP4_ROUTE(obj); + + nm_assert(route->ifindex > 0); + nm_assert(route->ifindex == nm_l3cfg_get_ifindex(l3cfg)); + nm_assert(route->n_nexthops <= 1); + + priv = NM_NETNS_GET_PRIVATE(self); + + track_obj = g_hash_table_lookup(priv->ecmp_track_by_obj, &obj); + + if (NM_MORE_ASSERTS > 10) { + EcmpTrackObj *track_obj2; + gboolean found = FALSE; + + c_list_for_each_entry (track_obj2, + &l3cfg->internal_netns.ecmp_track_ifindex_lst_head, + ifindex_lst) { + if (track_obj2->obj == obj) { + found = TRUE; + break; + } + } + + nm_assert((!!track_obj) == found); + } + + if (!track_obj) { + EcmpTrackEcmpid *track_ecmpid; + + track_ecmpid = g_hash_table_lookup(priv->ecmp_track_by_ecmpid, &obj); + if (!track_ecmpid) { + track_ecmpid = g_slice_new(EcmpTrackEcmpid); + *track_ecmpid = (EcmpTrackEcmpid){ + .representative_obj = nmp_object_ref(obj), + .merged_obj = NULL, + .ecmpid_lst_head = C_LIST_INIT(track_ecmpid->ecmpid_lst_head), + .needs_update = TRUE, + }; + g_hash_table_add(priv->ecmp_track_by_ecmpid, track_ecmpid); + } else + track_ecmpid->needs_update = TRUE; + + track_obj = g_slice_new(EcmpTrackObj); + *track_obj = (EcmpTrackObj){ + .obj = nmp_object_ref(obj), + .l3cfg = l3cfg, + .parent_track_ecmpid = track_ecmpid, + .dirty = FALSE, + .is_new = TRUE, + .is_ready = FALSE, + }; + + g_hash_table_add(priv->ecmp_track_by_obj, track_obj); + c_list_link_tail(&l3cfg->internal_netns.ecmp_track_ifindex_lst_head, + &track_obj->ifindex_lst); + c_list_link_tail(&track_ecmpid->ecmpid_lst_head, &track_obj->ecmpid_lst); + + _LOGT( + "ecmp-route: track %s", + nmp_object_to_string(track_obj->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + } else { + track_obj->dirty = FALSE; + track_obj->parent_track_ecmpid->needs_update = TRUE; + } +} + +void +nm_netns_ip_route_ecmp_commit(NMNetns *self, + NML3Cfg *l3cfg, + GPtrArray **out_singlehop_routes, + gboolean is_reapply) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + EcmpTrackObj *track_obj; + EcmpTrackObj *track_obj_safe; + EcmpTrackEcmpid *track_ecmpid; + const NMPObject *route_obj; + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + gboolean already_notified = FALSE; + + nm_assert_l3cfg(self, l3cfg); + + _LOGT("ecmp-route: committing IPv4 ECMP routes"); + + /* First, delete all dirty entries, and mark the survivors as dirty, so that on the + * next update they must be touched again. */ + c_list_for_each_entry_safe (track_obj, + track_obj_safe, + &l3cfg->internal_netns.ecmp_track_ifindex_lst_head, + ifindex_lst) { + track_ecmpid = track_obj->parent_track_ecmpid; + track_ecmpid->already_visited = FALSE; + + nm_assert(g_hash_table_lookup(priv->ecmp_track_by_ecmpid, track_ecmpid) == track_ecmpid); + nm_assert(g_hash_table_lookup(priv->ecmp_track_by_obj, track_obj) == track_obj); + nm_assert(c_list_contains(&track_ecmpid->ecmpid_lst_head, &track_obj->ecmpid_lst)); + nm_assert(track_obj->l3cfg == l3cfg); + + if (!track_obj->dirty) { + /* This one is still in used. Keep it, but mark dirty, so that on the + * next update cycle, it needs to be touched again or will be deleted. */ + track_obj->dirty = TRUE; + if (is_reapply) { + track_obj->is_new = TRUE; + track_obj->is_ready = FALSE; + } + if (track_obj->is_new) { + const NMPlatformIP4Route *route = + NMP_OBJECT_CAST_IP4_ROUTE(track_ecmpid->merged_obj); + + /* This is a new route entry that was just added. Upon first + * addition, the route is not yet ready for configuration, + * because we need to make sure that the gateway is reachable + * via an onlink route. The calling l3cfg will configure that + * route, but only after returning from this function. So we + * need to go through one more commit. + * + * We also need to make sure that we are called back right + * after l3cfg configured that route. We achieve that by + * scheduling another idle commit on "l3cfg". */ + track_obj->is_new = FALSE; + if (route + && (route->gateway == 0 + || NM_FLAGS_HAS(route->r_rtm_flags, (unsigned) RTNH_F_ONLINK))) { + /* This route is onlink. We don't need to configure an onlink route + * to the gateway, and the route is immediately ready for configuration. */ + track_obj->is_ready = TRUE; + } else if (c_list_length_is(&track_ecmpid->ecmpid_lst_head, 1)) { + /* This route has no merge partner and ends up being a + * single hop route. It will be returned and configured by + * the calling "l3cfg". + * + * Unlike for multi-hop routes, we don't need to be called + * again after the onlink route was added. We are done, and + * don't need to schedule an idle commit. */ + track_obj->is_ready = TRUE; + } else { + /* This is a new route which has a gateway. We need for the "l3cfg" + * to first configure the onlink route. It's not yet ready for configuration. + * + * Instead, schedule an idle commit to make sure we get called back + * again, and then (upon seeing the entry the second time) the onlink + * route is already configured and we will be ready. */ + if (!already_notified) { + /* Some micro optimization with already_notified to avoid calling + * schedule unnecessarily. */ + already_notified = TRUE; + nm_l3cfg_commit_on_idle_schedule(l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO); + } + } + } else { + /* We see this entry the second time (or more) so it's ready. */ + track_obj->is_ready = TRUE; + } + continue; + } + + /* This entry can be dropped. */ + if (!g_hash_table_remove(priv->ecmp_track_by_obj, track_obj)) + nm_assert_not_reached(); + + if (c_list_is_empty(&track_ecmpid->ecmpid_lst_head)) { + if (track_ecmpid->merged_obj) { + if (NMP_OBJECT_CAST_IP4_ROUTE(track_ecmpid->merged_obj)->n_nexthops > 1) + nm_platform_object_delete(priv->platform, track_ecmpid->merged_obj); + } + g_hash_table_remove(priv->ecmp_track_by_ecmpid, track_ecmpid); + + continue; + } + + /* We need to update the representative obj. */ + nmp_object_ref_set( + &track_ecmpid->representative_obj, + c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj); + track_ecmpid->needs_update = TRUE; + } + + /* Now, we need to iterate again over all objects, and regenerate the merged_obj. */ + c_list_for_each_entry (track_obj, + &l3cfg->internal_netns.ecmp_track_ifindex_lst_head, + ifindex_lst) { + const NMPlatformIP4Route *route; + EcmpTrackObj *track_obj2; + nm_auto_nmpobj const NMPObject *obj_del = NULL; + gboolean changed; + gboolean all_is_ready; + + track_ecmpid = track_obj->parent_track_ecmpid; + if (track_ecmpid->already_visited) { + /* We already visited this ecmpid in the same loop. We can skip, otherwise + * we might add the same route twice. */ + continue; + } + track_ecmpid->already_visited = TRUE; + + all_is_ready = TRUE; + c_list_for_each_entry (track_obj2, &track_ecmpid->ecmpid_lst_head, ecmpid_lst) { + if (!track_obj2->is_ready) { + all_is_ready = FALSE; + break; + } + } + if (!all_is_ready) { + /* Here we might have a merged_obj already which can have the wrong + * setting e.g the wrong nexthops. We leave them for the moment and + * then we reconfigure it when this entry is ready. */ + continue; + } + + changed = _ecmp_track_init_merged_obj(track_obj->parent_track_ecmpid, &obj_del); + + nm_assert(!obj_del || changed); + + route_obj = track_ecmpid->merged_obj; + route = NMP_OBJECT_CAST_IP4_ROUTE(route_obj); + + if (obj_del) { + if (NMP_OBJECT_CAST_IP4_ROUTE(obj_del)->n_nexthops > 1) + nm_platform_object_delete(priv->platform, obj_del); + else if (track_obj->l3cfg != l3cfg) + nm_l3cfg_commit_on_idle_schedule(track_obj->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO); + } + + if (route->n_nexthops <= 1) { + /* This is a single hop route. Return it to the caller. */ + if (!*out_singlehop_routes) { + /* Note that the returned array does not own a reference. This + * function has only one caller, and for that caller, it's just + * fine that the result is not additionally kept alive. */ + *out_singlehop_routes = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + } + g_ptr_array_add(*out_singlehop_routes, (gpointer) nmp_object_ref(route_obj)); + if (changed) { + _LOGT("ecmp-route: single-hop %s", + nmp_object_to_string(route_obj, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf, + sizeof(sbuf))); + } + continue; + } + + if (changed || is_reapply) { + _LOGT("ecmp-route: multi-hop %s", + nmp_object_to_string(route_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, route_obj); + } + } +} + +/*****************************************************************************/ + static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { @@ -385,6 +851,14 @@ nm_netns_init(NMNetns *self) priv->_self_signal_user_data = self; c_list_init(&priv->l3cfg_signal_pending_lst_head); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0); + priv->ecmp_track_by_obj = + g_hash_table_new_full(nm_pdirect_hash, nm_pdirect_equal, _ecmp_routes_by_obj_free, NULL); + priv->ecmp_track_by_ecmpid = g_hash_table_new_full(_ecmp_routes_by_ecmpid_hash, + _ecmp_routes_by_ecmpid_equal, + _ecmp_routes_by_ecmpid_free, + NULL); } static void @@ -396,7 +870,7 @@ constructed(GObject *object) if (!priv->platform) g_return_if_reached(); - priv->l3cfgs = g_hash_table_new_full(nm_pint_hash, nm_pint_equal, _l3cfg_data_free, NULL); + priv->l3cfgs = g_hash_table_new_full(nm_pint_hash, nm_pint_equal, _l3cfg_hashed_free, NULL); priv->platform_netns = nm_platform_netns_get(priv->platform); @@ -464,6 +938,9 @@ dispose(GObject *object) nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head)); nm_assert(!priv->shared_ips); + nm_clear_pointer(&priv->ecmp_track_by_obj, g_hash_table_destroy); + nm_clear_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy); + nm_clear_g_source_inst(&priv->signal_pending_idle_source); if (priv->platform) |