diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c index e801e4fd93..f50969fb7c 100644 --- a/libnm-core/nm-setting-tc-config.c +++ b/libnm-core/nm-setting-tc-config.c @@ -154,6 +154,19 @@ nm_tc_qdisc_equal (NMTCQdisc *qdisc, NMTCQdisc *other) return TRUE; } +static guint +_nm_tc_qdisc_hash (NMTCQdisc *qdisc) +{ + NMHashState h; + + nm_hash_init (&h, 43869703); + nm_hash_update_vals (&h, + qdisc->handle, + qdisc->parent); + nm_hash_update_str0 (&h, qdisc->kind); + return nm_hash_complete (&h); +} + /** * nm_tc_qdisc_dup: * @qdisc: the #NMTCQdisc @@ -626,6 +639,38 @@ nm_tc_tfilter_equal (NMTCTfilter *tfilter, NMTCTfilter *other) return TRUE; } +static guint +_nm_tc_tfilter_hash (NMTCTfilter *tfilter) +{ + gs_free const char **names = NULL; + guint i, attr_hash; + GVariant *variant; + NMHashState h; + guint length; + + nm_hash_init (&h, 63624437); + nm_hash_update_vals (&h, + tfilter->handle, + tfilter->parent); + nm_hash_update_str0 (&h, tfilter->kind); + if (tfilter->action) { + nm_hash_update_str0 (&h, tfilter->action->kind); + names = nm_utils_strdict_get_keys (tfilter->action->attributes, TRUE, &length); + for (i = 0; i < length; i++) { + nm_hash_update_str (&h, names[i]); + variant = g_hash_table_lookup (tfilter->action->attributes, names[i]); + if (g_variant_type_is_basic (g_variant_get_type (variant))) { + /* g_variant_hash() works only for basic types, thus + * we ignore any non-basic attribute. Actions differing + * only for non-basic attributes will collide. */ + attr_hash = g_variant_hash (variant); + nm_hash_update_val (&h, attr_hash); + } + } + } + return nm_hash_complete (&h); +} + /** * nm_tc_tfilter_dup: * @tfilter: the #NMTCTfilter @@ -1137,6 +1182,55 @@ finalize (GObject *object) G_OBJECT_CLASS (nm_setting_tc_config_parent_class)->finalize (object); } +static gboolean +verify (NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingTCConfig *self = NM_SETTING_TC_CONFIG (setting); + guint i; + + if (self->qdiscs->len != 0) { + gs_unref_hashtable GHashTable *ht = NULL; + + ht = g_hash_table_new ((GHashFunc) _nm_tc_qdisc_hash, + (GEqualFunc) nm_tc_qdisc_equal); + for (i = 0; i < self->qdiscs->len; i++) { + if (!g_hash_table_add (ht, self->qdiscs->pdata[i])) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("there are duplicate TC qdiscs")); + g_prefix_error (error, + "%s.%s: ", + NM_SETTING_TC_CONFIG_SETTING_NAME, + NM_SETTING_TC_CONFIG_QDISCS); + return FALSE; + } + } + } + + if (self->tfilters->len != 0) { + gs_unref_hashtable GHashTable *ht = NULL; + + ht = g_hash_table_new ((GHashFunc) _nm_tc_tfilter_hash, + (GEqualFunc) nm_tc_tfilter_equal); + for (i = 0; i < self->tfilters->len; i++) { + if (!g_hash_table_add (ht, self->tfilters->pdata[i])) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("there are duplicate TC filters")); + g_prefix_error (error, + "%s.%s: ", + NM_SETTING_TC_CONFIG_SETTING_NAME, + NM_SETTING_TC_CONFIG_TFILTERS); + return FALSE; + } + } + } + + return TRUE; +} + static gboolean compare_property (NMSetting *setting, NMSetting *other, @@ -1502,6 +1596,7 @@ nm_setting_tc_config_class_init (NMSettingTCConfigClass *setting_class) object_class->get_property = get_property; object_class->finalize = finalize; parent_class->compare_property = compare_property; + parent_class->verify = verify; /* Properties */ diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c index 6c5c6ad97e..e71d6b7cc2 100644 --- a/libnm-core/tests/test-setting.c +++ b/libnm-core/tests/test-setting.c @@ -1440,7 +1440,7 @@ test_tc_config_tfilter (void) } static void -test_tc_config_setting (void) +test_tc_config_setting_valid (void) { gs_unref_object NMSettingTCConfig *s_tc = NULL; NMTCQdisc *qdisc1, *qdisc2; @@ -1473,6 +1473,70 @@ test_tc_config_setting (void) nm_tc_qdisc_unref (qdisc2); } +static void +test_tc_config_setting_duplicates (void) +{ + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_unref_ptrarray GPtrArray *tfilters = NULL; + NMSettingConnection *s_con; + NMConnection *con; + NMSetting *s_tc; + NMTCQdisc *qdisc; + NMTCTfilter *tfilter; + GError *error = NULL; + + con = nmtst_create_minimal_connection ("dummy", + NULL, + NM_SETTING_DUMMY_SETTING_NAME, + &s_con); + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, "dummy1", + NULL); + + s_tc = nm_setting_tc_config_new (); + nm_connection_add_setting (con, s_tc); + qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref); + tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref); + + /* 1. add duplicate qdiscs */ + qdisc = nm_utils_tc_qdisc_from_str ("handle 1234 parent fff1:1 pfifo_fast", &error); + nmtst_assert_success (qdisc, error); + g_ptr_array_add (qdiscs, qdisc); + + qdisc = nm_utils_tc_qdisc_from_str ("handle 1234 parent fff1:1 pfifo_fast", &error); + nmtst_assert_success (qdisc, error); + g_ptr_array_add (qdiscs, qdisc); + + g_object_set (s_tc, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); + nmtst_assert_connection_unnormalizable (con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* 2. make qdiscs unique */ + g_ptr_array_remove_index (qdiscs, 0); + g_object_set (s_tc, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); + nmtst_assert_connection_verifies_and_normalizable (con); + + /* 3. add duplicate filters */ + tfilter = nm_utils_tc_tfilter_from_str ("parent 1234: matchall action simple sdata Hello", &error); + nmtst_assert_success (tfilter, error); + g_ptr_array_add (tfilters, tfilter); + + tfilter = nm_utils_tc_tfilter_from_str ("parent 1234: matchall action simple sdata Hello", &error); + nmtst_assert_success (tfilter, error); + g_ptr_array_add (tfilters, tfilter); + + g_object_set (s_tc, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); + nmtst_assert_connection_unnormalizable (con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* 4. make filters unique */ + g_ptr_array_remove_index (tfilters, 0); + g_object_set (s_tc, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); + nmtst_assert_connection_verifies_and_normalizable (con); +} + static void test_tc_config_dbus (void) { @@ -1608,7 +1672,8 @@ main (int argc, char **argv) g_test_add_func ("/libnm/settings/tc_config/qdisc", test_tc_config_qdisc); g_test_add_func ("/libnm/settings/tc_config/action", test_tc_config_action); g_test_add_func ("/libnm/settings/tc_config/tfilter", test_tc_config_tfilter); - g_test_add_func ("/libnm/settings/tc_config/setting", test_tc_config_setting); + g_test_add_func ("/libnm/settings/tc_config/setting/valid", test_tc_config_setting_valid); + g_test_add_func ("/libnm/settings/tc_config/setting/duplicates", test_tc_config_setting_duplicates); g_test_add_func ("/libnm/settings/tc_config/dbus", test_tc_config_dbus); #if WITH_JSON_VALIDATION