/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2017 - 2020 Red Hat, Inc. */ #include "libnm-core-impl/nm-default-libnm-core.h" #include "nm-setting-ovs-external-ids.h" #include "nm-setting-private.h" #include "nm-utils-private.h" #include "nm-connection-private.h" #define MAX_NUM_KEYS 256 /*****************************************************************************/ /** * SECTION:nm-setting-ovs-external-ids * @short_description: External-IDs for OVS database * * The #NMSettingOvsExternalIDs object is a #NMSetting subclass that allow to * configure external ids for OVS. **/ /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingOvsExternalIDs, PROP_DATA, ); typedef struct { GHashTable * data; const char **data_keys; } NMSettingOvsExternalIDsPrivate; /** * NMSettingOvsExternalIDs: * * OVS External IDs Settings */ struct _NMSettingOvsExternalIDs { NMSetting parent; NMSettingOvsExternalIDsPrivate _priv; }; struct _NMSettingOvsExternalIDsClass { NMSettingClass parent; }; G_DEFINE_TYPE(NMSettingOvsExternalIDs, nm_setting_ovs_external_ids, NM_TYPE_SETTING) #define NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMSettingOvsExternalIDs, NM_IS_SETTING_OVS_EXTERNAL_IDS) /*****************************************************************************/ static gboolean _exid_key_char_is_regular(char ch) { /* allow words of printable characters, plus some * special characters, for example to support base64 encoding. */ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || NM_IN_SET(ch, '-', '_', '+', '/', '=', '.'); } /** * nm_setting_ovs_external_ids_check_key: * @key: (allow-none): the key to check * @error: a #GError, %NULL to ignore. * * Checks whether @key is a valid key for OVS' external-ids. * This means, the key cannot be %NULL, not too large and valid ASCII. * Also, only digits and numbers are allowed with a few special * characters. They key must also not start with "NM.". * * Since: 1.30 * * Returns: %TRUE if @key is a valid user data key. */ gboolean nm_setting_ovs_external_ids_check_key(const char *key, GError **error) { gsize len; g_return_val_if_fail(!error || !*error, FALSE); if (!key || !key[0]) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("missing key")); return FALSE; } len = strlen(key); if (len > 255u) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("key is too long")); return FALSE; } if (!g_utf8_validate(key, len, NULL)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("key must be UTF8")); return FALSE; } if (!NM_STRCHAR_ALL(key, ch, _exid_key_char_is_regular(ch))) { /* Probably OVS is more forgiving about what makes a valid key for * an external-id. However, we are strict (at least, for now). */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("key contains invalid characters")); return FALSE; } if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX)) { /* these keys are reserved. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("key cannot start with \"NM.\"")); return FALSE; } return TRUE; } /** * nm_setting_ovs_external_ids_check_val: * @val: (allow-none): the value to check * @error: a #GError, %NULL to ignore. * * Checks whether @val is a valid user data value. This means, * value is not %NULL, not too large and valid UTF-8. * * Since: 1.30 * * Returns: %TRUE if @val is a valid user data value. */ gboolean nm_setting_ovs_external_ids_check_val(const char *val, GError **error) { gsize len; g_return_val_if_fail(!error || !*error, FALSE); if (!val) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("value is missing")); return FALSE; } len = strlen(val); if (len > (8u * 1024u)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("value is too large")); return FALSE; } if (!g_utf8_validate(val, len, NULL)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("value is not valid UTF8")); return FALSE; } return TRUE; } /*****************************************************************************/ static GHashTable * _create_data_hash(void) { return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); } GHashTable * _nm_setting_ovs_external_ids_get_data(NMSettingOvsExternalIDs *self) { return NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self)->data; } /** * nm_setting_ovs_external_ids_get_data_keys: * @setting: the #NMSettingOvsExternalIDs * @out_len: (out): the length of the returned array * * Returns: (array length=out_len) (transfer none): a * %NULL-terminated array containing each key from the table. **/ const char *const * nm_setting_ovs_external_ids_get_data_keys(NMSettingOvsExternalIDs *setting, guint *out_len) { NMSettingOvsExternalIDs * self = setting; NMSettingOvsExternalIDsPrivate *priv; g_return_val_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self), NULL); priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); if (priv->data_keys) { NM_SET_OUT(out_len, g_hash_table_size(priv->data)); return priv->data_keys; } priv->data_keys = nm_utils_strdict_get_keys(priv->data, TRUE, out_len); /* don't return %NULL, but hijack the @data_keys fields as a pseudo * empty strv array. */ return priv->data_keys ?: ((const char **) &priv->data_keys); } /*****************************************************************************/ /** * nm_setting_ovs_external_ids_get_data: * @setting: the #NMSettingOvsExternalIDs instance * @key: the external-id to lookup * * Since: 1.30 * * Returns: (transfer none): the value associated with @key or %NULL if no such * value exists. */ const char * nm_setting_ovs_external_ids_get_data(NMSettingOvsExternalIDs *setting, const char *key) { NMSettingOvsExternalIDs * self = setting; NMSettingOvsExternalIDsPrivate *priv; g_return_val_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self), NULL); g_return_val_if_fail(key, NULL); priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); if (!priv->data) return NULL; return g_hash_table_lookup(priv->data, key); } /** * nm_setting_ovs_external_ids_set_data: * @setting: the #NMSettingOvsExternalIDs instance * @key: the key to set * @val: (allow-none): the value to set or %NULL to clear a key. * * Since: 1.30 */ void nm_setting_ovs_external_ids_set_data(NMSettingOvsExternalIDs *setting, const char * key, const char * val) { NMSettingOvsExternalIDs * self = setting; NMSettingOvsExternalIDsPrivate *priv; g_return_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self)); priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); if (!val) { if (priv->data && g_hash_table_remove(priv->data, key)) goto out_changed; return; } if (priv->data) { const char *val2; if (g_hash_table_lookup_extended(priv->data, key, NULL, (gpointer *) &val2)) { if (nm_streq(val, val2)) return; } } else priv->data = _create_data_hash(); g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); out_changed: nm_clear_g_free(&priv->data_keys); _notify(self, PROP_DATA); } /*****************************************************************************/ static gboolean verify(NMSetting *setting, NMConnection *connection, GError **error) { NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(setting); NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); if (priv->data) { gs_free_error GError *local = NULL; GHashTableIter iter; const char * key; const char * val; g_hash_table_iter_init(&iter, priv->data); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { if (!nm_setting_ovs_external_ids_check_key(key, &local)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, _("invalid key \"%s\": %s"), key, local->message); } else if (!nm_setting_ovs_external_ids_check_val(val, &local)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, _("invalid value for \"%s\": %s"), key, local->message); } else continue; g_prefix_error(error, "%s.%s: ", NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, NM_SETTING_OVS_EXTERNAL_IDS_DATA); return FALSE; } } if (priv->data && g_hash_table_size(priv->data) > MAX_NUM_KEYS) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("maximum number of user data entries reached (%u instead of %u)"), g_hash_table_size(priv->data), (unsigned) MAX_NUM_KEYS); g_prefix_error(error, "%s.%s: ", NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, NM_SETTING_OVS_EXTERNAL_IDS_DATA); return FALSE; } if (connection) { NMSettingConnection *s_con; const char * type; const char * slave_type; type = nm_connection_get_connection_type(connection); if (!type) { NMSetting *s_base; s_base = _nm_connection_find_base_type_setting(connection); if (s_base) type = nm_setting_get_name(s_base); } if (NM_IN_STRSET(type, NM_SETTING_OVS_BRIDGE_SETTING_NAME, NM_SETTING_OVS_PORT_SETTING_NAME, NM_SETTING_OVS_INTERFACE_SETTING_NAME)) goto connection_type_is_good; if ((s_con = nm_connection_get_setting_connection(connection)) && _nm_connection_detect_slave_type_full(s_con, connection, &slave_type, NULL, NULL, NULL, NULL) && nm_streq0(slave_type, NM_SETTING_OVS_PORT_SETTING_NAME)) goto connection_type_is_good; g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("OVS external IDs can only be added to a profile of type OVS " "bridge/port/interface or to OVS system interface")); return FALSE; } connection_type_is_good: return TRUE; } static NMTernary compare_property(const NMSettInfoSetting *sett_info, guint property_idx, NMConnection * con_a, NMSetting * set_a, NMConnection * con_b, NMSetting * set_b, NMSettingCompareFlags flags) { NMSettingOvsExternalIDsPrivate *priv; NMSettingOvsExternalIDsPrivate *pri2; if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)) { if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) return NM_TERNARY_DEFAULT; if (!set_b) return TRUE; priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(NM_SETTING_OVS_EXTERNAL_IDS(set_a)); pri2 = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(NM_SETTING_OVS_EXTERNAL_IDS(set_b)); return nm_utils_hashtable_equal(priv->data, pri2->data, TRUE, g_str_equal); } return NM_SETTING_CLASS(nm_setting_ovs_external_ids_parent_class) ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); } /*****************************************************************************/ static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); GHashTableIter iter; GHashTable * data; const char * key; const char * val; switch (prop_id) { case PROP_DATA: data = _create_data_hash(); if (priv->data) { g_hash_table_iter_init(&iter, priv->data); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) g_hash_table_insert(data, g_strdup(key), g_strdup(val)); } g_value_take_boxed(value, data); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); switch (prop_id) { case PROP_DATA: { gs_unref_hashtable GHashTable *old = NULL; GHashTableIter iter; GHashTable * data; const char * key; const char * val; nm_clear_g_free(&priv->data_keys); old = g_steal_pointer(&priv->data); data = g_value_get_boxed(value); if (nm_g_hash_table_size(data) <= 0) return; priv->data = _create_data_hash(); g_hash_table_iter_init(&iter, data); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_setting_ovs_external_ids_init(NMSettingOvsExternalIDs *self) {} /** * nm_setting_ovs_external_ids_new: * * Creates a new #NMSettingOvsExternalIDs object with default values. * * Returns: (transfer full) (type NMSettingOvsExternalIDs): the new empty * #NMSettingOvsExternalIDs object * * Since: 1.30 */ NMSetting * nm_setting_ovs_external_ids_new(void) { return g_object_new(NM_TYPE_SETTING_OVS_EXTERNAL_IDS, NULL); } static void finalize(GObject *object) { NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); g_free(priv->data_keys); if (priv->data) g_hash_table_unref(priv->data); G_OBJECT_CLASS(nm_setting_ovs_external_ids_parent_class)->finalize(object); } static void nm_setting_ovs_external_ids_class_init(NMSettingOvsExternalIDsClass *klass) { GObjectClass * object_class = G_OBJECT_CLASS(klass); NMSettingClass *setting_class = NM_SETTING_CLASS(klass); GArray * properties_override = _nm_sett_info_property_override_create_array(); object_class->get_property = get_property; object_class->set_property = set_property; object_class->finalize = finalize; setting_class->compare_property = compare_property; setting_class->verify = verify; /** * NMSettingOvsExternalIDs:data: (type GHashTable(utf8,utf8)) * * A dictionary of key/value pairs with exernal-ids for OVS. * * Since: 1.30 **/ obj_properties[PROP_DATA] = g_param_spec_boxed(NM_SETTING_OVS_EXTERNAL_IDS_DATA, "", "", G_TYPE_HASH_TABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); _nm_properties_override_gobj(properties_override, obj_properties[PROP_DATA], &nm_sett_info_propert_type_strdict); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit_full(setting_class, NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, NULL, properties_override); }