summary refs log tree commit diff
path: root/libnm-core/nm-setting-bridge.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-setting-bridge.c')
-rw-r--r--libnm-core/nm-setting-bridge.c863
1 files changed, 861 insertions, 2 deletions
diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c
index e418013c..c6aca021 100644
--- a/libnm-core/nm-setting-bridge.c
+++ b/libnm-core/nm-setting-bridge.c
@@ -40,7 +40,7 @@
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge,
 	PROP_MAC_ADDRESS,
 	PROP_STP,
 	PROP_PRIORITY,
@@ -50,6 +50,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 	PROP_AGEING_TIME,
 	PROP_GROUP_FORWARD_MASK,
 	PROP_MULTICAST_SNOOPING,
+	PROP_VLAN_FILTERING,
+	PROP_VLAN_DEFAULT_PVID,
+	PROP_VLANS,
 );
 
 typedef struct {
@@ -62,6 +65,9 @@ typedef struct {
 	guint32  ageing_time;
 	guint16  group_forward_mask;
 	gboolean multicast_snooping;
+	gboolean vlan_filtering;
+	guint16  vlan_default_pvid;
+	GPtrArray *vlans;
 } NMSettingBridgePrivate;
 
 G_DEFINE_TYPE (NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING)
@@ -70,6 +76,506 @@ G_DEFINE_TYPE (NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+G_DEFINE_BOXED_TYPE (NMBridgeVlan, nm_bridge_vlan, _nm_bridge_vlan_dup, nm_bridge_vlan_unref)
+
+struct _NMBridgeVlan {
+	guint refcount;
+	guint16 vid_start;
+	guint16 vid_end;
+	bool untagged:1;
+	bool pvid:1;
+	bool sealed:1;
+};
+
+static gboolean
+NM_IS_BRIDGE_VLAN (const NMBridgeVlan *self, gboolean also_sealed)
+{
+	return    self
+	       && self->refcount > 0
+	       && (also_sealed || !self->sealed);
+}
+
+/**
+ * nm_bridge_vlan_new:
+ * @vid_start: the start VLAN id, must be between 1 and 4094.
+ * @vid_end: the end VLAN id, must be 0 or between @vid_start and 4094.
+ *
+ * Creates a new #NMBridgeVlan object for the given VLAN id range.
+ * Setting @vid_end to 0 is equivalent to setting it to @vid_start
+ * and creates a single-id VLAN.
+ *
+ * Returns: (transfer full): the new #NMBridgeVlan object.
+ *
+ * Since: 1.18
+ **/
+NMBridgeVlan *
+nm_bridge_vlan_new (guint16 vid_start, guint16 vid_end)
+{
+	NMBridgeVlan *vlan;
+
+	if (vid_end == 0)
+		vid_end = vid_start;
+
+	g_return_val_if_fail (vid_start >= NM_BRIDGE_VLAN_VID_MIN, NULL);
+	g_return_val_if_fail (vid_end <= NM_BRIDGE_VLAN_VID_MAX, NULL);
+	g_return_val_if_fail (vid_start <= vid_end, NULL);
+
+	vlan = g_slice_new0 (NMBridgeVlan);
+	vlan->refcount = 1;
+	vlan->vid_start = vid_start;
+	vlan->vid_end = vid_end;
+
+	return vlan;
+}
+
+/**
+ * nm_bridge_vlan_ref:
+ * @vlan: the #NMBridgeVlan
+ *
+ * Increases the reference count of the object.
+ *
+ * Returns: the input argument @vlan object.
+ *
+ * Since: 1.18
+ **/
+NMBridgeVlan *
+nm_bridge_vlan_ref (NMBridgeVlan *vlan)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL);
+
+	nm_assert (vlan->refcount < G_MAXUINT);
+
+	vlan->refcount++;
+	return vlan;
+}
+
+/**
+ * nm_bridge_vlan_unref:
+ * @vlan: the #NMBridgeVlan
+ *
+ * Decreases the reference count of the object.  If the reference count
+ * reaches zero the object will be destroyed.
+ *
+ * Since: 1.18
+ **/
+void
+nm_bridge_vlan_unref (NMBridgeVlan *vlan)
+{
+	g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE));
+
+	if (--vlan->refcount == 0)
+		g_slice_free (NMBridgeVlan, vlan);
+}
+
+/**
+ * nm_bridge_vlan_cmp:
+ * @a: a #NMBridgeVlan
+ * @b: another #NMBridgeVlan
+ *
+ * Compare two bridge VLAN objects.
+ *
+ * Returns: zero of the two instances are equivalent or
+ *   a non-zero integer otherwise. This defines a total ordering
+ *   over the VLANs. Whether a VLAN is sealed or not does not
+ *   affect the comparison.
+ *
+ * Since: 1.18
+ **/
+int
+nm_bridge_vlan_cmp (const NMBridgeVlan *a, const NMBridgeVlan *b)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (a, TRUE), 0);
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (b, TRUE), 0);
+
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, vid_start);
+	NM_CMP_FIELD (a, b, vid_end);
+	NM_CMP_FIELD_BOOL (a, b, untagged);
+	NM_CMP_FIELD_BOOL (a, b, pvid);
+
+	return 0;
+}
+
+NMBridgeVlan *
+_nm_bridge_vlan_dup (const NMBridgeVlan *vlan)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL);
+
+	if (vlan->sealed) {
+		nm_bridge_vlan_ref ((NMBridgeVlan *) vlan);
+		return (NMBridgeVlan *) vlan;
+	}
+
+	return nm_bridge_vlan_new_clone (vlan);
+}
+
+NMBridgeVlan *
+_nm_bridge_vlan_dup_and_seal (const NMBridgeVlan *vlan)
+{
+	NMBridgeVlan *new;
+
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL);
+
+	new = _nm_bridge_vlan_dup (vlan);
+	nm_bridge_vlan_seal (new);
+
+	return new;
+}
+
+/**
+ * nm_bridge_vlan_get_vid_range:
+ * @vlan: the #NMBridgeVlan
+ * @vid_start: location to store the VLAN id range start.
+ * @vid_end: location to store the VLAN id range end
+ *
+ * Gets the VLAN id range.
+ *
+ * Returns: %TRUE is the VLAN specifies a range, %FALSE if it is
+ * a single-id VLAN.
+ *
+ * Since: 1.18
+ **/
+gboolean
+nm_bridge_vlan_get_vid_range (const NMBridgeVlan *vlan,
+                              guint16 *vid_start,
+                              guint16 *vid_end)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), 0);
+
+	NM_SET_OUT (vid_start, vlan->vid_start);
+	NM_SET_OUT (vid_end, vlan->vid_end);
+
+	return vlan->vid_start != vlan->vid_end;
+}
+
+/**
+ * nm_bridge_vlan_is_untagged:
+ * @vlan: the #NMBridgeVlan
+ *
+ * Returns whether the VLAN is untagged.
+ *
+ * Returns: %TRUE if the VLAN is untagged, %FALSE otherwise
+ *
+ * Since: 1.18
+ **/
+gboolean
+nm_bridge_vlan_is_untagged (const NMBridgeVlan *vlan)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE);
+
+	return vlan->untagged;
+}
+
+/**
+ * nm_bridge_vlan_is_pvid:
+ * @vlan: the #NMBridgeVlan
+ *
+ * Returns whether the VLAN is the PVID for the port.
+ *
+ * Returns: %TRUE if the VLAN is the PVID
+ *
+ * Since: 1.18
+ **/
+gboolean
+nm_bridge_vlan_is_pvid (const NMBridgeVlan *vlan)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE);
+
+	return vlan->pvid;
+}
+
+/**
+ * nm_bridge_vlan_set_untagged:
+ * @vlan: the #NMBridgeVlan
+ * @value: the new value
+ *
+ * Change the value of the untagged property of the VLAN.
+ *
+ * Since: 1.18
+ **/
+void
+nm_bridge_vlan_set_untagged (NMBridgeVlan *vlan, gboolean value)
+{
+	g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, FALSE));
+
+	vlan->untagged = value;
+}
+
+/**
+ * nm_bridge_vlan_set_pvid:
+ * @vlan: the #NMBridgeVlan
+ * @value: the new value
+ *
+ * Change the value of the PVID property of the VLAN. It
+ * is invalid to set the value to %TRUE for non-single-id
+ * VLANs.
+ *
+ * Since: 1.18
+ **/
+void
+nm_bridge_vlan_set_pvid (NMBridgeVlan *vlan, gboolean value)
+{
+	g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, FALSE));
+	g_return_if_fail (!value || vlan->vid_start == vlan->vid_end);
+
+	vlan->pvid = value;
+}
+
+/**
+ * nm_bridge_vlan_is_sealed:
+ * @vlan: the #NMBridgeVlan instance
+ *
+ * Returns: whether @self is sealed or not.
+ *
+ * Since: 1.18
+ */
+gboolean
+nm_bridge_vlan_is_sealed (const NMBridgeVlan *vlan)
+{
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE);
+
+	return vlan->sealed;
+}
+
+/**
+ * nm_bridge_vlan_seal:
+ * @vlan: the #NMBridgeVlan instance
+ *
+ * Seal the #NMBridgeVlan instance. Afterwards, it is a bug
+ * to call all functions that modify the instance (except ref/unref).
+ * A sealed instance cannot be unsealed again, but you can create
+ * an unsealed copy with nm_bridge_vlan_new_clone().
+ *
+ * Since: 1.18
+ */
+void
+nm_bridge_vlan_seal (NMBridgeVlan *vlan)
+{
+	g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE));
+
+	vlan->sealed = TRUE;
+}
+
+/**
+ * nm_bridge_vlan_new_clone:
+ * @vlan: the #NMBridgeVlan instance to copy
+ *
+ * Returns: (transfer full): a clone of @vlan. This instance
+ *   is always unsealed.
+ *
+ * Since: 1.18
+ */
+NMBridgeVlan *
+nm_bridge_vlan_new_clone (const NMBridgeVlan *vlan)
+{
+	NMBridgeVlan *copy;
+
+	g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL);
+
+	copy = nm_bridge_vlan_new (vlan->vid_start, vlan->vid_end);
+	copy->untagged = vlan->untagged;
+	copy->pvid = vlan->pvid;
+
+	return copy;
+}
+
+void
+_nm_bridge_vlan_str_append_rest (const NMBridgeVlan *vlan,
+                                 GString *string,
+                                 gboolean leading_space)
+{
+	if (nm_bridge_vlan_is_pvid (vlan)) {
+		if (leading_space)
+			g_string_append_c (string, ' ');
+		g_string_append (string, "pvid");
+		leading_space = TRUE;
+	}
+	if (nm_bridge_vlan_is_untagged (vlan)) {
+		if (leading_space)
+			g_string_append_c (string, ' ');
+		g_string_append (string, "untagged");
+		leading_space = TRUE;
+	}
+}
+
+/**
+ * nm_bridge_vlan_to_str:
+ * @vlan: the %NMBridgeVlan
+ * @error: location of the error
+ *
+ * Convert a %NMBridgeVlan to a string.
+ *
+ * Returns: formatted string or %NULL
+ *
+ * Since: 1.18
+ */
+char *
+nm_bridge_vlan_to_str (const NMBridgeVlan *vlan, GError **error)
+{
+	GString *string;
+
+	g_return_val_if_fail (vlan, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	/* The function never fails at the moment, but it might in the
+	 * future if more parameters are added to the object that could
+	 * make it invalid. */
+
+	string = g_string_sized_new (28);
+
+	if (vlan->vid_start == vlan->vid_end)
+		g_string_append_printf (string, "%u", vlan->vid_start);
+	else
+		g_string_append_printf (string, "%u-%u", vlan->vid_start, vlan->vid_end);
+
+	_nm_bridge_vlan_str_append_rest (vlan, string, TRUE);
+
+	return g_string_free (string, FALSE);
+}
+
+/**
+ * nm_bridge_vlan_from_str:
+ * @str: the string representation of a bridge VLAN
+ * @error: location of the error
+ *
+ * Parses the string representation of the queueing
+ * discipline to a %NMBridgeVlan instance.
+ *
+ * Returns: the %NMBridgeVlan or %NULL
+ *
+ * Since: 1.18
+ */
+NMBridgeVlan *
+nm_bridge_vlan_from_str (const char *str, GError **error)
+{
+	NMBridgeVlan *vlan = NULL;
+	gs_free const char **tokens = NULL;
+	guint i, vid_start, vid_end = 0;
+	gboolean pvid = FALSE;
+	gboolean untagged = FALSE;
+	char *c;
+
+	g_return_val_if_fail (str, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	tokens = nm_utils_escaped_tokens_split (str, NM_ASCII_SPACES);
+	if (!tokens || !tokens[0]) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_FAILED,
+		                     "missing VLAN id");
+		return NULL;
+	}
+
+	c = strchr (tokens[0], '-');
+	if (c)
+		*c = '\0';
+
+	vid_start = _nm_utils_ascii_str_to_uint64 (tokens[0],
+	                                           10,
+	                                           NM_BRIDGE_VLAN_VID_MIN,
+	                                           NM_BRIDGE_VLAN_VID_MAX,
+	                                           G_MAXUINT);
+	if (vid_start == G_MAXUINT) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_FAILED,
+		             "invalid VLAN id range start '%s', must be in [1,4094]", tokens[0]);
+		return NULL;
+	}
+
+	if (c) {
+		vid_end = _nm_utils_ascii_str_to_uint64 (c + 1,
+		                                         10,
+		                                         NM_BRIDGE_VLAN_VID_MIN,
+		                                         NM_BRIDGE_VLAN_VID_MAX,
+		                                         G_MAXUINT);
+		if (vid_end == G_MAXUINT) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_FAILED,
+			             "invalid VLAN id range end '%s', must be in [1,4094]", c + 1);
+			return NULL;
+		}
+		if (vid_end < vid_start) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_FAILED,
+			             "invalid VLAN id range %u-%u, start VLAN id must be less than end VLAN id",
+			             vid_start, vid_end);
+			return NULL;
+		}
+	} else
+		vid_end = vid_start;
+
+	for (i = 1; tokens[i]; i++) {
+		if (nm_streq (tokens[i], "pvid")) {
+			if (vid_start != vid_end) {
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_FAILED,
+				                     "a VLAN range can't be a PVID");
+				return NULL;
+			}
+			pvid = TRUE;
+		} else if (nm_streq (tokens[i], "untagged"))
+			untagged = TRUE;
+		else {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_FAILED,
+			             "invalid option '%s'", tokens[i]);
+			return NULL;
+		}
+	}
+
+	vlan = nm_bridge_vlan_new (vid_start, vid_end);
+	nm_bridge_vlan_set_pvid (vlan, pvid);
+	nm_bridge_vlan_set_untagged (vlan, untagged);
+
+	return vlan;
+}
+
+/*****************************************************************************/
+
+static int
+vlan_ptr_cmp (gconstpointer a, gconstpointer b)
+{
+	const NMBridgeVlan *vlan_a = *(const NMBridgeVlan **) a;
+	const NMBridgeVlan *vlan_b = *(const NMBridgeVlan **) b;
+
+	return nm_bridge_vlan_cmp (vlan_a, vlan_b);
+}
+
+gboolean
+_nm_setting_bridge_sort_vlans (NMSettingBridge *setting)
+{
+	NMSettingBridgePrivate *priv;
+	gboolean need_sort = FALSE;
+	guint i;
+
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	for (i = 1; i < priv->vlans->len; i++) {
+		NMBridgeVlan *vlan_prev = priv->vlans->pdata[i - 1];
+		NMBridgeVlan *vlan = priv->vlans->pdata[i];
+
+		if (nm_bridge_vlan_cmp (vlan_prev, vlan) > 0) {
+			need_sort = TRUE;
+			break;
+		}
+	}
+
+	if (need_sort) {
+		g_ptr_array_sort (priv->vlans, vlan_ptr_cmp);
+		_notify (setting, PROP_VLANS);
+	}
+
+	return need_sort;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_setting_bridge_get_mac_address:
  * @setting: the #NMSettingBridge
@@ -200,6 +706,194 @@ nm_setting_bridge_get_multicast_snooping (NMSettingBridge *setting)
 	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_snooping;
 }
 
+/**
+ * nm_setting_bridge_get_vlan_filtering:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:vlan-filtering property of the setting
+ *
+ * Since: 1.18
+ **/
+gboolean
+nm_setting_bridge_get_vlan_filtering (NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_filtering;
+}
+
+/**
+ * nm_setting_bridge_get_vlan_default_pvid:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:vlan-default-pvid property of the setting
+ *
+ * Since: 1.18
+ **/
+guint16
+nm_setting_bridge_get_vlan_default_pvid (NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 1);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_default_pvid;
+}
+
+/**
+ * nm_setting_bridge_add_vlan:
+ * @setting: the #NMSettingBridge
+ * @vlan: the vlan to add
+ *
+ * Appends a new vlan and associated information to the setting.  The
+ * given vlan gets sealed and a reference to it is added.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_bridge_add_vlan (NMSettingBridge *setting,
+                            NMBridgeVlan *vlan)
+{
+	NMSettingBridgePrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTING_BRIDGE (setting));
+	g_return_if_fail (vlan);
+
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	nm_bridge_vlan_seal (vlan);
+	nm_bridge_vlan_ref (vlan);
+
+	g_ptr_array_add (priv->vlans, vlan);
+	_notify (setting, PROP_VLANS);
+}
+
+/**
+ * nm_setting_bridge_get_num_vlans:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the number of VLANs
+ *
+ * Since: 1.18
+ **/
+guint
+nm_setting_bridge_get_num_vlans (NMSettingBridge *setting)
+{
+	NMSettingBridgePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0);
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	return priv->vlans->len;
+}
+
+/**
+ * nm_setting_bridge_get_vlan:
+ * @setting: the #NMSettingBridge
+ * @idx: index number of the VLAN to return
+ *
+ * Returns: (transfer none): the VLAN at index @idx
+ *
+ * Since: 1.18
+ **/
+NMBridgeVlan *
+nm_setting_bridge_get_vlan (NMSettingBridge *setting, guint idx)
+{
+	NMSettingBridgePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), NULL);
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (idx < priv->vlans->len, NULL);
+
+	return priv->vlans->pdata[idx];
+}
+
+/**
+ * nm_setting_bridge_remove_vlan:
+ * @setting: the #NMSettingBridge
+ * @idx: index number of the VLAN.
+ *
+ * Removes the vlan at index @idx.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_bridge_remove_vlan (NMSettingBridge *setting, guint idx)
+{
+	NMSettingBridgePrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTING_BRIDGE (setting));
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	g_return_if_fail (idx < priv->vlans->len);
+
+	g_ptr_array_remove_index (priv->vlans, idx);
+	_notify (setting, PROP_VLANS);
+}
+
+/**
+ * nm_setting_bridge_remove_vlan_by_vid:
+ * @setting: the #NMSettingBridge
+ * @vid_start: the vlan start index
+ * @vid_end: the vlan end index
+ *
+ * Remove the VLAN with range @vid_start to @vid_end.
+ * If @vid_end is zero, it is assumed to be equal to @vid_start
+ * and so the single-id VLAN with id @vid_start is removed.
+ *
+ * Returns: %TRUE if the vlan was found and removed; %FALSE otherwise
+ *
+ * Since: 1.18
+ **/
+gboolean
+nm_setting_bridge_remove_vlan_by_vid (NMSettingBridge *setting,
+                                      guint16 vid_start,
+                                      guint16 vid_end)
+{
+	NMSettingBridgePrivate *priv;
+	NMBridgeVlan *vlan;
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE);
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	if (vid_end == 0)
+		vid_end = vid_start;
+
+	for (i = 0; i < priv->vlans->len; i++) {
+		vlan = (NMBridgeVlan *) priv->vlans->pdata[i];
+		if (vlan->vid_start == vid_start && vlan->vid_end == vid_end) {
+			g_ptr_array_remove_index (priv->vlans, i);
+			_notify (setting, PROP_VLANS);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_bridge_clear_vlans:
+ * @setting: the #NMSettingBridge
+ *
+ * Removes all configured VLANs.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_bridge_clear_vlans (NMSettingBridge *setting)
+{
+	NMSettingBridgePrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTING_BRIDGE (setting));
+	priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	if (priv->vlans->len != 0) {
+		g_ptr_array_set_size (priv->vlans, 0);
+		_notify (setting, PROP_VLANS);
+	}
+}
+
+/*****************************************************************************/
+
 static gboolean
 check_range (guint32 val,
              guint32 min,
@@ -278,7 +972,59 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	return _nm_connection_verify_required_interface_name (connection, error);
+	if (!_nm_connection_verify_required_interface_name (connection, error))
+		return FALSE;
+
+	if (!_nm_utils_bridge_vlan_verify_list (priv->vlans,
+	                                        FALSE,
+	                                        error,
+	                                        NM_SETTING_BRIDGE_SETTING_NAME,
+	                                        NM_SETTING_BRIDGE_VLANS))
+		return FALSE;
+
+	/* Failures from here on are NORMALIZABLE... */
+
+	if (!_nm_utils_bridge_vlan_verify_list (priv->vlans,
+	                                        TRUE,
+	                                        error,
+	                                        NM_SETTING_BRIDGE_SETTING_NAME,
+	                                        NM_SETTING_BRIDGE_VLANS))
+		return NM_SETTING_VERIFY_NORMALIZABLE;
+
+	return TRUE;
+}
+
+static NMTernary
+compare_property (const NMSettInfoSetting *sett_info,
+                  guint property_idx,
+                  NMSetting *setting,
+                  NMSetting *other,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingBridgePrivate *priv_a;
+	NMSettingBridgePrivate *priv_b;
+	guint i;
+
+	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_VLANS)) {
+		if (other) {
+			priv_a = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+			priv_b = NM_SETTING_BRIDGE_GET_PRIVATE (other);
+
+			if (priv_a->vlans->len != priv_b->vlans->len)
+				return FALSE;
+			for (i = 0; i < priv_a->vlans->len; i++) {
+				if (nm_bridge_vlan_cmp (priv_a->vlans->pdata[i], priv_b->vlans->pdata[i]))
+					return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
+	return NM_SETTING_CLASS (nm_setting_bridge_parent_class)->compare_property (sett_info,
+	                                                                            property_idx,
+	                                                                            setting,
+	                                                                            other,
+	                                                                            flags);
 }
 
 /*****************************************************************************/
@@ -318,6 +1064,17 @@ get_property (GObject *object, guint prop_id,
 	case PROP_MULTICAST_SNOOPING:
 		g_value_set_boolean (value, priv->multicast_snooping);
 		break;
+	case PROP_VLAN_FILTERING:
+		g_value_set_boolean (value, priv->vlan_filtering);
+		break;
+	case PROP_VLAN_DEFAULT_PVID:
+		g_value_set_uint (value, priv->vlan_default_pvid);
+		break;
+	case PROP_VLANS:
+		g_value_take_boxed (value, _nm_utils_copy_array (priv->vlans,
+		                                                 (NMUtilsCopyFunc) nm_bridge_vlan_ref,
+		                                                 (GDestroyNotify) nm_bridge_vlan_unref));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -360,6 +1117,18 @@ set_property (GObject *object, guint prop_id,
 	case PROP_MULTICAST_SNOOPING:
 		priv->multicast_snooping = g_value_get_boolean (value);
 		break;
+	case PROP_VLAN_FILTERING:
+		priv->vlan_filtering = g_value_get_boolean (value);
+		break;
+	case PROP_VLAN_DEFAULT_PVID:
+		priv->vlan_default_pvid = g_value_get_uint (value);
+		break;
+	case PROP_VLANS:
+		g_ptr_array_unref (priv->vlans);
+		priv->vlans = _nm_utils_copy_array (g_value_get_boxed (value),
+		                                    (NMUtilsCopyFunc) _nm_bridge_vlan_dup_and_seal,
+		                                    (GDestroyNotify) nm_bridge_vlan_unref);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -371,6 +1140,9 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_setting_bridge_init (NMSettingBridge *setting)
 {
+	NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
+
+	priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
 }
 
 /**
@@ -392,6 +1164,7 @@ finalize (GObject *object)
 	NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (object);
 
 	g_free (priv->mac_address);
+	g_ptr_array_unref (priv->vlans);
 
 	G_OBJECT_CLASS (nm_setting_bridge_parent_class)->finalize (object);
 }
@@ -409,6 +1182,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	object_class->set_property = set_property;
 	object_class->finalize     = finalize;
 
+	setting_class->compare_property = compare_property;
 	setting_class->verify = verify;
 
 	/**
@@ -627,6 +1401,91 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * NMSettingBridge:vlan-filtering:
+	 *
+	 * Control whether VLAN filtering is enabled on the bridge.
+	 *
+	 * Since: 1.18
+	 **/
+	/* ---ifcfg-rh---
+	 * property: vlan-filtering
+	 * variable: BRIDGING_OPTS: vlan_filtering=
+	 * values: 0 or 1
+	 * default: 0
+	 * description: VLAN filtering support.
+	 * ---end---
+	 */
+	obj_properties[PROP_VLAN_FILTERING] =
+	    g_param_spec_boolean (NM_SETTING_BRIDGE_VLAN_FILTERING, "", "",
+	                          FALSE,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT |
+	                          NM_SETTING_PARAM_INFERRABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:vlan-default-pvid:
+	 *
+	 * The default PVID for the ports of the bridge, that is the VLAN id
+	 * assigned to incoming untagged frames.
+	 *
+	 * Since: 1.18
+	 **/
+	/* ---ifcfg-rh---
+	 * property: vlan-default-pvid
+	 * variable: BRIDGING_OPTS: default_pvid=
+	 * values: 0 - 4094
+	 * default: 1
+	 * description: default VLAN PVID.
+	 * ---end---
+	 */
+	obj_properties[PROP_VLAN_DEFAULT_PVID] =
+	    g_param_spec_uint (NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, "", "",
+	                       0, NM_BRIDGE_VLAN_VID_MAX, 1,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_CONSTRUCT |
+	                       NM_SETTING_PARAM_INFERRABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:vlans: (type GPtrArray(NMBridgeVlan))
+	 *
+	 * Array of bridge VLAN objects. In addition to the VLANs
+	 * specified here, the bridge will also have the default-pvid
+	 * VLAN configured  by the bridge.vlan-default-pvid property.
+	 *
+	 * In nmcli the VLAN list can be specified with the following
+	 * syntax:
+	 *
+	 *  $vid [pvid] [untagged] [, $vid [pvid] [untagged]]...
+	 *
+	 * where $vid is either a single id between 1 and 4094 or a
+	 * range, represented as a couple of ids separated by a dash.
+	 *
+	 * Since: 1.18
+	 **/
+	/* ---ifcfg-rh---
+	 * property: vlans
+	 * variable: BRIDGE_VLANS
+	 * description: List of VLANs on the bridge
+	 * example: BRIDGE_VLANS="1 pvid untagged,20,300-400 untagged"
+	 * ---end---
+	 */
+	obj_properties[PROP_VLANS] =
+	    g_param_spec_boxed (NM_SETTING_BRIDGE_VLANS, "", "",
+	                        G_TYPE_PTR_ARRAY,
+	                        G_PARAM_READWRITE |
+	                        NM_SETTING_PARAM_INFERRABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	_properties_override_add_override (properties_override,
+	                                   obj_properties[PROP_VLANS],
+	                                   G_VARIANT_TYPE ("aa{sv}"),
+	                                   _nm_utils_bridge_vlans_to_dbus,
+	                                   _nm_utils_bridge_vlans_from_dbus,
+	                                   NULL);
+
 	/* ---dbus---
 	 * property: interface-name
 	 * format: string