summary refs log tree commit diff
path: root/src/libnm-core-impl/nm-setting-vlan.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-impl/nm-setting-vlan.c')
-rw-r--r--src/libnm-core-impl/nm-setting-vlan.c1001
1 files changed, 1001 insertions, 0 deletions
diff --git a/src/libnm-core-impl/nm-setting-vlan.c b/src/libnm-core-impl/nm-setting-vlan.c
new file mode 100644
index 00000000..e4e07e35
--- /dev/null
+++ b/src/libnm-core-impl/nm-setting-vlan.c
@@ -0,0 +1,1001 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2011 - 2014 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-setting-vlan.h"
+
+#include <stdlib.h>
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "nm-utils.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-private.h"
+#include "nm-setting-wired.h"
+#include "nm-connection-private.h"
+
+/**
+ * SECTION:nm-setting-vlan
+ * @short_description: Describes connection properties for VLAN interfaces
+ *
+ * The #NMSettingVlan object is a #NMSetting subclass that describes properties
+ * necessary for connection to VLAN interfaces.
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingVlan,
+                             PROP_PARENT,
+                             PROP_ID,
+                             PROP_FLAGS,
+                             PROP_INGRESS_PRIORITY_MAP,
+                             PROP_EGRESS_PRIORITY_MAP, );
+
+typedef struct {
+    GSList *ingress_priority_map;
+    GSList *egress_priority_map;
+    char *  parent;
+    guint32 id;
+    guint32 flags;
+} NMSettingVlanPrivate;
+
+/**
+ * NMSettingVlan:
+ *
+ * VLAN Settings
+ */
+struct _NMSettingVlan {
+    NMSetting parent;
+    /* In the past, this struct was public API. Preserve ABI! */
+};
+
+struct _NMSettingVlanClass {
+    NMSettingClass parent;
+    /* In the past, this struct was public API. Preserve ABI! */
+    gpointer padding[4];
+};
+
+G_DEFINE_TYPE(NMSettingVlan, nm_setting_vlan, NM_TYPE_SETTING)
+
+#define NM_SETTING_VLAN_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VLAN, NMSettingVlanPrivate))
+
+/*****************************************************************************/
+
+#define MAX_SKB_PRIO   G_MAXUINT32
+#define MAX_8021P_PRIO 7 /* Max 802.1p priority */
+
+/**
+ * nm_setting_vlan_get_parent:
+ * @setting: the #NMSettingVlan
+ *
+ * Returns: the #NMSettingVlan:parent property of the setting
+ **/
+const char *
+nm_setting_vlan_get_parent(NMSettingVlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), NULL);
+    return NM_SETTING_VLAN_GET_PRIVATE(setting)->parent;
+}
+
+/**
+ * nm_setting_vlan_get_id:
+ * @setting: the #NMSettingVlan
+ *
+ * Returns: the #NMSettingVlan:id property of the setting
+ **/
+guint32
+nm_setting_vlan_get_id(NMSettingVlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), 0);
+    return NM_SETTING_VLAN_GET_PRIVATE(setting)->id;
+}
+
+/**
+ * nm_setting_vlan_get_flags:
+ * @setting: the #NMSettingVlan
+ *
+ * Returns: the #NMSettingVlan:flags property of the setting
+ **/
+guint32
+nm_setting_vlan_get_flags(NMSettingVlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), 0);
+    return NM_SETTING_VLAN_GET_PRIVATE(setting)->flags;
+}
+
+static NMVlanQosMapping *
+priority_map_new(guint32 from, guint32 to)
+{
+    NMVlanQosMapping *mapping;
+
+    mapping  = g_new(NMVlanQosMapping, 1);
+    *mapping = (NMVlanQosMapping){
+        .from = from,
+        .to   = to,
+    };
+    return mapping;
+}
+
+static NMVlanQosMapping *
+priority_map_new_from_str(NMVlanPriorityMap map, const char *str)
+{
+    guint32 from, to;
+
+    if (!nm_utils_vlan_priority_map_parse_str(map, str, FALSE, &from, &to, NULL))
+        return NULL;
+    return priority_map_new(from, to);
+}
+
+static void
+priority_map_free(NMVlanQosMapping *map)
+{
+    nm_assert(map);
+    g_free(map);
+}
+
+static GSList *
+get_map(NMSettingVlan *self, NMVlanPriorityMap map)
+{
+    if (map == NM_VLAN_INGRESS_MAP)
+        return NM_SETTING_VLAN_GET_PRIVATE(self)->ingress_priority_map;
+    else if (map == NM_VLAN_EGRESS_MAP)
+        return NM_SETTING_VLAN_GET_PRIVATE(self)->egress_priority_map;
+    nm_assert_not_reached();
+    return NULL;
+}
+
+static int
+prio_map_compare(gconstpointer p_a, gconstpointer p_b)
+{
+    const NMVlanQosMapping *a = p_a;
+    const NMVlanQosMapping *b = p_b;
+
+    return a->from < b->from
+               ? -1
+               : (a->from > b->from ? 1 : (a->to < b->to ? -1 : (a->to > b->to ? 1 : 0)));
+}
+
+static void
+set_map(NMSettingVlan *self, NMVlanPriorityMap map, GSList *list)
+{
+    /* Assert that the list is sorted */
+#if NM_MORE_ASSERTS >= 2
+    {
+        GSList *iter, *last;
+
+        last = list;
+        iter = list ? list->next : NULL;
+        while (iter) {
+            const NMVlanQosMapping *l = last->data;
+            const NMVlanQosMapping *m = iter->data;
+
+            nm_assert(prio_map_compare(last->data, iter->data) < 0);
+
+            /* Also reject duplicates (based on "from") */
+            nm_assert(l->from < m->from);
+
+            last = iter;
+            iter = iter->next;
+        }
+    }
+#endif
+
+    if (map == NM_VLAN_INGRESS_MAP) {
+        NM_SETTING_VLAN_GET_PRIVATE(self)->ingress_priority_map = list;
+        _notify(self, PROP_INGRESS_PRIORITY_MAP);
+    } else if (map == NM_VLAN_EGRESS_MAP) {
+        NM_SETTING_VLAN_GET_PRIVATE(self)->egress_priority_map = list;
+        _notify(self, PROP_EGRESS_PRIORITY_MAP);
+    } else
+        nm_assert_not_reached();
+}
+
+static gboolean
+check_replace_duplicate_priority(GSList *list, guint32 from, guint32 to)
+{
+    GSList *          iter;
+    NMVlanQosMapping *p;
+
+    for (iter = list; iter; iter = g_slist_next(iter)) {
+        p = iter->data;
+        if (p->from == from) {
+            p->to = to;
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/**
+ * nm_setting_vlan_add_priority_str:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @str: the string which contains a priority map, like "3:7"
+ *
+ * Adds a priority map entry into either the #NMSettingVlan:ingress_priority_map
+ * or the #NMSettingVlan:egress_priority_map properties.  The priority map maps
+ * the Linux SKB priorities to 802.1p priorities.
+ *
+ * Returns: %TRUE if the entry was successfully added to the list, or it
+ * overwrote the old value, %FALSE if @str is not a valid mapping.
+ */
+gboolean
+nm_setting_vlan_add_priority_str(NMSettingVlan *setting, NMVlanPriorityMap map, const char *str)
+{
+    GSList *          list = NULL;
+    NMVlanQosMapping *item = NULL;
+
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE);
+    g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE);
+    g_return_val_if_fail(str && str[0], FALSE);
+
+    item = priority_map_new_from_str(map, str);
+    if (!item)
+        return FALSE;
+
+    list = get_map(setting, map);
+
+    /* Duplicates get replaced */
+    if (check_replace_duplicate_priority(list, item->from, item->to)) {
+        g_free(item);
+        if (map == NM_VLAN_INGRESS_MAP)
+            _notify(setting, PROP_INGRESS_PRIORITY_MAP);
+        else
+            _notify(setting, PROP_EGRESS_PRIORITY_MAP);
+        return TRUE;
+    }
+
+    set_map(setting, map, g_slist_insert_sorted(list, item, prio_map_compare));
+    return TRUE;
+}
+
+/**
+ * nm_setting_vlan_get_num_priorities:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ *
+ * Returns the number of entries in the
+ * #NMSettingVlan:ingress_priority_map or #NMSettingVlan:egress_priority_map
+ * properties of this setting.
+ *
+ * Returns: return the number of ingress/egress priority entries.
+ **/
+gint32
+nm_setting_vlan_get_num_priorities(NMSettingVlan *setting, NMVlanPriorityMap map)
+{
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), -1);
+    g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, -1);
+
+    return g_slist_length(get_map(setting, map));
+}
+
+/**
+ * nm_setting_vlan_get_priority:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @idx: the zero-based index of the ingress/egress priority map entry
+ * @out_from: (out) (allow-none): on return the value of the priority map's 'from' item
+ * @out_to: (out) (allow-none): on return the value of priority map's 'to' item
+ *
+ * Retrieve one of the entries of the #NMSettingVlan:ingress_priority_map
+ * or #NMSettingVlan:egress_priority_map properties of this setting.
+ *
+ * Returns: returns %TRUE if @idx is in range. Otherwise, %FALSE.
+ **/
+gboolean
+nm_setting_vlan_get_priority(NMSettingVlan *   setting,
+                             NMVlanPriorityMap map,
+                             guint32           idx,
+                             guint32 *         out_from,
+                             guint32 *         out_to)
+{
+    NMVlanQosMapping *item;
+    GSList *          list;
+
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE);
+    g_return_val_if_fail(NM_IN_SET(map, NM_VLAN_INGRESS_MAP, NM_VLAN_EGRESS_MAP), FALSE);
+
+    list = get_map(setting, map);
+    item = g_slist_nth_data(list, idx);
+
+    if (!item) {
+        NM_SET_OUT(out_from, 0);
+        NM_SET_OUT(out_to, 0);
+        return FALSE;
+    }
+
+    NM_SET_OUT(out_from, item->from);
+    NM_SET_OUT(out_to, item->to);
+    return TRUE;
+}
+
+/**
+ * nm_setting_vlan_add_priority:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @from: the priority to map to @to
+ * @to: the priority to map @from to
+ *
+ * Adds a priority mapping to the #NMSettingVlan:ingress_priority_map or
+ * #NMSettingVlan:egress_priority_map properties of the setting. If @from is
+ * already in the given priority map, this function will overwrite the
+ * existing entry with the new @to.
+ *
+ * If @map is #NM_VLAN_INGRESS_MAP then @from is the incoming 802.1q VLAN
+ * Priority Code Point (PCP) value, and @to is the Linux SKB priority value.
+ *
+ * If @map is #NM_VLAN_EGRESS_MAP then @from is the Linux SKB priority value and
+ * @to is the outgoing 802.1q VLAN Priority Code Point (PCP) value.
+ *
+ * Returns: %TRUE.
+ */
+gboolean
+nm_setting_vlan_add_priority(NMSettingVlan *   setting,
+                             NMVlanPriorityMap map,
+                             guint32           from,
+                             guint32           to)
+{
+    GSList *          list = NULL;
+    NMVlanQosMapping *item;
+
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE);
+    g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE);
+
+    list = get_map(setting, map);
+    if (check_replace_duplicate_priority(list, from, to)) {
+        if (map == NM_VLAN_INGRESS_MAP)
+            _notify(setting, PROP_INGRESS_PRIORITY_MAP);
+        else
+            _notify(setting, PROP_EGRESS_PRIORITY_MAP);
+        return TRUE;
+    }
+
+    item       = g_malloc0(sizeof(NMVlanQosMapping));
+    item->from = from;
+    item->to   = to;
+    set_map(setting, map, g_slist_insert_sorted(list, item, prio_map_compare));
+
+    return TRUE;
+}
+
+gboolean
+_nm_setting_vlan_set_priorities(NMSettingVlan *         setting,
+                                NMVlanPriorityMap       map,
+                                const NMVlanQosMapping *qos_map,
+                                guint                   n_qos_map)
+{
+    gboolean has_changes = FALSE;
+    GSList * map_prev, *map_new;
+    guint    i;
+    gint64   from_last;
+
+    map_prev = get_map(setting, map);
+
+    if (n_qos_map != g_slist_length(map_prev))
+        has_changes = TRUE;
+    else {
+        const GSList *iter;
+
+        iter = map_prev;
+        for (i = 0; i < n_qos_map; i++, iter = iter->next) {
+            const NMVlanQosMapping *m = iter->data;
+
+            if (m->from != qos_map[i].from || m->to != qos_map[i].to) {
+                has_changes = TRUE;
+                break;
+            }
+        }
+    }
+
+    if (!has_changes)
+        return FALSE;
+
+    map_new   = NULL;
+    from_last = G_MAXINT64;
+    for (i = n_qos_map; i > 0;) {
+        const NMVlanQosMapping *m = &qos_map[--i];
+        NMVlanQosMapping *      item;
+
+        /* We require the array to be presorted. */
+        if (m->from >= from_last)
+            g_return_val_if_reached(FALSE);
+        from_last = m->from;
+
+        item       = g_malloc0(sizeof(NMVlanQosMapping));
+        item->from = m->from;
+        item->to   = m->to;
+        map_new    = g_slist_prepend(map_new, item);
+    }
+
+    g_slist_free_full(map_prev, g_free);
+    set_map(setting, map, map_new);
+
+    return TRUE;
+}
+
+void
+_nm_setting_vlan_get_priorities(NMSettingVlan *    setting,
+                                NMVlanPriorityMap  map,
+                                NMVlanQosMapping **out_qos_map,
+                                guint *            out_n_qos_map)
+{
+    GSList *          list;
+    NMVlanQosMapping *qos_map = NULL;
+    guint             n_qos_map, i;
+
+    list = get_map(setting, map);
+
+    n_qos_map = g_slist_length(list);
+
+    if (n_qos_map > 0) {
+        qos_map = g_new(NMVlanQosMapping, n_qos_map);
+
+        for (i = 0; list; i++, list = list->next) {
+            nm_assert(i < n_qos_map);
+            qos_map[i] = *((const NMVlanQosMapping *) list->data);
+        }
+    }
+    *out_qos_map   = qos_map;
+    *out_n_qos_map = n_qos_map;
+}
+
+/**
+ * nm_setting_vlan_remove_priority:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @idx: the zero-based index of the priority map to remove
+ *
+ * Removes the priority map at index @idx from the
+ * #NMSettingVlan:ingress_priority_map or #NMSettingVlan:egress_priority_map
+ * properties.
+ */
+void
+nm_setting_vlan_remove_priority(NMSettingVlan *setting, NMVlanPriorityMap map, guint32 idx)
+{
+    GSList *list = NULL, *item = NULL;
+
+    g_return_if_fail(NM_IS_SETTING_VLAN(setting));
+    g_return_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP);
+
+    list = get_map(setting, map);
+    g_return_if_fail(idx < g_slist_length(list));
+
+    item = g_slist_nth(list, idx);
+    priority_map_free((NMVlanQosMapping *) (item->data));
+    set_map(setting, map, g_slist_delete_link(list, item));
+}
+
+static gboolean
+priority_map_remove_by_value(NMSettingVlan *   setting,
+                             NMVlanPriorityMap map,
+                             guint32           from,
+                             guint32           to,
+                             gboolean          wildcard_to)
+{
+    GSList *          list = NULL, *iter = NULL;
+    NMVlanQosMapping *item;
+
+    nm_assert(NM_IS_SETTING_VLAN(setting));
+    nm_assert(NM_IN_SET(map, NM_VLAN_INGRESS_MAP, NM_VLAN_EGRESS_MAP));
+
+    list = get_map(setting, map);
+    for (iter = list; iter; iter = g_slist_next(iter)) {
+        item = iter->data;
+
+        if (item->from != from)
+            continue;
+        if (!wildcard_to && item->to != to)
+            continue;
+
+        priority_map_free((NMVlanQosMapping *) (iter->data));
+        set_map(setting, map, g_slist_delete_link(list, iter));
+        return TRUE;
+    }
+    return FALSE;
+}
+
+/**
+ * nm_setting_vlan_remove_priority_by_value:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @from: the priority to map to @to
+ * @to: the priority to map @from to
+ *
+ * Removes the priority map @form:@to from the #NMSettingVlan:ingress_priority_map
+ * or #NMSettingVlan:egress_priority_map (according to @map argument)
+ * properties.
+ *
+ * Returns: %TRUE if the priority mapping was found and removed; %FALSE if it was not.
+ */
+gboolean
+nm_setting_vlan_remove_priority_by_value(NMSettingVlan *   setting,
+                                         NMVlanPriorityMap map,
+                                         guint32           from,
+                                         guint32           to)
+{
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE);
+    g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE);
+
+    return priority_map_remove_by_value(setting, map, from, to, FALSE);
+}
+
+/**
+ * nm_setting_vlan_remove_priority_str_by_value:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ * @str: the string which contains a priority map, like "3:7"
+ *
+ * Removes the priority map @str from the #NMSettingVlan:ingress_priority_map
+ * or #NMSettingVlan:egress_priority_map (according to @map argument)
+ * properties.
+ *
+ * Returns: %TRUE if the priority mapping was found and removed; %FALSE if it was not.
+ */
+gboolean
+nm_setting_vlan_remove_priority_str_by_value(NMSettingVlan *   setting,
+                                             NMVlanPriorityMap map,
+                                             const char *      str)
+{
+    gboolean is_wildcard_to;
+    guint32  from, to;
+
+    g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE);
+    g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE);
+
+    if (!nm_utils_vlan_priority_map_parse_str(map, str, TRUE, &from, &to, &is_wildcard_to))
+        return FALSE;
+    return priority_map_remove_by_value(setting, map, from, to, is_wildcard_to);
+}
+
+/**
+ * nm_setting_vlan_clear_priorities:
+ * @setting: the #NMSettingVlan
+ * @map: the type of priority map
+ *
+ * Clear all the entries from #NMSettingVlan:ingress_priority_map or
+ * #NMSettingVlan:egress_priority_map properties.
+ */
+void
+nm_setting_vlan_clear_priorities(NMSettingVlan *setting, NMVlanPriorityMap map)
+{
+    GSList *list = NULL;
+
+    g_return_if_fail(NM_IS_SETTING_VLAN(setting));
+    g_return_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP);
+
+    list = get_map(setting, map);
+    g_slist_free_full(list, g_free);
+    set_map(setting, map, NULL);
+}
+
+/*****************************************************************************/
+
+static int
+verify(NMSetting *setting, NMConnection *connection, GError **error)
+{
+    NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(setting);
+    NMSettingConnection * s_con;
+    NMSettingWired *      s_wired;
+
+    if (connection) {
+        s_con   = nm_connection_get_setting_connection(connection);
+        s_wired = nm_connection_get_setting_wired(connection);
+    } else {
+        s_con   = NULL;
+        s_wired = NULL;
+    }
+
+    if (priv->parent) {
+        if (nm_utils_is_uuid(priv->parent)) {
+            /* If we have an NMSettingConnection:master with slave-type="vlan",
+             * then it must be the same UUID.
+             */
+            if (s_con) {
+                const char *master = NULL, *slave_type = NULL;
+
+                slave_type = nm_setting_connection_get_slave_type(s_con);
+                if (!g_strcmp0(slave_type, NM_SETTING_VLAN_SETTING_NAME))
+                    master = nm_setting_connection_get_master(s_con);
+
+                if (master && g_strcmp0(priv->parent, master) != 0) {
+                    g_set_error(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("'%s' value doesn't match '%s=%s'"),
+                                priv->parent,
+                                NM_SETTING_CONNECTION_MASTER,
+                                master);
+                    g_prefix_error(error,
+                                   "%s.%s: ",
+                                   NM_SETTING_VLAN_SETTING_NAME,
+                                   NM_SETTING_VLAN_PARENT);
+                    return FALSE;
+                }
+            }
+        } else if (!nm_utils_ifname_valid_kernel(priv->parent, NULL)) {
+            /* parent must be either a UUID or an interface name */
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is neither an UUID nor an interface name"),
+                        priv->parent);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_PARENT);
+            return FALSE;
+        }
+    } else {
+        /* If parent is NULL, the parent must be specified via
+         * NMSettingWired:mac-address.
+         */
+        if (connection && (!s_wired || !nm_setting_wired_get_mac_address(s_wired))) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                        _("property is not specified and neither is '%s:%s'"),
+                        NM_SETTING_WIRED_SETTING_NAME,
+                        NM_SETTING_WIRED_MAC_ADDRESS);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_PARENT);
+            return FALSE;
+        }
+    }
+
+    if (priv->id >= 4095) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("the vlan id must be in range 0-4094 but is %u"),
+                    priv->id);
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_ID);
+        return FALSE;
+    }
+
+    if (priv->flags & ~NM_VLAN_FLAGS_ALL) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("flags are invalid"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_FLAGS);
+        return FALSE;
+    }
+
+    if (connection && !s_wired) {
+        /* technically, a VLAN setting does not require an ethernet setting. However,
+         * the ifcfg-rh reader always adds a ethernet setting when reading a vlan setting.
+         * Thus, in order to be consistent, always add one via normalization. */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+                            _("vlan setting should have a ethernet setting as well"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_FLAGS);
+        return NM_SETTING_VERIFY_NORMALIZABLE;
+    }
+
+    return TRUE;
+}
+
+static GVariant *
+_override_flags_get(const NMSettInfoSetting *               sett_info,
+                    guint                                   property_idx,
+                    NMConnection *                          connection,
+                    NMSetting *                             setting,
+                    NMConnectionSerializationFlags          flags,
+                    const NMConnectionSerializationOptions *options)
+{
+    return g_variant_new_uint32(nm_setting_vlan_get_flags((NMSettingVlan *) setting));
+}
+
+static gboolean
+_override_flags_not_set(NMSetting *         setting,
+                        GVariant *          connection_dict,
+                        const char *        property,
+                        NMSettingParseFlags parse_flags,
+                        GError **           error)
+{
+    /* we changed the default value for FLAGS. When an older client
+     * doesn't serialize the property, we assume it is the old default. */
+    g_object_set(G_OBJECT(setting), NM_SETTING_VLAN_FLAGS, (NMVlanFlags) 0, NULL);
+    return TRUE;
+}
+
+static GSList *
+priority_strv_to_maplist(NMVlanPriorityMap map, char **strv)
+{
+    GSList *list = NULL;
+    gsize   i;
+
+    for (i = 0; strv && strv[i]; i++) {
+        guint32 from, to;
+
+        if (!nm_utils_vlan_priority_map_parse_str(map, strv[i], FALSE, &from, &to, NULL))
+            continue;
+        if (check_replace_duplicate_priority(list, from, to))
+            continue;
+        list = g_slist_prepend(list, priority_map_new(from, to));
+    }
+    return g_slist_sort(list, prio_map_compare);
+}
+
+static char **
+priority_maplist_to_strv(GSList *list)
+{
+    GSList *   iter;
+    GPtrArray *strv;
+
+    strv = g_ptr_array_new();
+
+    for (iter = list; iter; iter = g_slist_next(iter)) {
+        NMVlanQosMapping *item = iter->data;
+
+        g_ptr_array_add(strv, g_strdup_printf("%d:%d", item->from, item->to));
+    }
+    g_ptr_array_add(strv, NULL);
+
+    return (char **) g_ptr_array_free(strv, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMSettingVlan *       setting = NM_SETTING_VLAN(object);
+    NMSettingVlanPrivate *priv    = NM_SETTING_VLAN_GET_PRIVATE(setting);
+
+    switch (prop_id) {
+    case PROP_PARENT:
+        g_value_set_string(value, priv->parent);
+        break;
+    case PROP_ID:
+        g_value_set_uint(value, priv->id);
+        break;
+    case PROP_FLAGS:
+        g_value_set_flags(value, priv->flags);
+        break;
+    case PROP_INGRESS_PRIORITY_MAP:
+        g_value_take_boxed(value, priority_maplist_to_strv(priv->ingress_priority_map));
+        break;
+    case PROP_EGRESS_PRIORITY_MAP:
+        g_value_take_boxed(value, priority_maplist_to_strv(priv->egress_priority_map));
+        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)
+{
+    NMSettingVlan *       setting = NM_SETTING_VLAN(object);
+    NMSettingVlanPrivate *priv    = NM_SETTING_VLAN_GET_PRIVATE(setting);
+
+    switch (prop_id) {
+    case PROP_PARENT:
+        g_free(priv->parent);
+        priv->parent = g_value_dup_string(value);
+        break;
+    case PROP_ID:
+        priv->id = g_value_get_uint(value);
+        break;
+    case PROP_FLAGS:
+        priv->flags = g_value_get_flags(value);
+        break;
+    case PROP_INGRESS_PRIORITY_MAP:
+        g_slist_free_full(priv->ingress_priority_map, g_free);
+        priv->ingress_priority_map =
+            priority_strv_to_maplist(NM_VLAN_INGRESS_MAP, g_value_get_boxed(value));
+        break;
+    case PROP_EGRESS_PRIORITY_MAP:
+        g_slist_free_full(priv->egress_priority_map, g_free);
+        priv->egress_priority_map =
+            priority_strv_to_maplist(NM_VLAN_EGRESS_MAP, g_value_get_boxed(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_vlan_init(NMSettingVlan *self)
+{
+    NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(self);
+
+    priv->flags = NM_VLAN_FLAG_REORDER_HEADERS;
+}
+
+/**
+ * nm_setting_vlan_new:
+ *
+ * Creates a new #NMSettingVlan object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingVlan object
+ **/
+NMSetting *
+nm_setting_vlan_new(void)
+{
+    return g_object_new(NM_TYPE_SETTING_VLAN, NULL);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMSettingVlan *       setting = NM_SETTING_VLAN(object);
+    NMSettingVlanPrivate *priv    = NM_SETTING_VLAN_GET_PRIVATE(setting);
+
+    g_free(priv->parent);
+    g_slist_free_full(priv->ingress_priority_map, g_free);
+    g_slist_free_full(priv->egress_priority_map, g_free);
+
+    G_OBJECT_CLASS(nm_setting_vlan_parent_class)->finalize(object);
+}
+
+static void
+nm_setting_vlan_class_init(NMSettingVlanClass *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();
+
+    g_type_class_add_private(klass, sizeof(NMSettingVlanPrivate));
+
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+    object_class->finalize     = finalize;
+
+    setting_class->verify = verify;
+
+    /**
+     * NMSettingVlan:parent:
+     *
+     * If given, specifies the parent interface name or parent connection UUID
+     * from which this VLAN interface should be created.  If this property is
+     * not specified, the connection must contain an #NMSettingWired setting
+     * with a #NMSettingWired:mac-address property.
+     **/
+    /* ---ifcfg-rh---
+     * property: parent
+     * variable: DEVICE or PHYSDEV
+     * description: Parent interface of the VLAN.
+     * ---end---
+     */
+    obj_properties[PROP_PARENT] = g_param_spec_string(
+        NM_SETTING_VLAN_PARENT,
+        "",
+        "",
+        NULL,
+        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingVlan:id:
+     *
+     * The VLAN identifier that the interface created by this connection should
+     * be assigned. The valid range is from 0 to 4094, without the reserved id 4095.
+     **/
+    /* ---ifcfg-rh---
+     * property: id
+     * variable: VLAN_ID, DEVICE.
+     * description: VLAN identifier. If VLAN_ID is not set, it is attempted
+     *   to be detected from the suffix of DEVICE=.
+     *   Note that older versions of NetworkManager had a bug where they would
+     *   prefer the detected ID from the DEVICE over VLAN_ID.
+     * ---end---
+     */
+    obj_properties[PROP_ID] =
+        g_param_spec_uint(NM_SETTING_VLAN_ID,
+                          "",
+                          "",
+                          0,
+                          4095,
+                          0,
+                          G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingVlan:flags:
+     *
+     * One or more flags which control the behavior and features of the VLAN
+     * interface.  Flags include %NM_VLAN_FLAG_REORDER_HEADERS (reordering of
+     * output packet headers), %NM_VLAN_FLAG_GVRP (use of the GVRP protocol),
+     * and %NM_VLAN_FLAG_LOOSE_BINDING (loose binding of the interface to its
+     * master device's operating state). %NM_VLAN_FLAG_MVRP (use of the MVRP
+     * protocol).
+     *
+     * The default value of this property is NM_VLAN_FLAG_REORDER_HEADERS,
+     * but it used to be 0. To preserve backward compatibility, the default-value
+     * in the D-Bus API continues to be 0 and a missing property on D-Bus
+     * is still considered as 0.
+     **/
+    /* ---ifcfg-rh---
+     * property: flags
+     * variable: GVRP, MVRP, VLAN_FLAGS
+     * values: "yes or "no" for GVRP and MVRP; "LOOSE_BINDING" and "NO_REORDER_HDR" for VLAN_FLAGS
+     * description: VLAN flags.
+     * ---end---
+     */
+    obj_properties[PROP_FLAGS] = g_param_spec_flags(NM_SETTING_VLAN_FLAGS,
+                                                    "",
+                                                    "",
+                                                    NM_TYPE_VLAN_FLAGS,
+                                                    NM_VLAN_FLAG_REORDER_HEADERS,
+                                                    G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE
+                                                        | G_PARAM_STATIC_STRINGS);
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_FLAGS],
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_UINT32,
+                                       .to_dbus_fcn           = _override_flags_get,
+                                       .missing_from_dbus_fcn = _override_flags_not_set, ));
+
+    /**
+     * NMSettingVlan:ingress-priority-map:
+     *
+     * For incoming packets, a list of mappings from 802.1p priorities to Linux
+     * SKB priorities.  The mapping is given in the format "from:to" where both
+     * "from" and "to" are unsigned integers, ie "7:3".
+     **/
+    /* ---ifcfg-rh---
+     * property: ingress-priority-map
+     * variable: VLAN_INGRESS_PRIORITY_MAP
+     * description: Ingress priority mapping.
+     * example: VLAN_INGRESS_PRIORITY_MAP=4:2,3:5
+     * ---end---
+     */
+    obj_properties[PROP_INGRESS_PRIORITY_MAP] = g_param_spec_boxed(
+        NM_SETTING_VLAN_INGRESS_PRIORITY_MAP,
+        "",
+        "",
+        G_TYPE_STRV,
+        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingVlan:egress-priority-map:
+     *
+     * For outgoing packets, a list of mappings from Linux SKB priorities to
+     * 802.1p priorities.  The mapping is given in the format "from:to" where
+     * both "from" and "to" are unsigned integers, ie "7:3".
+     **/
+    /* ---ifcfg-rh---
+     * property: egress-priority-map
+     * variable: VLAN_EGRESS_PRIORITY_MAP
+     * description: Egress priority mapping.
+     * example: VLAN_EGRESS_PRIORITY_MAP=5:4,4:1,3:7
+     * ---end---
+     */
+    obj_properties[PROP_EGRESS_PRIORITY_MAP] = g_param_spec_boxed(
+        NM_SETTING_VLAN_EGRESS_PRIORITY_MAP,
+        "",
+        "",
+        G_TYPE_STRV,
+        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /* ---ifcfg-rh---
+     * property: interface-name
+     * variable: PHYSDEV and VLAN_ID, or DEVICE
+     * description: VLAN interface name.
+     *   If all variables are set, parent device from PHYSDEV takes precedence over DEVICE,
+     *   but VLAN id from DEVICE takes precedence over VLAN_ID.
+     * example: PHYSDEV=eth0, VLAN_ID=12; or DEVICE=eth0.12
+     * ---end---
+     * ---dbus---
+     * property: interface-name
+     * format: string
+     * description: Deprecated in favor of connection.interface-name, but can
+     *   be used for backward-compatibility with older daemons, to set the
+     *   vlan's interface name.
+     * ---end---
+     */
+    _nm_properties_override_dbus(properties_override,
+                                 "interface-name",
+                                 &nm_sett_info_propert_type_deprecated_interface_name);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    _nm_setting_class_commit_full(setting_class,
+                                  NM_META_SETTING_TYPE_VLAN,
+                                  NULL,
+                                  properties_override);
+}