/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "libnm-core/nm-default-libnm-core.h" #include "nm-setting-tc-config.h" #include #include "nm-setting-private.h" /** * SECTION:nm-setting-tc-config * @short_description: Describes connection properties for the Linux Traffic Control * @include: nm-setting-tc-config.h **/ /*****************************************************************************/ G_DEFINE_BOXED_TYPE(NMTCQdisc, nm_tc_qdisc, nm_tc_qdisc_dup, nm_tc_qdisc_unref) struct NMTCQdisc { guint refcount; char * kind; guint32 handle; guint32 parent; GHashTable *attributes; }; /** * nm_tc_qdisc_new: * @kind: name of the queueing discipline * @parent: the parent queueing discipline * @error: location to store error, or %NULL * * Creates a new #NMTCQdisc object. * * Returns: (transfer full): the new #NMTCQdisc object, or %NULL on error * * Since: 1.12 **/ NMTCQdisc * nm_tc_qdisc_new(const char *kind, guint32 parent, GError **error) { NMTCQdisc *qdisc; if (!kind || !*kind) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("kind is missing")); return NULL; } if (strchr(kind, ' ') || strchr(kind, '\t')) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("'%s' is not a valid kind"), kind); return NULL; } if (!parent) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("parent handle missing")); return NULL; } qdisc = g_slice_new0(NMTCQdisc); qdisc->refcount = 1; qdisc->kind = g_strdup(kind); qdisc->parent = parent; return qdisc; } /** * nm_tc_qdisc_ref: * @qdisc: the #NMTCQdisc * * Increases the reference count of the object. * * Since: 1.12 **/ void nm_tc_qdisc_ref(NMTCQdisc *qdisc) { g_return_if_fail(qdisc != NULL); g_return_if_fail(qdisc->refcount > 0); qdisc->refcount++; } /** * nm_tc_qdisc_unref: * @qdisc: the #NMTCQdisc * * Decreases the reference count of the object. If the reference count * reaches zero, the object will be destroyed. * * Since: 1.12 **/ void nm_tc_qdisc_unref(NMTCQdisc *qdisc) { g_return_if_fail(qdisc != NULL); g_return_if_fail(qdisc->refcount > 0); qdisc->refcount--; if (qdisc->refcount == 0) { g_free(qdisc->kind); if (qdisc->attributes) g_hash_table_unref(qdisc->attributes); g_slice_free(NMTCQdisc, qdisc); } } /** * nm_tc_qdisc_equal: * @qdisc: the #NMTCQdisc * @other: the #NMTCQdisc to compare @qdisc to. * * Determines if two #NMTCQdisc objects contain the same kind, * handle * and parent. * * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. * * Since: 1.12 **/ gboolean nm_tc_qdisc_equal(NMTCQdisc *qdisc, NMTCQdisc *other) { GHashTableIter iter; const char * key; GVariant * value, *value2; guint n; g_return_val_if_fail(qdisc != NULL, FALSE); g_return_val_if_fail(qdisc->refcount > 0, FALSE); g_return_val_if_fail(other != NULL, FALSE); g_return_val_if_fail(other->refcount > 0, FALSE); if (qdisc->handle != other->handle || qdisc->parent != other->parent || g_strcmp0(qdisc->kind, other->kind) != 0) return FALSE; n = qdisc->attributes ? g_hash_table_size(qdisc->attributes) : 0; if (n != (other->attributes ? g_hash_table_size(other->attributes) : 0)) return FALSE; if (n) { g_hash_table_iter_init(&iter, qdisc->attributes); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { value2 = g_hash_table_lookup(other->attributes, key); if (!value2) return FALSE; if (!g_variant_equal(value, value2)) return FALSE; } } return TRUE; } static guint _nm_tc_qdisc_hash(NMTCQdisc *qdisc) { NMUtilsNamedValue attrs_static[30]; gs_free NMUtilsNamedValue *attrs_free = NULL; const NMUtilsNamedValue * attrs; NMHashState h; guint length; guint i; attrs = nm_utils_named_values_from_strdict(qdisc->attributes, &length, attrs_static, &attrs_free); nm_hash_init(&h, 43869703); nm_hash_update_vals(&h, qdisc->handle, qdisc->parent, length); nm_hash_update_str0(&h, qdisc->kind); for (i = 0; i < length; i++) { const char * key = attrs[i].name; GVariant * variant = attrs[i].value_ptr; const GVariantType *vtype; vtype = g_variant_get_type(variant); nm_hash_update_str(&h, key); nm_hash_update_str(&h, (const char *) vtype); if (g_variant_type_is_basic(vtype)) nm_hash_update_val(&h, g_variant_hash(variant)); } return nm_hash_complete(&h); } /** * nm_tc_qdisc_dup: * @qdisc: the #NMTCQdisc * * Creates a copy of @qdisc * * Returns: (transfer full): a copy of @qdisc * * Since: 1.12 **/ NMTCQdisc * nm_tc_qdisc_dup(NMTCQdisc *qdisc) { NMTCQdisc *copy; g_return_val_if_fail(qdisc != NULL, NULL); g_return_val_if_fail(qdisc->refcount > 0, NULL); copy = nm_tc_qdisc_new(qdisc->kind, qdisc->parent, NULL); nm_tc_qdisc_set_handle(copy, qdisc->handle); if (qdisc->attributes) { GHashTableIter iter; const char * key; GVariant * value; g_hash_table_iter_init(&iter, qdisc->attributes); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) nm_tc_qdisc_set_attribute(copy, key, value); } return copy; } /** * nm_tc_qdisc_get_kind: * @qdisc: the #NMTCQdisc * * Returns: * * Since: 1.12 **/ const char * nm_tc_qdisc_get_kind(NMTCQdisc *qdisc) { g_return_val_if_fail(qdisc != NULL, NULL); g_return_val_if_fail(qdisc->refcount > 0, NULL); return qdisc->kind; } /** * nm_tc_qdisc_get_handle: * @qdisc: the #NMTCQdisc * * Returns: the queueing discipline handle * * Since: 1.12 **/ guint32 nm_tc_qdisc_get_handle(NMTCQdisc *qdisc) { g_return_val_if_fail(qdisc != NULL, TC_H_UNSPEC); g_return_val_if_fail(qdisc->refcount > 0, TC_H_UNSPEC); return qdisc->handle; } /** * nm_tc_qdisc_set_handle: * @qdisc: the #NMTCQdisc * @handle: the queueing discipline handle * * Sets the queueing discipline handle. * * Since: 1.12 **/ void nm_tc_qdisc_set_handle(NMTCQdisc *qdisc, guint32 handle) { g_return_if_fail(qdisc != NULL); g_return_if_fail(qdisc->refcount > 0); qdisc->handle = handle; } /** * nm_tc_qdisc_get_parent: * @qdisc: the #NMTCQdisc * * Returns: the parent class * * Since: 1.12 **/ guint32 nm_tc_qdisc_get_parent(NMTCQdisc *qdisc) { g_return_val_if_fail(qdisc != NULL, TC_H_UNSPEC); g_return_val_if_fail(qdisc->refcount > 0, TC_H_UNSPEC); return qdisc->parent; } /** * nm_tc_qdisc_get_attribute_names: * @qdisc: the #NMTCQdisc * * Gets an array of attribute names defined on @qdisc. * * Returns: (transfer container): a %NULL-terminated array of attribute names * or %NULL if no attributes are set. * * Since: 1.18 **/ const char ** nm_tc_qdisc_get_attribute_names(NMTCQdisc *qdisc) { g_return_val_if_fail(qdisc, NULL); return nm_utils_strdict_get_keys(qdisc->attributes, TRUE, NULL); } GHashTable * _nm_tc_qdisc_get_attributes(NMTCQdisc *qdisc) { nm_assert(qdisc); return qdisc->attributes; } /** * nm_tc_qdisc_get_attribute: * @qdisc: the #NMTCQdisc * @name: the name of an qdisc attribute * * Gets the value of the attribute with name @name on @qdisc * * Returns: (transfer none): the value of the attribute with name @name on * @qdisc, or %NULL if @qdisc has no such attribute. * * Since: 1.18 **/ GVariant * nm_tc_qdisc_get_attribute(NMTCQdisc *qdisc, const char *name) { g_return_val_if_fail(qdisc != NULL, NULL); g_return_val_if_fail(name != NULL && *name != '\0', NULL); if (qdisc->attributes) return g_hash_table_lookup(qdisc->attributes, name); else return NULL; } /** * nm_tc_qdisc_set_attribute: * @qdisc: the #NMTCQdisc * @name: the name of an qdisc attribute * @value: (transfer none) (allow-none): the value * * Sets or clears the named attribute on @qdisc to the given value. * * Since: 1.18 **/ void nm_tc_qdisc_set_attribute(NMTCQdisc *qdisc, const char *name, GVariant *value) { g_return_if_fail(qdisc != NULL); g_return_if_fail(name != NULL && *name != '\0'); g_return_if_fail(strcmp(name, "kind") != 0); if (!qdisc->attributes) { qdisc->attributes = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); } if (value) g_hash_table_insert(qdisc->attributes, g_strdup(name), g_variant_ref_sink(value)); else g_hash_table_remove(qdisc->attributes, name); } /*****************************************************************************/ G_DEFINE_BOXED_TYPE(NMTCAction, nm_tc_action, nm_tc_action_dup, nm_tc_action_unref) struct NMTCAction { guint refcount; char *kind; GHashTable *attributes; }; /** * nm_tc_action_new: * @kind: name of the queueing discipline * @error: location to store error, or %NULL * * Creates a new #NMTCAction object. * * Returns: (transfer full): the new #NMTCAction object, or %NULL on error * * Since: 1.12 **/ NMTCAction * nm_tc_action_new(const char *kind, GError **error) { NMTCAction *action; if (!kind || !*kind) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("kind is missing")); return NULL; } if (strchr(kind, ' ') || strchr(kind, '\t')) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("'%s' is not a valid kind"), kind); return NULL; } action = g_slice_new0(NMTCAction); action->refcount = 1; action->kind = g_strdup(kind); return action; } /** * nm_tc_action_ref: * @action: the #NMTCAction * * Increases the reference count of the object. * * Since: 1.12 **/ void nm_tc_action_ref(NMTCAction *action) { g_return_if_fail(action != NULL); g_return_if_fail(action->refcount > 0); action->refcount++; } /** * nm_tc_action_unref: * @action: the #NMTCAction * * Decreases the reference count of the object. If the reference count * reaches zero, the object will be destroyed. * * Since: 1.12 **/ void nm_tc_action_unref(NMTCAction *action) { g_return_if_fail(action != NULL); g_return_if_fail(action->refcount > 0); action->refcount--; if (action->refcount == 0) { g_free(action->kind); if (action->attributes) g_hash_table_unref(action->attributes); g_slice_free(NMTCAction, action); } } /** * nm_tc_action_equal: * @action: the #NMTCAction * @other: the #NMTCAction to compare @action to. * * Determines if two #NMTCAction objects contain the same kind, family, * handle, parent and info. * * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. * * Since: 1.12 **/ gboolean nm_tc_action_equal(NMTCAction *action, NMTCAction *other) { GHashTableIter iter; const char * key; GVariant * value, *value2; guint n; g_return_val_if_fail(!action || action->refcount > 0, FALSE); g_return_val_if_fail(!other || other->refcount > 0, FALSE); if (action == other) return TRUE; if (!action || !other) return FALSE; if (g_strcmp0(action->kind, other->kind) != 0) return FALSE; n = action->attributes ? g_hash_table_size(action->attributes) : 0; if (n != (other->attributes ? g_hash_table_size(other->attributes) : 0)) return FALSE; if (n) { g_hash_table_iter_init(&iter, action->attributes); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { value2 = g_hash_table_lookup(other->attributes, key); if (!value2) return FALSE; if (!g_variant_equal(value, value2)) return FALSE; } } return TRUE; } /** * nm_tc_action_dup: * @action: the #NMTCAction * * Creates a copy of @action * * Returns: (transfer full): a copy of @action * * Since: 1.12 **/ NMTCAction * nm_tc_action_dup(NMTCAction *action) { NMTCAction *copy; g_return_val_if_fail(action != NULL, NULL); g_return_val_if_fail(action->refcount > 0, NULL); copy = nm_tc_action_new(action->kind, NULL); if (action->attributes) { GHashTableIter iter; const char * key; GVariant * value; g_hash_table_iter_init(&iter, action->attributes); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) nm_tc_action_set_attribute(copy, key, value); } return copy; } /** * nm_tc_action_get_kind: * @action: the #NMTCAction * * Returns: * * Since: 1.12 **/ const char * nm_tc_action_get_kind(NMTCAction *action) { g_return_val_if_fail(action != NULL, NULL); g_return_val_if_fail(action->refcount > 0, NULL); return action->kind; } /** * nm_tc_action_get_attribute_names: * @action: the #NMTCAction * * Gets an array of attribute names defined on @action. * * Returns: (transfer full): a %NULL-terminated array of attribute names, * * Since: 1.12 **/ char ** nm_tc_action_get_attribute_names(NMTCAction *action) { const char **names; g_return_val_if_fail(action, NULL); names = nm_utils_strdict_get_keys(action->attributes, TRUE, NULL); return nm_utils_strv_make_deep_copied_nonnull(names); } GHashTable * _nm_tc_action_get_attributes(NMTCAction *action) { nm_assert(action); return action->attributes; } /** * nm_tc_action_get_attribute: * @action: the #NMTCAction * @name: the name of an action attribute * * Gets the value of the attribute with name @name on @action * * Returns: (transfer none): the value of the attribute with name @name on * @action, or %NULL if @action has no such attribute. * * Since: 1.12 **/ GVariant * nm_tc_action_get_attribute(NMTCAction *action, const char *name) { g_return_val_if_fail(action != NULL, NULL); g_return_val_if_fail(name != NULL && *name != '\0', NULL); if (action->attributes) return g_hash_table_lookup(action->attributes, name); else return NULL; } /** * nm_tc_action_set_attribute: * @action: the #NMTCAction * @name: the name of an action attribute * @value: (transfer none) (allow-none): the value * * Sets or clears the named attribute on @action to the given value. * * Since: 1.12 **/ void nm_tc_action_set_attribute(NMTCAction *action, const char *name, GVariant *value) { g_return_if_fail(action != NULL); g_return_if_fail(name != NULL && *name != '\0'); g_return_if_fail(strcmp(name, "kind") != 0); if (!action->attributes) { action->attributes = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); } if (value) g_hash_table_insert(action->attributes, g_strdup(name), g_variant_ref_sink(value)); else g_hash_table_remove(action->attributes, name); } /*****************************************************************************/ G_DEFINE_BOXED_TYPE(NMTCTfilter, nm_tc_tfilter, nm_tc_tfilter_dup, nm_tc_tfilter_unref) struct NMTCTfilter { guint refcount; char * kind; guint32 handle; guint32 parent; NMTCAction *action; }; /** * nm_tc_tfilter_new: * @kind: name of the queueing discipline * @parent: the parent queueing discipline * @error: location to store error, or %NULL * * Creates a new #NMTCTfilter object. * * Returns: (transfer full): the new #NMTCTfilter object, or %NULL on error * * Since: 1.12 **/ NMTCTfilter * nm_tc_tfilter_new(const char *kind, guint32 parent, GError **error) { NMTCTfilter *tfilter; if (!kind || !*kind) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("kind is missing")); return NULL; } if (strchr(kind, ' ') || strchr(kind, '\t')) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("'%s' is not a valid kind"), kind); return NULL; } if (!parent) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("parent handle missing")); return NULL; } tfilter = g_slice_new0(NMTCTfilter); tfilter->refcount = 1; tfilter->kind = g_strdup(kind); tfilter->parent = parent; return tfilter; } /** * nm_tc_tfilter_ref: * @tfilter: the #NMTCTfilter * * Increases the reference count of the object. * * Since: 1.12 **/ void nm_tc_tfilter_ref(NMTCTfilter *tfilter) { g_return_if_fail(tfilter != NULL); g_return_if_fail(tfilter->refcount > 0); tfilter->refcount++; } /** * nm_tc_tfilter_unref: * @tfilter: the #NMTCTfilter * * Decreases the reference count of the object. If the reference count * reaches zero, the object will be destroyed. * * Since: 1.12 **/ void nm_tc_tfilter_unref(NMTCTfilter *tfilter) { g_return_if_fail(tfilter != NULL); g_return_if_fail(tfilter->refcount > 0); tfilter->refcount--; if (tfilter->refcount == 0) { g_free(tfilter->kind); if (tfilter->action) nm_tc_action_unref(tfilter->action); g_slice_free(NMTCTfilter, tfilter); } } /** * nm_tc_tfilter_equal: * @tfilter: the #NMTCTfilter * @other: the #NMTCTfilter to compare @tfilter to. * * Determines if two #NMTCTfilter objects contain the same kind, family, * handle, parent and info. * * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. * * Since: 1.12 **/ gboolean nm_tc_tfilter_equal(NMTCTfilter *tfilter, NMTCTfilter *other) { g_return_val_if_fail(tfilter != NULL, FALSE); g_return_val_if_fail(tfilter->refcount > 0, FALSE); g_return_val_if_fail(other != NULL, FALSE); g_return_val_if_fail(other->refcount > 0, FALSE); if (tfilter->handle != other->handle || tfilter->parent != other->parent || g_strcmp0(tfilter->kind, other->kind) != 0 || !nm_tc_action_equal(tfilter->action, other->action)) return FALSE; return TRUE; } static guint _nm_tc_tfilter_hash(NMTCTfilter *tfilter) { NMHashState h; nm_hash_init(&h, 63624437); nm_hash_update_vals(&h, tfilter->handle, tfilter->parent); nm_hash_update_str0(&h, tfilter->kind); if (tfilter->action) { gs_free NMUtilsNamedValue *attrs_free = NULL; NMUtilsNamedValue attrs_static[30]; const NMUtilsNamedValue * attrs; guint length; guint i; nm_hash_update_str0(&h, tfilter->action->kind); attrs = nm_utils_named_values_from_strdict(tfilter->action->attributes, &length, attrs_static, &attrs_free); for (i = 0; i < length; i++) { GVariant *variant = attrs[i].value_ptr; nm_hash_update_str(&h, attrs[i].name); if (g_variant_type_is_basic(g_variant_get_type(variant))) { guint attr_hash; /* 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 * * Creates a copy of @tfilter * * Returns: (transfer full): a copy of @tfilter * * Since: 1.12 **/ NMTCTfilter * nm_tc_tfilter_dup(NMTCTfilter *tfilter) { NMTCTfilter *copy; g_return_val_if_fail(tfilter != NULL, NULL); g_return_val_if_fail(tfilter->refcount > 0, NULL); copy = nm_tc_tfilter_new(tfilter->kind, tfilter->parent, NULL); nm_tc_tfilter_set_handle(copy, tfilter->handle); nm_tc_tfilter_set_action(copy, tfilter->action); return copy; } /** * nm_tc_tfilter_get_kind: * @tfilter: the #NMTCTfilter * * Returns: * * Since: 1.12 **/ const char * nm_tc_tfilter_get_kind(NMTCTfilter *tfilter) { g_return_val_if_fail(tfilter != NULL, NULL); g_return_val_if_fail(tfilter->refcount > 0, NULL); return tfilter->kind; } /** * nm_tc_tfilter_get_handle: * @tfilter: the #NMTCTfilter * * Returns: the queueing discipline handle * * Since: 1.12 **/ guint32 nm_tc_tfilter_get_handle(NMTCTfilter *tfilter) { g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); return tfilter->handle; } /** * nm_tc_tfilter_set_handle: * @tfilter: the #NMTCTfilter * @handle: the queueing discipline handle * * Sets the queueing discipline handle. * * Since: 1.12 **/ void nm_tc_tfilter_set_handle(NMTCTfilter *tfilter, guint32 handle) { g_return_if_fail(tfilter != NULL); g_return_if_fail(tfilter->refcount > 0); tfilter->handle = handle; } /** * nm_tc_tfilter_get_parent: * @tfilter: the #NMTCTfilter * * Returns: the parent class * * Since: 1.12 **/ guint32 nm_tc_tfilter_get_parent(NMTCTfilter *tfilter) { g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); return tfilter->parent; } /** * nm_tc_tfilter_get_action: * @tfilter: the #NMTCTfilter * * Returns: the action associated with a traffic filter. * * Since: 1.12 **/ NMTCAction * nm_tc_tfilter_get_action(NMTCTfilter *tfilter) { g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); if (tfilter->action == NULL) return NULL; return tfilter->action; } /** * nm_tc_tfilter_set_action: * @tfilter: the #NMTCTfilter * @action: the action object * * Sets the action associated with a traffic filter. * * Since: 1.12 **/ void nm_tc_tfilter_set_action(NMTCTfilter *tfilter, NMTCAction *action) { g_return_if_fail(tfilter != NULL); g_return_if_fail(tfilter->refcount > 0); if (action) nm_tc_action_ref(action); if (tfilter->action) nm_tc_action_unref(tfilter->action); tfilter->action = action; } /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingTCConfig, PROP_QDISCS, PROP_TFILTERS, ); /** * NMSettingTCConfig: * * Linux Traffic Control Settings * * Since: 1.12 */ struct _NMSettingTCConfig { NMSetting parent; GPtrArray *qdiscs; GPtrArray *tfilters; }; struct _NMSettingTCConfigClass { NMSettingClass parent; }; G_DEFINE_TYPE(NMSettingTCConfig, nm_setting_tc_config, NM_TYPE_SETTING) /*****************************************************************************/ /** * nm_setting_tc_config_get_num_qdiscs: * @setting: the #NMSettingTCConfig * * Returns: the number of configured queueing disciplines * * Since: 1.12 **/ guint nm_setting_tc_config_get_num_qdiscs(NMSettingTCConfig *self) { g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), 0); return self->qdiscs->len; } /** * nm_setting_tc_config_get_qdisc: * @setting: the #NMSettingTCConfig * @idx: index number of the qdisc to return * * Returns: (transfer none): the qdisc at index @idx * * Since: 1.12 **/ NMTCQdisc * nm_setting_tc_config_get_qdisc(NMSettingTCConfig *self, guint idx) { g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), NULL); g_return_val_if_fail(idx < self->qdiscs->len, NULL); return self->qdiscs->pdata[idx]; } /** * nm_setting_tc_config_add_qdisc: * @setting: the #NMSettingTCConfig * @qdisc: the qdisc to add * * Appends a new qdisc and associated information to the setting. The * given qdisc is duplicated internally and is not changed by this function. * If an identical qdisc (considering attributes as well) already exists, the * qdisc is not added and the function returns %FALSE. * * Returns: %TRUE if the qdisc was added; %FALSE if the qdisc was already known. * * Since: 1.12 **/ gboolean nm_setting_tc_config_add_qdisc(NMSettingTCConfig *self, NMTCQdisc *qdisc) { guint i; g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); g_return_val_if_fail(qdisc != NULL, FALSE); for (i = 0; i < self->qdiscs->len; i++) { if (nm_tc_qdisc_equal(self->qdiscs->pdata[i], qdisc)) return FALSE; } g_ptr_array_add(self->qdiscs, nm_tc_qdisc_dup(qdisc)); _notify(self, PROP_QDISCS); return TRUE; } /** * nm_setting_tc_config_remove_qdisc: * @setting: the #NMSettingTCConfig * @idx: index number of the qdisc * * Removes the qdisc at index @idx. * * Since: 1.12 **/ void nm_setting_tc_config_remove_qdisc(NMSettingTCConfig *self, guint idx) { g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); g_return_if_fail(idx < self->qdiscs->len); g_ptr_array_remove_index(self->qdiscs, idx); _notify(self, PROP_QDISCS); } /** * nm_setting_tc_config_remove_qdisc_by_value: * @setting: the #NMSettingTCConfig * @qdisc: the qdisc to remove * * Removes the first matching qdisc that matches @qdisc. * * Returns: %TRUE if the qdisc was found and removed; %FALSE if it was not. * * Since: 1.12 **/ gboolean nm_setting_tc_config_remove_qdisc_by_value(NMSettingTCConfig *self, NMTCQdisc *qdisc) { guint i; g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); g_return_val_if_fail(qdisc != NULL, FALSE); for (i = 0; i < self->qdiscs->len; i++) { if (nm_tc_qdisc_equal(self->qdiscs->pdata[i], qdisc)) { g_ptr_array_remove_index(self->qdiscs, i); _notify(self, PROP_QDISCS); return TRUE; } } return FALSE; } /** * nm_setting_tc_config_clear_qdiscs: * @setting: the #NMSettingTCConfig * * Removes all configured queueing disciplines. * * Since: 1.12 **/ void nm_setting_tc_config_clear_qdiscs(NMSettingTCConfig *self) { g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); if (self->qdiscs->len != 0) { g_ptr_array_set_size(self->qdiscs, 0); _notify(self, PROP_QDISCS); } } /*****************************************************************************/ /** * nm_setting_tc_config_get_num_tfilters: * @setting: the #NMSettingTCConfig * * Returns: the number of configured queueing disciplines * * Since: 1.12 **/ guint nm_setting_tc_config_get_num_tfilters(NMSettingTCConfig *self) { g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), 0); return self->tfilters->len; } /** * nm_setting_tc_config_get_tfilter: * @setting: the #NMSettingTCConfig * @idx: index number of the tfilter to return * * Returns: (transfer none): the tfilter at index @idx * * Since: 1.12 **/ NMTCTfilter * nm_setting_tc_config_get_tfilter(NMSettingTCConfig *self, guint idx) { g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), NULL); g_return_val_if_fail(idx < self->tfilters->len, NULL); return self->tfilters->pdata[idx]; } /** * nm_setting_tc_config_add_tfilter: * @setting: the #NMSettingTCConfig * @tfilter: the tfilter to add * * Appends a new tfilter and associated information to the setting. The * given tfilter is duplicated internally and is not changed by this function. * If an identical tfilter (considering attributes as well) already exists, the * tfilter is not added and the function returns %FALSE. * * Returns: %TRUE if the tfilter was added; %FALSE if the tfilter was already known. * * Since: 1.12 **/ gboolean nm_setting_tc_config_add_tfilter(NMSettingTCConfig *self, NMTCTfilter *tfilter) { guint i; g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); g_return_val_if_fail(tfilter != NULL, FALSE); for (i = 0; i < self->tfilters->len; i++) { if (nm_tc_tfilter_equal(self->tfilters->pdata[i], tfilter)) return FALSE; } g_ptr_array_add(self->tfilters, nm_tc_tfilter_dup(tfilter)); _notify(self, PROP_TFILTERS); return TRUE; } /** * nm_setting_tc_config_remove_tfilter: * @setting: the #NMSettingTCConfig * @idx: index number of the tfilter * * Removes the tfilter at index @idx. * * Since: 1.12 **/ void nm_setting_tc_config_remove_tfilter(NMSettingTCConfig *self, guint idx) { g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); g_return_if_fail(idx < self->tfilters->len); g_ptr_array_remove_index(self->tfilters, idx); _notify(self, PROP_TFILTERS); } /** * nm_setting_tc_config_remove_tfilter_by_value: * @setting: the #NMSettingTCConfig * @tfilter: the tfilter to remove * * Removes the first matching tfilter that matches @tfilter. * * Returns: %TRUE if the tfilter was found and removed; %FALSE if it was not. * * Since: 1.12 **/ gboolean nm_setting_tc_config_remove_tfilter_by_value(NMSettingTCConfig *self, NMTCTfilter *tfilter) { guint i; g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); g_return_val_if_fail(tfilter != NULL, FALSE); for (i = 0; i < self->tfilters->len; i++) { if (nm_tc_tfilter_equal(self->tfilters->pdata[i], tfilter)) { g_ptr_array_remove_index(self->tfilters, i); _notify(self, PROP_TFILTERS); return TRUE; } } return FALSE; } /** * nm_setting_tc_config_clear_tfilters: * @setting: the #NMSettingTCConfig * * Removes all configured queueing disciplines. * * Since: 1.12 **/ void nm_setting_tc_config_clear_tfilters(NMSettingTCConfig *self) { g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); if (self->tfilters->len != 0) { g_ptr_array_set_size(self->tfilters, 0); _notify(self, PROP_TFILTERS); } } /*****************************************************************************/ 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 NMTernary compare_property(const NMSettInfoSetting *sett_info, guint property_idx, NMConnection * con_a, NMSetting * set_a, NMConnection * con_b, NMSetting * set_b, NMSettingCompareFlags flags) { NMSettingTCConfig *a_tc_config = NM_SETTING_TC_CONFIG(set_a); NMSettingTCConfig *b_tc_config = NM_SETTING_TC_CONFIG(set_b); guint i; if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_QDISCS)) { if (set_b) { if (a_tc_config->qdiscs->len != b_tc_config->qdiscs->len) return FALSE; for (i = 0; i < a_tc_config->qdiscs->len; i++) { if (!nm_tc_qdisc_equal(a_tc_config->qdiscs->pdata[i], b_tc_config->qdiscs->pdata[i])) return FALSE; } } return TRUE; } if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_TFILTERS)) { if (set_b) { if (a_tc_config->tfilters->len != b_tc_config->tfilters->len) return FALSE; for (i = 0; i < a_tc_config->tfilters->len; i++) { if (!nm_tc_tfilter_equal(a_tc_config->tfilters->pdata[i], b_tc_config->tfilters->pdata[i])) return FALSE; } } return TRUE; } return NM_SETTING_CLASS(nm_setting_tc_config_parent_class) ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); } /** * _qdiscs_to_variant: * @qdiscs: (element-type NMTCQdisc): an array of #NMTCQdisc objects * * Utility function to convert a #GPtrArray of #NMTCQdisc objects representing * TC qdiscs into a #GVariant of type 'aa{sv}' representing an array * of NetworkManager TC qdiscs. * * Returns: (transfer none): a new floating #GVariant representing @qdiscs. **/ static GVariant * _qdiscs_to_variant(GPtrArray *qdiscs) { GVariantBuilder builder; int i; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); if (qdiscs) { for (i = 0; i < qdiscs->len; i++) { NMUtilsNamedValue attrs_static[30]; gs_free NMUtilsNamedValue *attrs_free = NULL; const NMUtilsNamedValue * attrs; NMTCQdisc * qdisc = qdiscs->pdata[i]; guint length; GVariantBuilder qdisc_builder; guint y; g_variant_builder_init(&qdisc_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&qdisc_builder, "{sv}", "kind", g_variant_new_string(nm_tc_qdisc_get_kind(qdisc))); g_variant_builder_add(&qdisc_builder, "{sv}", "handle", g_variant_new_uint32(nm_tc_qdisc_get_handle(qdisc))); g_variant_builder_add(&qdisc_builder, "{sv}", "parent", g_variant_new_uint32(nm_tc_qdisc_get_parent(qdisc))); attrs = nm_utils_named_values_from_strdict(qdisc->attributes, &length, attrs_static, &attrs_free); for (y = 0; y < length; y++) { g_variant_builder_add(&qdisc_builder, "{sv}", attrs[y].name, attrs[y].value_ptr); } g_variant_builder_add(&builder, "a{sv}", &qdisc_builder); } } return g_variant_builder_end(&builder); } /** * _qdiscs_from_variant: * @value: a #GVariant of type 'aa{sv}' * * Utility function to convert a #GVariant representing a list of TC qdiscs * into a #GPtrArray of * #NMTCQdisc objects. * * Returns: (transfer full) (element-type NMTCQdisc): a newly allocated * #GPtrArray of #NMTCQdisc objects **/ static GPtrArray * _qdiscs_from_variant(GVariant *value) { GPtrArray * qdiscs; GVariant * qdisc_var; GVariantIter iter; GError * error = NULL; g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); g_variant_iter_init(&iter, value); qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); while (g_variant_iter_next(&iter, "@a{sv}", &qdisc_var)) { const char * kind; guint32 parent; NMTCQdisc * qdisc; GVariantIter qdisc_iter; const char * key; GVariant * attr_value; if (!g_variant_lookup(qdisc_var, "kind", "&s", &kind) || !g_variant_lookup(qdisc_var, "parent", "u", &parent)) { //g_warning ("Ignoring invalid qdisc"); goto next; } qdisc = nm_tc_qdisc_new(kind, parent, &error); if (!qdisc) { //g_warning ("Ignoring invalid qdisc: %s", error->message); g_clear_error(&error); goto next; } g_variant_iter_init(&qdisc_iter, qdisc_var); while (g_variant_iter_next(&qdisc_iter, "{&sv}", &key, &attr_value)) { if (strcmp(key, "kind") == 0 || strcmp(key, "parent") == 0) { /* Already processed above */ } else if (strcmp(key, "handle") == 0) { nm_tc_qdisc_set_handle(qdisc, g_variant_get_uint32(attr_value)); } else { nm_tc_qdisc_set_attribute(qdisc, key, attr_value); } g_variant_unref(attr_value); } g_ptr_array_add(qdiscs, qdisc); next: g_variant_unref(qdisc_var); } return qdiscs; } static GVariant * tc_qdiscs_get(const NMSettInfoSetting * sett_info, guint property_idx, NMConnection * connection, NMSetting * setting, NMConnectionSerializationFlags flags, const NMConnectionSerializationOptions *options) { gs_unref_ptrarray GPtrArray *qdiscs = NULL; g_object_get(setting, NM_SETTING_TC_CONFIG_QDISCS, &qdiscs, NULL); return _qdiscs_to_variant(qdiscs); } static gboolean tc_qdiscs_set(NMSetting * setting, GVariant * connection_dict, const char * property, GVariant * value, NMSettingParseFlags parse_flags, GError ** error) { GPtrArray *qdiscs; qdiscs = _qdiscs_from_variant(value); g_object_set(setting, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); g_ptr_array_unref(qdiscs); return TRUE; } static GVariant * _action_to_variant(NMTCAction *action) { GVariantBuilder builder; gs_strfreev char **attrs = nm_tc_action_get_attribute_names(action); int i; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&builder, "{sv}", "kind", g_variant_new_string(nm_tc_action_get_kind(action))); for (i = 0; attrs[i]; i++) { g_variant_builder_add(&builder, "{sv}", attrs[i], nm_tc_action_get_attribute(action, attrs[i])); } return g_variant_builder_end(&builder); } /** * _tfilters_to_variant: * @tfilters: (element-type NMTCTfilter): an array of #NMTCTfilter objects * * Utility function to convert a #GPtrArray of #NMTCTfilter objects representing * TC tfilters into a #GVariant of type 'aa{sv}' representing an array * of NetworkManager TC tfilters. * * Returns: (transfer none): a new floating #GVariant representing @tfilters. **/ static GVariant * _tfilters_to_variant(GPtrArray *tfilters) { GVariantBuilder builder; int i; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); if (tfilters) { for (i = 0; i < tfilters->len; i++) { NMTCTfilter * tfilter = tfilters->pdata[i]; NMTCAction * action = nm_tc_tfilter_get_action(tfilter); GVariantBuilder tfilter_builder; g_variant_builder_init(&tfilter_builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&tfilter_builder, "{sv}", "kind", g_variant_new_string(nm_tc_tfilter_get_kind(tfilter))); g_variant_builder_add(&tfilter_builder, "{sv}", "handle", g_variant_new_uint32(nm_tc_tfilter_get_handle(tfilter))); g_variant_builder_add(&tfilter_builder, "{sv}", "parent", g_variant_new_uint32(nm_tc_tfilter_get_parent(tfilter))); if (action) { g_variant_builder_add(&tfilter_builder, "{sv}", "action", _action_to_variant(action)); } g_variant_builder_add(&builder, "a{sv}", &tfilter_builder); } } return g_variant_builder_end(&builder); } /** * _tfilters_from_variant: * @value: a #GVariant of type 'aa{sv}' * * Utility function to convert a #GVariant representing a list of TC tfilters * into a #GPtrArray of * #NMTCTfilter objects. * * Returns: (transfer full) (element-type NMTCTfilter): a newly allocated * #GPtrArray of #NMTCTfilter objects **/ static GPtrArray * _tfilters_from_variant(GVariant *value) { GPtrArray * tfilters; GVariant * tfilter_var; GVariantIter iter; GError * error = NULL; g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); g_variant_iter_init(&iter, value); tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); while (g_variant_iter_next(&iter, "@a{sv}", &tfilter_var)) { NMTCTfilter *tfilter = NULL; const char * kind; guint32 handle; guint32 parent; NMTCAction * action; const char * action_kind = NULL; char * action_key; GVariantIter action_iter; GVariant * action_var = NULL; GVariant * action_val; if (!g_variant_lookup(tfilter_var, "kind", "&s", &kind) || !g_variant_lookup(tfilter_var, "parent", "u", &parent)) { //g_warning ("Ignoring invalid tfilter"); goto next; } tfilter = nm_tc_tfilter_new(kind, parent, &error); if (!tfilter) { //g_warning ("Ignoring invalid tfilter: %s", error->message); g_clear_error(&error); goto next; } if (g_variant_lookup(tfilter_var, "handle", "u", &handle)) nm_tc_tfilter_set_handle(tfilter, handle); action_var = g_variant_lookup_value(tfilter_var, "action", G_VARIANT_TYPE_VARDICT); if (action_var) { if (!g_variant_lookup(action_var, "kind", "&s", &action_kind)) { //g_warning ("Ignoring tfilter with invalid action"); goto next; } action = nm_tc_action_new(action_kind, &error); if (!action) { //g_warning ("Ignoring tfilter with invalid action: %s", error->message); g_clear_error(&error); goto next; } g_variant_iter_init(&action_iter, action_var); while (g_variant_iter_next(&action_iter, "{&sv}", &action_key, &action_val)) { if (strcmp(action_key, "kind") != 0) nm_tc_action_set_attribute(action, action_key, action_val); g_variant_unref(action_val); } nm_tc_tfilter_set_action(tfilter, action); nm_tc_action_unref(action); } nm_tc_tfilter_ref(tfilter); g_ptr_array_add(tfilters, tfilter); next: if (tfilter) nm_tc_tfilter_unref(tfilter); if (action_var) g_variant_unref(action_var); g_variant_unref(tfilter_var); } return tfilters; } static GVariant * tc_tfilters_get(const NMSettInfoSetting * sett_info, guint property_idx, NMConnection * connection, NMSetting * setting, NMConnectionSerializationFlags flags, const NMConnectionSerializationOptions *options) { gs_unref_ptrarray GPtrArray *tfilters = NULL; g_object_get(setting, NM_SETTING_TC_CONFIG_TFILTERS, &tfilters, NULL); return _tfilters_to_variant(tfilters); } static gboolean tc_tfilters_set(NMSetting * setting, GVariant * connection_dict, const char * property, GVariant * value, NMSettingParseFlags parse_flags, GError ** error) { gs_unref_ptrarray GPtrArray *tfilters = NULL; tfilters = _tfilters_from_variant(value); g_object_set(setting, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); return TRUE; } /*****************************************************************************/ static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); switch (prop_id) { case PROP_QDISCS: g_value_take_boxed(value, _nm_utils_copy_array(self->qdiscs, (NMUtilsCopyFunc) nm_tc_qdisc_dup, (GDestroyNotify) nm_tc_qdisc_unref)); break; case PROP_TFILTERS: g_value_take_boxed(value, _nm_utils_copy_array(self->tfilters, (NMUtilsCopyFunc) nm_tc_tfilter_dup, (GDestroyNotify) nm_tc_tfilter_unref)); 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) { NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); switch (prop_id) { case PROP_QDISCS: g_ptr_array_unref(self->qdiscs); self->qdiscs = _nm_utils_copy_array(g_value_get_boxed(value), (NMUtilsCopyFunc) nm_tc_qdisc_dup, (GDestroyNotify) nm_tc_qdisc_unref); break; case PROP_TFILTERS: g_ptr_array_unref(self->tfilters); self->tfilters = _nm_utils_copy_array(g_value_get_boxed(value), (NMUtilsCopyFunc) nm_tc_tfilter_dup, (GDestroyNotify) nm_tc_tfilter_unref); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_setting_tc_config_init(NMSettingTCConfig *self) { self->qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); self->tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); } /** * nm_setting_tc_config_new: * * Creates a new #NMSettingTCConfig object with default values. * * Returns: (transfer full): the new empty #NMSettingTCConfig object * * Since: 1.12 **/ NMSetting * nm_setting_tc_config_new(void) { return g_object_new(NM_TYPE_SETTING_TC_CONFIG, NULL); } static void finalize(GObject *object) { NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); g_ptr_array_unref(self->qdiscs); g_ptr_array_unref(self->tfilters); G_OBJECT_CLASS(nm_setting_tc_config_parent_class)->finalize(object); } static void nm_setting_tc_config_class_init(NMSettingTCConfigClass *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; /** * NMSettingTCConfig:qdiscs: (type GPtrArray(NMTCQdisc)) * * Array of TC queueing disciplines. **/ /* ---ifcfg-rh--- * property: qdiscs * variable: QDISC1(+), QDISC2(+), ... * description: Queueing disciplines * example: QDISC1=ingress, QDISC2="root handle 1234: fq_codel" * ---end--- */ obj_properties[PROP_QDISCS] = g_param_spec_boxed(NM_SETTING_TC_CONFIG_QDISCS, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); _nm_properties_override_gobj(properties_override, obj_properties[PROP_QDISCS], NM_SETT_INFO_PROPERT_TYPE(.dbus_type = NM_G_VARIANT_TYPE("aa{sv}"), .to_dbus_fcn = tc_qdiscs_get, .from_dbus_fcn = tc_qdiscs_set, )); /** * NMSettingTCConfig:tfilters: (type GPtrArray(NMTCTfilter)) * * Array of TC traffic filters. **/ /* ---ifcfg-rh--- * property: qdiscs * variable: FILTER1(+), FILTER2(+), ... * description: Traffic filters * example: FILTER1="parent ffff: matchall action simple sdata Input", ... * ---end--- */ obj_properties[PROP_TFILTERS] = g_param_spec_boxed( NM_SETTING_TC_CONFIG_TFILTERS, "", "", G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); _nm_properties_override_gobj(properties_override, obj_properties[PROP_TFILTERS], NM_SETT_INFO_PROPERT_TYPE(.dbus_type = NM_G_VARIANT_TYPE("aa{sv}"), .to_dbus_fcn = tc_tfilters_get, .from_dbus_fcn = tc_tfilters_set, )); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit_full(setting_class, NM_META_SETTING_TYPE_TC_CONFIG, NULL, properties_override); }