diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c index 6f3ad05c9..3f70f5fe7 100644 --- a/src/libnm-platform/nm-platform-utils.c +++ b/src/libnm-platform/nm-platform-utils.c @@ -2275,6 +2275,89 @@ 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 overlapping ranges with different + * flags. + */ +void +nmp_utils_bridge_vlan_normalize(NMPlatformBridgeVlan *vlans, guint *num_vlans) +{ + guint i; + + 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) { + guint j = i + 1; + gboolean can_merge = vlans[j].vid_start <= vlans[i].vid_end + 1 + && vlans[j].pvid == vlans[i].pvid + && vlans[j].untagged == vlans[i].untagged; + + if (can_merge) { + vlans[i].vid_end = NM_MAX(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 normalized (sorted and without overlapping or + * duplicated ranges). Normalize with nmp_utils_bridge_vlan_normalize(). + */ +gboolean +nmp_utils_bridge_normalized_vlans_equal(const NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + const 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 18fc61555..96ac22ef3 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(const NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + const 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 e5110ff11..e33be8135 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 70b5d1bc5..c7d487e23 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 5fc8a5dde..377078750 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)); + + /* Overlapping 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(); }