From ce9520919d4a7e0a0693bbad181a86f5b1838d9a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 26 Jul 2024 15:17:58 +0200 Subject: [PATCH] platform: add nmp_utils_bridge_normalized_vlans_equal() Add a function to compare two arrays of NMPlatformBridgeVlan. It will be used in the next commit to compare the VLANs from platform to the ones we want to set. To compare in a performant way, the vlans need to be normalized (no duplicated VLANS, ranges into their minimal expression...). Add the function nmp_utils_bridge_vlan_normalize. --- src/libnm-platform/nm-platform-utils.c | 84 +++++++ src/libnm-platform/nm-platform-utils.h | 7 + src/libnm-platform/nm-platform.h | 7 - src/libnm-platform/nmp-base.h | 9 + src/libnm-platform/tests/test-nm-platform.c | 237 ++++++++++++++++++++ 5 files changed, 337 insertions(+), 7 deletions(-) diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c index 6f3ad05c94..00cae0c8b6 100644 --- a/src/libnm-platform/nm-platform-utils.c +++ b/src/libnm-platform/nm-platform-utils.c @@ -2275,6 +2275,90 @@ nmp_utils_lifetime_get(guint32 timestamp, /*****************************************************************************/ +static int +bridge_vlan_compare(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMPlatformBridgeVlan *vlan_a = a; + const NMPlatformBridgeVlan *vlan_b = b; + + return (int) vlan_a->vid_start - (int) vlan_b->vid_start; +} + +/** + * nmp_utils_bridge_vlan_normalize: + * @vlans: the array of VLAN ranges + * @num_vlans: the number of VLAN ranges in the array. On return, it contains + * the new number. + * + * Sort the VLAN ranges and merge those that are contiguous or overlapping.It + * must not contain invalid data such as 2 overlaping ranges with different + * flags. + */ +void +nmp_utils_bridge_vlan_normalize(NMPlatformBridgeVlan *vlans, guint *num_vlans) +{ + guint i, j; + + if (*num_vlans <= 1) + return; + + g_qsort_with_data(vlans, *num_vlans, sizeof(NMPlatformBridgeVlan), bridge_vlan_compare, NULL); + + /* Merge VLAN ranges that are contiguous or overlap */ + i = 0; + while (i < *num_vlans - 1) { + j = i + 1; + + if (vlans[j].vid_start >= vlans[i].vid_start && vlans[j].vid_start <= vlans[i].vid_end + 1 + && vlans[j].pvid == vlans[i].pvid && vlans[j].untagged == vlans[i].untagged) { + if (vlans[i].vid_end < vlans[j].vid_end) + vlans[i].vid_end = vlans[j].vid_end; + + for (; j < *num_vlans - 1; j++) + vlans[j] = vlans[j + 1]; + + *num_vlans -= 1; + } else { + i++; + } + } +} + +/** + * nmp_utils_bridge_normalized_vlans_equal: + * @vlans_a: the first array of bridge VLANs + * @num_vlans_a: the number of elements of first array + * @vlans_b: the second array of bridge VLANs + * @num_vlans_b: the number of elements of second array + * + * Given two arrays of bridge VLAN ranges, compare if they are equal, + * i.e. if they represent the same set of VLANs with the same attributes. + * The input arrays must be sorted and must not have overlapping or duplicated + * ranges. + */ +gboolean +nmp_utils_bridge_normalized_vlans_equal(NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + NMPlatformBridgeVlan *vlans_b, + guint num_vlans_b) +{ + guint i; + + if (num_vlans_a != num_vlans_b) + return FALSE; + + for (i = 0; i < num_vlans_a; i++) { + if (vlans_a[i].vid_start != vlans_b[i].vid_start || vlans_a[i].vid_end != vlans_b[i].vid_end + || vlans_a[i].pvid != vlans_b[i].pvid || vlans_a[i].untagged != vlans_b[i].untagged) { + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + static const char * _trunk_first_line(char *str) { diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h index 18fc615557..1d1f501116 100644 --- a/src/libnm-platform/nm-platform-utils.h +++ b/src/libnm-platform/nm-platform-utils.h @@ -99,4 +99,11 @@ guint32 nmp_utils_lifetime_get(guint32 timestamp, int nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) G_GNUC_NULL_TERMINATED; +void nmp_utils_bridge_vlan_normalize(NMPlatformBridgeVlan *vlans, guint *num_vlans); + +gboolean nmp_utils_bridge_normalized_vlans_equal(NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + NMPlatformBridgeVlan *vlans_b, + guint num_vlans_b); + #endif /* __NM_PLATFORM_UTILS_H__ */ diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index e5110ff117..e33be81356 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -741,13 +741,6 @@ typedef struct { gint8 trust; } NMPlatformVF; -typedef struct { - guint16 vid_start; - guint16 vid_end; - bool untagged : 1; - bool pvid : 1; -} NMPlatformBridgeVlan; - typedef struct { guint16 vlan_default_pvid_val; bool vlan_filtering_val : 1; diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index 70b5d1bc55..c7d487e23c 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -38,6 +38,15 @@ typedef enum { /*****************************************************************************/ +typedef struct { + guint16 vid_start; + guint16 vid_end; + bool untagged : 1; + bool pvid : 1; +} NMPlatformBridgeVlan; + +/*****************************************************************************/ + typedef struct { /* We don't want to include in header files, * thus create a ABI compatible version of struct ethtool_drvinfo.*/ diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c index 5fc8a5dded..04b2e6161e 100644 --- a/src/libnm-platform/tests/test-nm-platform.c +++ b/src/libnm-platform/tests/test-nm-platform.c @@ -190,6 +190,239 @@ test_nmp_link_mode_all_advertised_modes_bits(void) /*****************************************************************************/ +static void +test_nmp_utils_bridge_vlans_normalize(void) +{ + NMPlatformBridgeVlan vlans[10]; + NMPlatformBridgeVlan expect[10]; + guint vlans_len; + + /* Single one is unmodified */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans_len = 1; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 1); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + /* Not merged if flags are different */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 25, + }; + vlans[3] = (NMPlatformBridgeVlan){ + .vid_start = 26, + .vid_end = 30, + .untagged = TRUE, + }; + vlans[4] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + }; + vlans[5] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + .pvid = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + expect[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + expect[2] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 25, + }; + expect[3] = (NMPlatformBridgeVlan){ + .vid_start = 26, + .vid_end = 30, + .untagged = TRUE, + }; + expect[4] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + }; + expect[5] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + .pvid = TRUE, + }; + vlans_len = 6; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 6); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + /* Overlaping and contiguous ranges are merged */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 20, + .untagged = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 19, + .vid_end = 30, + .untagged = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 30, + .untagged = TRUE, + }; + vlans_len = 3; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 1); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 20, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 4, + .vid_end = 4, + .pvid = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 33, + .vid_end = 33, + }; + vlans[3] = (NMPlatformBridgeVlan){ + .vid_start = 100, + .vid_end = 100, + .untagged = TRUE, + }; + vlans[4] = (NMPlatformBridgeVlan){ + .vid_start = 34, + .vid_end = 40, + }; + vlans[5] = (NMPlatformBridgeVlan){ + .vid_start = 21, + .vid_end = 32, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 4, + .vid_end = 4, + .pvid = TRUE, + }; + expect[1] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 40, + }; + expect[2] = (NMPlatformBridgeVlan){ + .vid_start = 100, + .vid_end = 100, + .untagged = TRUE, + }; + vlans_len = 6; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 3); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); +} + +static void +test_nmp_utils_bridge_normalized_vlans_equal(void) +{ + NMPlatformBridgeVlan a[10]; + NMPlatformBridgeVlan b[10]; + + /* Both empty */ + g_assert(nmp_utils_bridge_normalized_vlans_equal(NULL, 0, NULL, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 0, b, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 0, NULL, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(NULL, 0, b, 0)); + + /* One empty, other not */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, NULL, 0)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(NULL, 0, a, 1)); + + /* Equal range + VLAN */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + a[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + b[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + b[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 2, b, 2)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(b, 2, a, 2)); + + /* Different flag */ + b[1].pvid = FALSE; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 2, b, 2)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 2, a, 2)); + + /* Different ranges */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 30, + .untagged = TRUE, + }; + b[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 29, + .untagged = TRUE, + }; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, b, 1)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 1, a, 1)); + + b[0].vid_start = 2; + b[0].vid_end = 30; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, b, 1)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 1, a, 1)); +} + +/*****************************************************************************/ + static void test_nmpclass_consistency(void) { @@ -252,6 +485,10 @@ main(int argc, char **argv) g_test_add_func("/nm-platform/test_nmp_link_mode_all_advertised_modes_bits", test_nmp_link_mode_all_advertised_modes_bits); g_test_add_func("/nm-platform/test_nmpclass_consistency", test_nmpclass_consistency); + g_test_add_func("/nm-platform/test_nmp_utils_bridge_vlans_normalize", + test_nmp_utils_bridge_vlans_normalize); + g_test_add_func("/nm-platform/nmp-utils-bridge-vlans-equal", + test_nmp_utils_bridge_normalized_vlans_equal); return g_test_run(); }