summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2022-07-18 10:16:31 +0200
committerThomas Haller <thaller@redhat.com>2022-08-09 08:02:52 +0200
commit5374c403d285b542cf2ed95d6208cc792a4d18db (patch)
treec24a390d9ba61cf581bcd41cc2ef87a8a1e5df05
parentce635c4339125a30cb470fee222e73c2125ecfd7 (diff)
downloadNetworkManager-5374c403d285b542cf2ed95d6208cc792a4d18db.tar.gz
platfrom: handle MPTCP addresses with NMPGlobalTracker
When we configure MPTCP addresses, we usually do so per interface (ifindex). That is, because each interface (via NMDevice and NML3Cfg) decides how to configure MPTCP, and then we always add MTCP addresses for this certain ifindex. With that, we could have a purely interface-specific view and not a global sync method. However, there are two problems: The minor problem is that we don't cache the endpoints (because we don't get notifications). We can only get a dump of all endpoints. It seems odd to have a mptcp-addr-sync method that is per-ifindex, when it needs to dump all addresses. The much more important reason is that the number of endpoints that we can configure in kernel is very limited. So we need to make a choice which endpoints to configure, and for that we need to holistic view that NMPGlobalTracker has.
-rw-r--r--src/core/platform/tests/test-common.h5
-rw-r--r--src/core/platform/tests/test-route.c199
-rw-r--r--src/libnm-platform/nmp-global-tracker.c394
-rw-r--r--src/libnm-platform/nmp-global-tracker.h7
4 files changed, 590 insertions, 15 deletions
diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h
index 77f2c50de0..da30b0ed2f 100644
--- a/src/core/platform/tests/test-common.h
+++ b/src/core/platform/tests/test-common.h
@@ -518,6 +518,11 @@ void nmtstp_link_delete(NMPlatform *platform,
/*****************************************************************************/
+#define nmtst_object_new_mptcp_addr(...) \
+ nmp_object_new(NMP_OBJECT_TYPE_MPTCP_ADDR, &((const NMPlatformMptcpAddr){__VA_ARGS__}))
+
+/*****************************************************************************/
+
extern int NMTSTP_ENV1_IFINDEX;
extern int NMTSTP_ENV1_EX;
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index f0142fcb5f..061b99a34f 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -1961,6 +1961,200 @@ test_blackhole(gconstpointer test_data)
/*****************************************************************************/
+static gboolean
+_mptcp_has_permissions(void)
+{
+ static int has_permissions = -1;
+ int p;
+
+ /* We create a new netns for testing, where we also have CAP_NET_ADMIN.
+ * However, that is not enough for configuring MPTCP endpoints. Probably
+ * you can only create them, by running the test as root. Detect the
+ * inability, to skip the test.
+ *
+ * See https://lore.kernel.org/mptcp/20220805115020.525181-1-thaller@redhat.com/T/#u */
+
+again:
+ p = g_atomic_int_get(&has_permissions);
+
+ if (p == -1) {
+ static gsize lock;
+ const NMPlatformMptcpAddr mptcp_addr = (NMPlatformMptcpAddr){
+ .id = 1,
+ .addr_family = AF_INET,
+ .addr.addr4 = nmtst_inet4_from_string("1.2.3.4"),
+ };
+ int r;
+
+ if (!g_once_init_enter(&lock))
+ goto again;
+
+ if (nmtst_get_rand_one_case_in(3)) {
+ gs_unref_ptrarray GPtrArray *arr = NULL;
+
+ arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+ g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 0);
+ }
+
+ r = nm_platform_mptcp_addr_update(NM_PLATFORM_GET, TRUE, &mptcp_addr);
+ if (r == 0)
+ p = TRUE;
+ else if (r == -EPERM)
+ p = FALSE;
+ else
+ g_assert_cmpint(r, ==, 0);
+
+ if (p) {
+ if (nmtst_get_rand_one_case_in(3)) {
+ gs_unref_ptrarray GPtrArray *arr = NULL;
+
+ arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+ g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 1);
+ }
+
+ r = nm_platform_mptcp_addr_update(NM_PLATFORM_GET, FALSE, &mptcp_addr);
+ g_assert_cmpint(r, ==, 0);
+ }
+
+ if (nmtst_get_rand_one_case_in(3)) {
+ gs_unref_ptrarray GPtrArray *arr = NULL;
+
+ arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+ g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 0);
+ }
+
+ g_atomic_int_set(&has_permissions, p);
+ g_once_init_leave(&lock, 1);
+ }
+
+ return p;
+}
+
+static gboolean
+_mptcp_skip_test(void)
+{
+ if (nm_platform_genl_get_family_id(NM_PLATFORM_GET, NMP_GENL_FAMILY_TYPE_MPTCP_PM) == 0) {
+ g_test_skip("mptcp not available");
+ return TRUE;
+ }
+
+ if (!_mptcp_has_permissions()) {
+ g_test_skip("No permissions to create MPTCP endpoints");
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+test_mptcp(gconstpointer test_data)
+{
+ const int TEST_IDX = GPOINTER_TO_INT(test_data);
+ gs_unref_object NMPlatform *platform = g_object_ref(NM_PLATFORM_GET);
+ nm_auto_unref_global_tracker NMPGlobalTracker *global_tracker =
+ nmp_global_tracker_new(platform);
+ gconstpointer const USER_TAG = &TEST_IDX;
+ const int IFINDEX = nm_platform_link_get_ifindex(platform, DEVICE_NAME);
+ guint i;
+ guint j;
+ int r;
+ gs_unref_ptrarray GPtrArray *arr_external =
+ g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+ gs_unref_ptrarray GPtrArray *arr_tracked =
+ g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+ const NMPObject *obj;
+ gboolean delete_extra;
+
+ g_assert_cmpint(IFINDEX, >, 0);
+
+ if (_mptcp_skip_test())
+ return;
+
+ j = nmtst_get_rand_uint32() % 5;
+ for (i = 0; i < j; i++) {
+ obj = nmtst_object_new_mptcp_addr(.id = i + 1,
+ .ifindex = IFINDEX,
+ .addr_family = AF_INET,
+ .addr.addr4 = htonl(0xC0A80001u + i));
+ g_ptr_array_add(arr_external, (gpointer) obj);
+ r = nm_platform_mptcp_addr_update(platform, TRUE, NMP_OBJECT_CAST_MPTCP_ADDR(obj));
+ g_assert_cmpint(r, ==, 0);
+ }
+
+ j = nmtst_get_rand_uint32() % 10;
+ for (i = 0; i < j; i++) {
+ obj = nmtst_object_new_mptcp_addr(.ifindex = IFINDEX,
+ .addr_family = AF_INET,
+ .addr.addr4 = htonl(0xC0A80001u + i));
+ g_ptr_array_add(arr_tracked, (gpointer) obj);
+ nmp_global_tracker_track(global_tracker,
+ NMP_OBJECT_TYPE_MPTCP_ADDR,
+ NMP_OBJECT_CAST_MPTCP_ADDR(obj),
+ 20 - i,
+ USER_TAG,
+ NULL);
+ }
+ for (i = 0; i < arr_tracked->len;) {
+ if (nmtst_get_rand_bool()) {
+ nmp_global_tracker_untrack(global_tracker,
+ NMP_OBJECT_TYPE_MPTCP_ADDR,
+ NMP_OBJECT_CAST_MPTCP_ADDR(arr_tracked->pdata[i]),
+ USER_TAG);
+ g_ptr_array_remove_index(arr_tracked, i);
+ } else
+ i++;
+ }
+
+ if (arr_tracked->len == 0 || nmtst_get_rand_bool()) {
+ NMPlatformMptcpAddr a;
+
+ /* Track a dummy object that marks the ifindex as managed. */
+ nmp_global_tracker_track(global_tracker,
+ NMP_OBJECT_TYPE_MPTCP_ADDR,
+ nmp_global_tracker_mptcp_addr_init_for_ifindex(&a, IFINDEX),
+ 10,
+ USER_TAG,
+ NULL);
+ }
+
+ nmp_global_tracker_sync_mptcp_addrs(global_tracker, FALSE, FALSE);
+
+ if (nmtst_get_rand_bool()) {
+ gboolean reapply;
+
+ nmp_global_tracker_untrack_all(global_tracker, USER_TAG, TRUE, FALSE);
+ reapply = nmtst_get_rand_bool();
+ nmp_global_tracker_sync_mptcp_addrs(global_tracker, reapply, FALSE);
+
+ delete_extra = !reapply;
+ } else
+ delete_extra = TRUE;
+
+ if (delete_extra) {
+ gs_unref_ptrarray GPtrArray *arr = NULL;
+
+ /* We need to delete all MPTCP address again, because the next test uses the
+ * same netns (this test setup doesn't create a netns per test). */
+ arr = nm_platform_mptcp_addrs_dump(platform);
+ for (i = 0; i < nm_g_ptr_array_len(arr); i++) {
+ r = nm_platform_mptcp_addr_update(platform,
+ FALSE,
+ NMP_OBJECT_CAST_MPTCP_ADDR(arr->pdata[i]));
+ g_assert(NMTST_NM_ERR_SUCCESS(r));
+ }
+ }
+
+ {
+ gs_unref_ptrarray GPtrArray *arr = NULL;
+
+ arr = nm_platform_mptcp_addrs_dump(platform);
+ g_assert(arr);
+ g_assert_cmpint(arr->len, ==, 0);
+ }
+}
+
+/*****************************************************************************/
+
NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
void
@@ -1975,6 +2169,7 @@ _nmtstp_setup_tests(void)
#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE)
#define add_test_func_data(testpath, test_func, arg) \
nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE)
+
add_test_func("/route/ip4", test_ip4_route);
add_test_func("/route/ip6", test_ip6_route);
add_test_func("/route/ip4_metric0", test_ip4_route_metric0);
@@ -2002,4 +2197,8 @@ _nmtstp_setup_tests(void)
add_test_func_data("/route/blackhole/1", test_blackhole, GINT_TO_POINTER(1));
add_test_func_data("/route/blackhole/2", test_blackhole, GINT_TO_POINTER(2));
}
+ if (nmtstp_is_root_test()) {
+ add_test_func_data("/route/mptcp/1", test_mptcp, GINT_TO_POINTER(1));
+ add_test_func_data("/route/mptcp/2", test_mptcp, GINT_TO_POINTER(2));
+ }
}
diff --git a/src/libnm-platform/nmp-global-tracker.c b/src/libnm-platform/nmp-global-tracker.c
index c4f50918f8..1f9a6ce2f1 100644
--- a/src/libnm-platform/nmp-global-tracker.c
+++ b/src/libnm-platform/nmp-global-tracker.c
@@ -11,6 +11,10 @@
#include "libnm-std-aux/c-list-util.h"
#include "nmp-object.h"
+/* This limit comes from kernel, and it limits the number of MPTCP addresses
+ * we can configure. */
+#define MPTCP_PM_ADDR_MAX 8
+
/*****************************************************************************/
/* NMPGlobalTracker tracks certain objects for the entire network namespace and can
@@ -48,7 +52,7 @@ struct _NMPGlobalTracker {
GHashTable *by_obj;
GHashTable *by_user_tag;
GHashTable *by_data;
- CList by_obj_lst_heads[3];
+ CList by_obj_lst_heads[4];
guint ref_count;
};
@@ -93,6 +97,9 @@ typedef struct {
guint32 track_priority_val;
bool track_priority_present : 1;
+ /* Calling nmp_global_tracker_track() will ensure that the tracked entry is
+ * non-dirty. Together with nmp_global_tracker_set_dirty() and nmp_global_tracker_untrack_all()'s
+ * @all parameter, this can be used to remove stale entries. */
bool dirty : 1;
} TrackData;
@@ -149,7 +156,7 @@ static void _track_data_untrack(NMPGlobalTracker *self,
static CList *
_by_obj_lst_head(NMPGlobalTracker *self, NMPObjectType obj_type)
{
- G_STATIC_ASSERT(G_N_ELEMENTS(self->by_obj_lst_heads) == 3);
+ G_STATIC_ASSERT(G_N_ELEMENTS(self->by_obj_lst_heads) == 4);
switch (obj_type) {
case NMP_OBJECT_TYPE_IP4_ROUTE:
@@ -158,6 +165,8 @@ _by_obj_lst_head(NMPGlobalTracker *self, NMPObjectType obj_type)
return &self->by_obj_lst_heads[1];
case NMP_OBJECT_TYPE_ROUTING_RULE:
return &self->by_obj_lst_heads[2];
+ case NMP_OBJECT_TYPE_MPTCP_ADDR:
+ return &self->by_obj_lst_heads[3];
default:
return nm_assert_unreachable_val(NULL);
}
@@ -172,7 +181,8 @@ _track_data_assert(const TrackData *track_data, gboolean linked)
nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(track_data->obj),
NMP_OBJECT_TYPE_IP4_ROUTE,
NMP_OBJECT_TYPE_IP6_ROUTE,
- NMP_OBJECT_TYPE_ROUTING_RULE));
+ NMP_OBJECT_TYPE_ROUTING_RULE,
+ NMP_OBJECT_TYPE_MPTCP_ADDR));
nm_assert(nmp_object_is_visible(track_data->obj));
nm_assert(track_data->user_tag);
nm_assert(!linked || !c_list_is_empty(&track_data->obj_lst));
@@ -296,13 +306,42 @@ _track_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user
/*****************************************************************************/
+static const NMPObject *
+_obj_stackinit(NMPObject *obj_stack, NMPObjectType obj_type, gconstpointer obj)
+{
+ nmp_object_stackinit(obj_stack, obj_type, obj);
+
+ if (NM_MORE_ASSERTS > 10) {
+ if (obj_type == NMP_OBJECT_TYPE_MPTCP_ADDR) {
+ NMPlatformMptcpAddr *m = NMP_OBJECT_CAST_MPTCP_ADDR(obj_stack);
+ NMPlatformMptcpAddr m_dummy;
+
+ /* Only certain MPTCP addresses can be added. */
+ nm_assert(m->ifindex > 0);
+ if (nm_platform_mptcp_addr_cmp(
+ nmp_global_tracker_mptcp_addr_init_for_ifindex(&m_dummy, m->ifindex),
+ m)
+ == 0) {
+ /* This is a dummy instance. We are good. */
+ } else {
+ nm_assert_addr_family(m->addr_family);
+ nm_assert(m->port == 0);
+ nm_assert(m->id == 0);
+ }
+ }
+ }
+
+ nm_assert(nmp_object_is_visible(obj_stack));
+ return obj_stack;
+}
+
/**
* nmp_global_tracker_track:
* @self: the #NMPGlobalTracker instance
* @obj_type: the NMPObjectType of @obj that we are tracking.
* @obj: the NMPlatformObject (of type NMPObjectType) to track. Usually
- * a #NMPlatformRoutingRule, #NMPlatformIP4Route or #NMPlatformIP6Route
- * pointer.
+ * a #NMPlatformRoutingRule, #NMPlatformIP4Route, #NMPlatformIP6Route
+ * or #NMPlatformMptcpAddr pointer.
* @track_priority: the priority for tracking the rule. Note that
* negative values indicate a forced absence of the rule. Priorities
* are compared with their absolute values (with higher absolute
@@ -345,16 +384,15 @@ nmp_global_tracker_track(NMPGlobalTracker *self,
/* The route must not be tied to an interface. We can only handle here
* blackhole/unreachable/prohibit route types. */
g_return_val_if_fail(
- obj_type == NMP_OBJECT_TYPE_ROUTING_RULE
+ NM_IN_SET(obj_type, NMP_OBJECT_TYPE_ROUTING_RULE, NMP_OBJECT_TYPE_MPTCP_ADDR)
|| (NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
&& ((const NMPlatformIPRoute *) obj)->ifindex == 0),
FALSE);
- nm_assert(track_priority != G_MININT32);
-
- p_obj_stack = nmp_object_stackinit(&obj_stack, obj_type, obj);
+ /* only positive track priorities are implemented for MPTCP addrs. */
+ nm_assert(obj_type != NMP_OBJECT_TYPE_MPTCP_ADDR || track_priority > 0);
- nm_assert(nmp_object_is_visible(p_obj_stack));
+ p_obj_stack = _obj_stackinit(&obj_stack, obj_type, obj);
if (track_priority >= 0) {
track_priority_val = track_priority;
@@ -512,13 +550,12 @@ nmp_global_tracker_untrack(NMPGlobalTracker *self,
nm_assert(NM_IN_SET(obj_type,
NMP_OBJECT_TYPE_IP4_ROUTE,
NMP_OBJECT_TYPE_IP6_ROUTE,
- NMP_OBJECT_TYPE_ROUTING_RULE));
+ NMP_OBJECT_TYPE_ROUTING_RULE,
+ NMP_OBJECT_TYPE_MPTCP_ADDR));
g_return_val_if_fail(obj, FALSE);
g_return_val_if_fail(user_tag, FALSE);
- p_obj_stack = nmp_object_stackinit(&obj_stack, obj_type, obj);
-
- nm_assert(nmp_object_is_visible(p_obj_stack));
+ p_obj_stack = _obj_stackinit(&obj_stack, obj_type, obj);
track_data = _track_data_lookup(self->by_data, p_obj_stack, user_tag);
if (track_data) {
@@ -584,12 +621,335 @@ nmp_global_tracker_untrack_all(NMPGlobalTracker *self,
/*****************************************************************************/
+/* Usually, we track NMPlatformMptcpAddr instances with an ifindex set.
+ * If we have *any* such instance, we know that the ifindex is fully
+ * synched (meaning, we will delete all unknown endpoints for that interface).
+ * However, if we don't have an endpoint on the interface, we may still
+ * want to track that a certain ifindex is fully managed.
+ *
+ * This initializes a dummy instance for exactly that purpose. */
+const NMPlatformMptcpAddr *
+nmp_global_tracker_mptcp_addr_init_for_ifindex(NMPlatformMptcpAddr *addr, int ifindex)
+{
+ nm_assert(addr);
+ nm_assert(ifindex > 0);
+
+ *addr = (NMPlatformMptcpAddr){
+ .ifindex = ifindex,
+ .addr_family = AF_UNSPEC,
+ };
+
+ return addr;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ TrackObjData *obj_data;
+ const TrackData *td_best;
+} MptcpSyncData;
+
+static int
+_mptcp_entries_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ const MptcpSyncData *d_a = a;
+ const MptcpSyncData *d_b = b;
+
+ /* 1) prefer addresses based on the priority (highest priority
+ * sorted first). */
+ NM_CMP_FIELD(d_b->td_best, d_a->td_best, track_priority_val);
+
+ /* Finally, we only care about the order in which they were tracked.
+ * Rely on the stable sort to get that right. */
+ return 0;
+}
+
+void
+nmp_global_tracker_sync_mptcp_addrs(NMPGlobalTracker *self, gboolean reapply, gboolean keep_deleted)
+{
+ char sbuf[64 + NM_UTILS_TO_STRING_BUFFER_SIZE];
+ gs_unref_ptrarray GPtrArray *kaddrs_arr = NULL;
+ gs_unref_hashtable GHashTable *kaddrs_idx = NULL;
+ TrackObjData *obj_data;
+ TrackObjData *obj_data_safe;
+ CList *by_obj_lst_head;
+ const TrackData *td_best;
+ gs_unref_hashtable GHashTable *handled_ifindexes = NULL;
+ gs_unref_array GArray *entries = NULL;
+ gs_unref_hashtable GHashTable *entries_hash_by_addr = NULL;
+ gs_unref_hashtable GHashTable *entries_to_delete = NULL;
+ guint i;
+ guint j;
+
+ g_return_if_fail(NMP_IS_GLOBAL_TRACKER(self));
+
+ _LOGD("sync mptcp-addr%s%s",
+ reapply ? " (reapply)" : "",
+ keep_deleted ? " (keep-deleted)" : "");
+
+ /* Iterate over the tracked objects and construct @handled_ifindexes, @entries
+ * and @entries_to_delete.
+ * - @handled_ifindexes is a hash with all managed interfaces (their ifindex).
+ * - @entries are the MptcpSyncData instances for the tracked objects.
+ * - @entries_to_delete are the NMPObject which we added earlier, but now not
+ * anymore (and which we shall delete). */
+ by_obj_lst_head = _by_obj_lst_head(self, NMP_OBJECT_TYPE_MPTCP_ADDR);
+ c_list_for_each_entry_safe (obj_data, obj_data_safe, by_obj_lst_head, by_obj_lst) {
+ const NMPlatformMptcpAddr *mptcp_addr = NMP_OBJECT_CAST_MPTCP_ADDR(obj_data->obj);
+ NMPlatformMptcpAddr xtst;
+
+ nm_assert(mptcp_addr->port == 0);
+ nm_assert(mptcp_addr->ifindex > 0);
+ nm_assert(mptcp_addr->id == 0);
+ nm_assert_addr_family_or_unspec(mptcp_addr->addr_family);
+
+ /* AF_UNSPEC means this is the dummy object. We only care about it to make the
+ * ifindex as managed via @handled_ifindexes. */
+ nm_assert(
+ (mptcp_addr->addr_family == AF_UNSPEC)
+ == (nm_platform_mptcp_addr_cmp(
+ mptcp_addr,
+ nmp_global_tracker_mptcp_addr_init_for_ifindex(&xtst, mptcp_addr->ifindex))
+ == 0));
+
+ if (reapply) {
+ /* For a reapply, we clear all configured MPTCP addresses that we no longer
+ * shall configured, provided they are on one of the ifindexes we care
+ * about. */
+ if (!handled_ifindexes)
+ handled_ifindexes = g_hash_table_new(nm_direct_hash, NULL);
+ g_hash_table_add(handled_ifindexes, GINT_TO_POINTER(mptcp_addr->ifindex));
+ }
+
+ td_best = _track_obj_data_get_best_data(obj_data);
+
+ if (!td_best) {
+ nm_assert(obj_data->config_state == CONFIG_STATE_ADDED_BY_US);
+
+ /* This entry is a tombstone, that tells us that added the object earlier.
+ * We can delete the MPTCP address (if it's still configured).
+ *
+ * Then we can drop the tombstone. */
+
+ if (mptcp_addr->addr_family != AF_UNSPEC) {
+ if (!reapply) {
+ if (!entries_to_delete) {
+ entries_to_delete = g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
+ (GEqualFunc) nmp_object_id_equal,
+ (GDestroyNotify) nmp_object_unref,
+ NULL);
+ }
+ g_hash_table_add(entries_to_delete, (gpointer) nmp_object_ref(obj_data->obj));
+ }
+ }
+
+ /* We can forget about this entry now. */
+ g_hash_table_remove(self->by_obj, obj_data);
+ continue;
+ }
+
+ /* negative and zero track priorities are not implemented (and make no sense?). */
+ nm_assert(td_best->track_priority_val > 0);
+ nm_assert(td_best->track_priority_present);
+
+ if (mptcp_addr->addr_family == AF_UNSPEC) {
+ /* This is a nmp_global_tracker_mptcp_addr_init_for_ifindex() dummy entry.
+ * It only exists so we can add the @handled_ifindexes entry above
+ * and handle addresses on this interface. */
+ obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
+ continue;
+ }
+
+ if (!entries)
+ entries = g_array_new(FALSE, FALSE, sizeof(MptcpSyncData));
+
+ g_array_append_val(entries,
+ ((const MptcpSyncData){
+ .obj_data = obj_data,
+ .td_best = td_best,
+ }));
+ }
+ /* We collected all the entires we want to configure. Now, sort them by
+ * priority, and drop all the duplicates (preferring the entries that
+ * appear first, where first means "older"). In kernel, we can only configure an IP
+ * address (without port) as endpoint once. If two interfaces provide the same IP
+ * address, we can only configure one. We need to select one and filter out duplicates.
+ * While there is no solution, the idea is to select the preferred address
+ * somewhat consistently.
+ *
+ * Also, create a lookup index @entries_hash_by_addr to lookup by address. */
+ if (entries) {
+ /* First we sort the entries by priority, to prefer the ones with higher
+ * priority. In case of equal priority, we rely on the stable sort to
+ * preserve the order in which things got tracked. */
+ g_array_sort_with_data(entries, _mptcp_entries_cmp, NULL);
+
+ entries_hash_by_addr = g_hash_table_new(nm_platform_mptcp_addr_index_addr_cmp,
+ nm_platform_mptcp_addr_index_addr_equal);
+
+ /* Now, drop all duplicates addresses. Only keep the first one. */
+ for (i = 0, j = 0; i < entries->len; i++) {
+ const MptcpSyncData *d = nm_g_array_index_p(entries, MptcpSyncData, i);
+ const NMPlatformMptcpAddr *mptcp_addr = NMP_OBJECT_CAST_MPTCP_ADDR(d->obj_data->obj);
+
+ obj_data = g_hash_table_lookup(entries_hash_by_addr, (gpointer) mptcp_addr);
+ if (obj_data) {
+ /* This object is shadowed. We ignore it.
+ *
+ * However, we first propagate the config_state. For MPTCP addrs, it can only be
+ * NONE or ADDED_BY_US. */
+ nm_assert(NM_IN_SET(d->obj_data->config_state,
+ CONFIG_STATE_NONE,
+ CONFIG_STATE_ADDED_BY_US));
+ nm_assert(
+ NM_IN_SET(obj_data->config_state, CONFIG_STATE_NONE, CONFIG_STATE_ADDED_BY_US));
+
+ if (d->obj_data->config_state == CONFIG_STATE_ADDED_BY_US) {
+ obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
+ d->obj_data->config_state = CONFIG_STATE_NONE;
+ }
+ continue;
+ }
+
+ if (!g_hash_table_insert(entries_hash_by_addr, (gpointer) mptcp_addr, d->obj_data))
+ nm_assert_not_reached();
+
+ if (i != j)
+ *(nm_g_array_index_p(entries, MptcpSyncData, j)) = *d;
+ j++;
+
+ if (j >= MPTCP_PM_ADDR_MAX) {
+ /* Kernel limits the number of addresses we can configure.
+ * It's hard-coded here, taken from current kernel. Hopefully
+ * it matches the running kernel.
+ *
+ * It's worse. There might be other addresses already configured
+ * on other interfaces (or with a port). Our sync method will leave
+ * them alone, as they were not added by us. So the actual limit
+ * is possibly smaller, and kernel fails with EINVAL.
+ *
+ * Still, we definitely need to truncate the list here. Imagine
+ * during an earlier sync we added MAX addresses on one interface.
+ * Now, another interface activates, and wants to configure one
+ * address. That address will get a higher priority (chosen by NML3Cfg),
+ * so that part is good. However, it means we must drop the last from
+ * the other MAX addresses. We achieve that by truncating the list
+ * to MPTCP_PM_ADDR_MAX.
+ */
+ break;
+ }
+ }
+ g_array_set_size(entries, j);
+ }
+
+ /* Get the list of currently (in kernel) configured MPTCP endpoints. */
+ kaddrs_arr = nm_platform_mptcp_addrs_dump(self->platform);
+
+ /* First, delete all kaddrs which we no longer want... */
+ if (kaddrs_arr) {
+ for (i = 0; i < kaddrs_arr->len; i++) {
+ const NMPObject *obj = kaddrs_arr->pdata[i];
+ const NMPlatformMptcpAddr *mptcp_addr = NMP_OBJECT_CAST_MPTCP_ADDR(obj);
+
+ if (mptcp_addr->port != 0 || mptcp_addr->ifindex <= 0) {
+ /* We ignore all endpoints that have a port or no ifindex.
+ * Those were never created by us, let the user who created
+ * them handle them. */
+ nm_clear_pointer(&kaddrs_arr->pdata[i], nmp_object_unref);
+ continue;
+ }
+
+ if (reapply) {
+ /* In full-sync-mode, we delete all MPTCP addrs that are for ifindexes
+ * that we care about. */
+ if (!nm_g_hash_table_contains(handled_ifindexes,
+ GINT_TO_POINTER(mptcp_addr->ifindex))) {
+ goto index_and_next;
+ }
+ } else {
+ /* Otherwise, we only delete objects that we remember to have
+ * added earlier. */
+ if (!nm_g_hash_table_contains(entries_to_delete, obj)) {
+ /* This object is not to delete. */
+ goto index_and_next;
+ }
+ }
+
+ /* We have the object in the delete-list. However, we might still also want
+ * to add it back. Check for that too. */
+ obj_data = nm_g_hash_table_lookup(entries_hash_by_addr, mptcp_addr);
+ if (obj_data) {
+ const NMPlatformMptcpAddr *mptcp_addr2 = NMP_OBJECT_CAST_MPTCP_ADDR(obj_data->obj);
+
+ if (mptcp_addr->flags == mptcp_addr2->flags
+ && mptcp_addr->ifindex == mptcp_addr2->ifindex) {
+ /* We also want to re-add this very same address. Don't delete it. */
+ goto index_and_next;
+ }
+ }
+
+ if (keep_deleted) {
+ _LOGD("forget/leak object added by us: %s \"%s\"",
+ NMP_OBJECT_GET_CLASS(obj)->obj_type_name,
+ nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ goto index_and_next;
+ }
+
+ /* This entry is marked for deletion. Delete it. */
+ if (nm_platform_object_delete(self->platform, obj)) {
+ nm_clear_pointer(&kaddrs_arr->pdata[i], nmp_object_unref);
+ continue;
+ }
+
+ /* We failed to delete it. It's unclear what is the matter with this
+ * object. Pretend it doesn't exist (don't add it to kaddrs_idx and
+ * proceed. */
+ nm_clear_pointer(&kaddrs_arr->pdata[i], nmp_object_unref);
+ continue;
+
+index_and_next:
+ _LOGt("keep: %s \"%s\"",
+ NMP_OBJECT_GET_CLASS(obj)->obj_type_name,
+ nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ if (!kaddrs_idx) {
+ kaddrs_idx = g_hash_table_new((GHashFunc) nmp_object_id_hash,
+ (GEqualFunc) nmp_object_id_equal);
+ }
+ g_hash_table_add(kaddrs_idx, (gpointer) obj);
+ }
+ }
+
+ if (entries) {
+ for (i = 0; i < entries->len; i++) {
+ const MptcpSyncData *d = nm_g_array_index_p(entries, MptcpSyncData, i);
+ const NMPlatformMptcpAddr *mptcp_addr = NMP_OBJECT_CAST_MPTCP_ADDR(d->obj_data->obj);
+ const NMPObject *kobj;
+
+ d->obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
+
+ kobj = nm_g_hash_table_lookup(kaddrs_idx, d->obj_data->obj);
+ if (kobj && kobj->mptcp_addr.flags == mptcp_addr->flags) {
+ /* This address is already added with the right flags. We can
+ * skip it. */
+ continue;
+ }
+
+ /* Kernel actually only allows us to add a small number of addresses.
+ * Also, if we have a conflicting address on another interface, the
+ * request will be rejected.
+ *
+ * Don't try to handle that. Just attempt to add the address, and if
+ * we fail, there is nothing we can do about it. */
+ nm_platform_mptcp_addr_update(self->platform, TRUE, mptcp_addr);
+ }
+ }
+}
+
void
nmp_global_tracker_sync(NMPGlobalTracker *self, NMPObjectType obj_type, gboolean keep_deleted)
{
char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
const NMDedupMultiHeadEntry *pl_head_entry;
- NMDedupMultiIter pl_iter;
const NMPObject *plobj;
gs_unref_ptrarray GPtrArray *objs_to_delete = NULL;
TrackObjData *obj_data;
@@ -614,6 +974,8 @@ nmp_global_tracker_sync(NMPGlobalTracker *self, NMPObjectType obj_type, gboolean
pl_head_entry = nm_platform_lookup_object(self->platform, obj_type, 0);
if (pl_head_entry) {
+ NMDedupMultiIter pl_iter;
+
nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) {
obj_data = g_hash_table_lookup(self->by_obj, &plobj);
@@ -867,6 +1229,7 @@ nmp_global_tracker_new(NMPlatform *platform)
.by_obj_lst_heads[0] = C_LIST_INIT(self->by_obj_lst_heads[0]),
.by_obj_lst_heads[1] = C_LIST_INIT(self->by_obj_lst_heads[1]),
.by_obj_lst_heads[2] = C_LIST_INIT(self->by_obj_lst_heads[2]),
+ .by_obj_lst_heads[3] = C_LIST_INIT(self->by_obj_lst_heads[3]),
};
return self;
}
@@ -894,6 +1257,7 @@ nmp_global_tracker_unref(NMPGlobalTracker *self)
nm_assert(c_list_is_empty(&self->by_obj_lst_heads[0]));
nm_assert(c_list_is_empty(&self->by_obj_lst_heads[1]));
nm_assert(c_list_is_empty(&self->by_obj_lst_heads[2]));
+ nm_assert(c_list_is_empty(&self->by_obj_lst_heads[3]));
g_object_unref(self->platform);
nm_g_slice_free(self);
}
diff --git a/src/libnm-platform/nmp-global-tracker.h b/src/libnm-platform/nmp-global-tracker.h
index 61a4c1eb58..3cb45f831b 100644
--- a/src/libnm-platform/nmp-global-tracker.h
+++ b/src/libnm-platform/nmp-global-tracker.h
@@ -74,6 +74,13 @@ gboolean nmp_global_tracker_untrack_all(NMPGlobalTracker *self,
void nmp_global_tracker_sync(NMPGlobalTracker *self, NMPObjectType obj_type, gboolean keep_deleted);
+void nmp_global_tracker_sync_mptcp_addrs(NMPGlobalTracker *self,
+ gboolean reapply,
+ gboolean keep_deleted);
+
/*****************************************************************************/
+const NMPlatformMptcpAddr *nmp_global_tracker_mptcp_addr_init_for_ifindex(NMPlatformMptcpAddr *addr,
+ int ifindex);
+
#endif /* __NMP_GLOBAL_TRACKER_H__ */