about summary refs log tree commit diff
path: root/libnm-core/nm-setting-ip-config.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-setting-ip-config.c')
-rw-r--r--libnm-core/nm-setting-ip-config.c2465
1 files changed, 2459 insertions, 6 deletions
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index 5250ae43..f362945f 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -25,6 +25,7 @@
 #include "nm-setting-ip-config.h"
 
 #include <arpa/inet.h>
+#include <linux/fib_rules.h>
 
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
@@ -204,7 +205,8 @@ struct NMIPAddress {
  **/
 NMIPAddress *
 nm_ip_address_new (int family,
-                   const char *addr, guint prefix,
+                   const char *addr,
+                   guint prefix,
                    GError **error)
 {
 	NMIPAddress *address;
@@ -242,7 +244,8 @@ nm_ip_address_new (int family,
  **/
 NMIPAddress *
 nm_ip_address_new_binary (int family,
-                          gconstpointer addr, guint prefix,
+                          gconstpointer addr,
+                          guint prefix,
                           GError **error)
 {
 	NMIPAddress *address;
@@ -1113,7 +1116,7 @@ nm_ip_route_set_metric (NMIPRoute *route,
 }
 
 GHashTable *
-_nm_ip_route_get_attributes_direct (NMIPRoute *route)
+_nm_ip_route_get_attributes (NMIPRoute *route)
 {
 	nm_assert (route);
 
@@ -1217,7 +1220,7 @@ static const NMVariantAttributeSpec * const ip_route_attribute_spec[] = {
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_SRC,             G_VARIANT_TYPE_STRING,   TRUE,  TRUE, 'a'),
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_FROM,            G_VARIANT_TYPE_STRING,   FALSE, TRUE, 'p'),
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_TOS,             G_VARIANT_TYPE_BYTE,     TRUE,  FALSE, 0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK,          G_VARIANT_TYPE_BOOLEAN,  TRUE,  FALSE, 0 ),
+	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK,          G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,          G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_CWND,            G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
 	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_INITCWND,        G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
@@ -1384,6 +1387,2109 @@ _nm_ip_route_attribute_validate_all (const NMIPRoute *route)
 
 /*****************************************************************************/
 
+struct NMIPRoutingRule {
+	NMIPAddr from_bin;
+	NMIPAddr to_bin;
+	char *from_str;
+	char *to_str;
+	char *iifname;
+	char *oifname;
+	guint ref_count;
+	guint32 priority;
+	guint32 table;
+	guint32 fwmark;
+	guint32 fwmask;
+	guint16 sport_start;
+	guint16 sport_end;
+	guint16 dport_start;
+	guint16 dport_end;
+	guint8 action;
+	guint8 from_len;
+	guint8 to_len;
+	guint8 tos;
+	guint8 ipproto;
+	bool is_v4:1;
+	bool sealed:1;
+	bool priority_has:1;
+	bool from_has:1;
+	bool from_valid:1;
+	bool to_has:1;
+	bool to_valid:1;
+	bool invert:1;
+};
+
+static NMIPRoutingRule *_ip_routing_rule_dup (const NMIPRoutingRule *rule);
+
+G_DEFINE_BOXED_TYPE (NMIPRoutingRule, nm_ip_routing_rule, _ip_routing_rule_dup, nm_ip_routing_rule_unref)
+
+static gboolean
+NM_IS_IP_ROUTING_RULE (const NMIPRoutingRule *self,
+                       gboolean also_sealed)
+{
+	return    self
+	       && self->ref_count > 0
+	       && (   also_sealed
+	           || !self->sealed);
+}
+
+static int
+_ip_routing_rule_get_addr_family (const NMIPRoutingRule *self)
+{
+	nm_assert (NM_IS_IP_ROUTING_RULE (self, TRUE));
+
+	return self->is_v4 ? AF_INET : AF_INET6;
+}
+
+static int
+_ip_routing_rule_get_addr_size (const NMIPRoutingRule *self)
+{
+	nm_assert (NM_IS_IP_ROUTING_RULE (self, TRUE));
+
+	return self->is_v4 ? sizeof (struct in_addr) : sizeof (struct in6_addr);
+}
+
+static NMIPRoutingRule *
+_ip_routing_rule_dup (const NMIPRoutingRule *rule)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (rule, TRUE), NULL);
+
+	if (rule->sealed)
+		return nm_ip_routing_rule_ref ((NMIPRoutingRule *) rule);
+	return nm_ip_routing_rule_new_clone (rule);
+}
+
+/**
+ * nm_ip_routing_rule_new:
+ * @addr_family: the address family of the routing rule. Must be either
+ *   %AF_INET (2) or %AF_INET6 (10).
+ *
+ * Returns: (transfer full): a newly created rule instance with the
+ *   provided address family. The instance is unsealed.
+ *
+ * Since: 1.18
+ */
+NMIPRoutingRule *
+nm_ip_routing_rule_new (int addr_family)
+{
+	NMIPRoutingRule *self;
+
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
+
+	self = g_slice_new (NMIPRoutingRule);
+	*self = (NMIPRoutingRule) {
+		.ref_count    = 1,
+		.is_v4        = (addr_family == AF_INET),
+		.action       = FR_ACT_TO_TBL,
+		.table        = RT_TABLE_MAIN,
+	};
+	return self;
+}
+
+/**
+ * nm_ip_routing_rule_new_clone:
+ * @rule: the #NMIPRoutingRule to clone.
+ *
+ * Returns: (transfer full): a newly created rule instance with
+ *   the same settings as @rule. Note that the instance will
+ *   always be unsealred.
+ *
+ * Since: 1.18
+ */
+NMIPRoutingRule *
+nm_ip_routing_rule_new_clone (const NMIPRoutingRule *rule)
+{
+	NMIPRoutingRule *self;
+
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (rule, TRUE), NULL);
+
+	self = g_slice_new (NMIPRoutingRule);
+	*self = (NMIPRoutingRule) {
+		.ref_count    = 1,
+		.sealed       = FALSE,
+		.is_v4        = rule->is_v4,
+
+		.priority     = rule->priority,
+		.priority_has = rule->priority_has,
+
+		.invert       = rule->invert,
+
+		.tos          = rule->tos,
+
+		.fwmark       = rule->fwmark,
+		.fwmask       = rule->fwmask,
+
+		.sport_start  = rule->sport_start,
+		.sport_end    = rule->sport_end,
+		.dport_start  = rule->dport_start,
+		.dport_end    = rule->dport_end,
+
+		.ipproto      = rule->ipproto,
+
+		.from_len     = rule->from_len,
+		.from_bin     = rule->from_bin,
+		.from_str     =   (   rule->from_has
+		                   && !rule->from_valid)
+		                ? g_strdup (rule->from_str)
+		                : NULL,
+		.from_has     = rule->from_has,
+		.from_valid   = rule->from_valid,
+
+		.to_len       = rule->to_len,
+		.to_bin       = rule->to_bin,
+		.to_str       =   (   rule->to_has
+		                   && !rule->to_valid)
+		                ? g_strdup (rule->to_str)
+		                : NULL,
+		.to_has       = rule->to_has,
+		.to_valid     = rule->to_valid,
+
+		.iifname      = g_strdup (rule->iifname),
+		.oifname      = g_strdup (rule->oifname),
+
+		.action       = rule->action,
+		.table        = rule->table,
+	};
+	return self;
+}
+
+/**
+ * nm_ip_routing_rule_ref:
+ * @self: (allow-none): the #NMIPRoutingRule instance
+ *
+ * Increases the reference count of the instance.
+ * This is not thread-safe.
+ *
+ * Returns: (transfer full): the @self argument with incremented
+ *  reference count.
+ *
+ * Since: 1.18
+ */
+NMIPRoutingRule *
+nm_ip_routing_rule_ref (NMIPRoutingRule *self)
+{
+	if (!self)
+		return NULL;
+
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	nm_assert (self->ref_count < G_MAXUINT);
+	self->ref_count++;
+	return self;
+}
+
+/**
+ * nm_ip_routing_rule_unref:
+ * @self: (allow-none): the #NMIPRoutingRule instance
+ *
+ * Decreases the reference count of the instance and destroys
+ * the instance if the reference count reaches zero.
+ * This is not thread-safe.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_unref (NMIPRoutingRule *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE));
+
+	if (--self->ref_count > 0)
+		return;
+
+	g_free (self->from_str);
+	g_free (self->to_str);
+	g_free (self->iifname);
+	g_free (self->oifname);
+
+	g_slice_free (NMIPRoutingRule, self);
+}
+
+/**
+ * nm_ip_routing_rule_is_sealed:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: whether @self is sealed. Once sealed, an instance
+ *   cannot be modified nor unsealed.
+ *
+ * Since: 1.18
+ */
+gboolean
+nm_ip_routing_rule_is_sealed (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), FALSE);
+
+	return self->sealed;
+}
+
+/**
+ * nm_ip_routing_rule_seal:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Seals the routing rule. Afterwards, the instance can no longer be
+ * modfied, and it is a bug to call any of the accessors that would
+ * modify the rule. If @self was already sealed, this has no effect.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_seal (NMIPRoutingRule *self)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE));
+
+	self->sealed = TRUE;
+}
+
+/**
+ * nm_ip_routing_rule_get_addr_family:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the address family of the rule. Either %AF_INET or %AF_INET6.
+ *
+ * Since: 1.18
+ */
+int
+nm_ip_routing_rule_get_addr_family (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), AF_UNSPEC);
+
+	return _ip_routing_rule_get_addr_family (self);
+}
+
+/**
+ * nm_ip_routing_rule_get_priority:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the priority. A valid priority is in the range from
+ *   0 to %G_MAXUINT32. If unset, -1 is returned.
+ *
+ * Since: 1.18
+ */
+gint64
+nm_ip_routing_rule_get_priority (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), -1);
+
+	return   self->priority_has
+	       ? (gint64) self->priority
+	       : (gint64) -1;
+}
+
+/**
+ * nm_ip_routing_rule_set_priority:
+ * @self: the #NMIPRoutingRule instance
+ * @priority: the priority to set
+ *
+ * A valid priority ranges from 0 to %G_MAXUINT32. "-1" is also allowed
+ * to reset the priority. It is a bug calling this function with any
+ * other value.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_priority (NMIPRoutingRule *self, gint64 priority)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	if (   priority >= 0
+	    && priority <= (gint64) G_MAXUINT32) {
+		self->priority = (guint32) priority;
+		self->priority_has = TRUE;
+	} else {
+		g_return_if_fail (priority == -1);
+		self->priority = 0;
+		self->priority_has = FALSE;
+	}
+}
+
+/**
+ * nm_ip_routing_rule_get_invert:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the "invert" setting of the rule.
+ *
+ * Since: 1.18
+ */
+gboolean
+nm_ip_routing_rule_get_invert (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), FALSE);
+
+	return self->invert;
+}
+
+/**
+ * nm_ip_routing_rule_set_invert:
+ * @self: the #NMIPRoutingRule instance
+ * @invert: the new value to set
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_invert (NMIPRoutingRule *self, gboolean invert)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->invert = invert;
+}
+
+/**
+ * nm_ip_routing_rule_get_from_len:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the set prefix length for the from/src parameter.
+ *
+ * Since: 1.18
+ */
+guint8
+nm_ip_routing_rule_get_from_len (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->from_len;
+}
+
+/**
+ * nm_ip_routing_rule_get_from:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: (transfer none): the set from/src parameter or
+ *   %NULL, if no value is set.
+ *
+ * Since: 1.18
+ */
+const char *
+nm_ip_routing_rule_get_from (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	if (!self->from_has)
+		return NULL;
+	if (!self->from_str) {
+		nm_assert (self->from_valid);
+		((NMIPRoutingRule *) self)->from_str = nm_utils_inet_ntop_dup (_ip_routing_rule_get_addr_family (self),
+		                                                               &self->from_bin);
+	}
+	return self->from_str;
+}
+
+const NMIPAddr *
+nm_ip_routing_rule_get_from_bin (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	return   (   self->from_has
+	          && self->from_valid)
+	       ? &self->from_bin
+	       : NULL;
+}
+
+void
+nm_ip_routing_rule_set_from_bin (NMIPRoutingRule *self,
+                                 gconstpointer from,
+                                 guint8 len)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	nm_clear_g_free (&self->from_str);
+
+	if (!from) {
+		self->from_has = FALSE;
+		self->from_len = len;
+		return;
+	}
+
+	self->from_has = TRUE;
+	self->from_len = len;
+	self->from_valid = TRUE;
+	nm_ip_addr_set (_ip_routing_rule_get_addr_family (self),
+	                &self->from_bin,
+	                from);
+}
+
+/**
+ * nm_ip_routing_rule_set_from:
+ * @self: the #NMIPRoutingRule instance
+ * @from: (allow-none): the from/src address to set.
+ *   The address family must match.
+ * @len: the corresponding prefix length of the address.
+ *
+ * Setting invalid values is accepted, but will later fail
+ * during nm_ip_routing_rule_validate().
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_from (NMIPRoutingRule *self,
+                             const char *from,
+                             guint8 len)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	if (!from) {
+		nm_clear_g_free (&self->from_str);
+		self->from_has = FALSE;
+		self->from_len = len;
+		return;
+	}
+
+	nm_clear_g_free (&self->from_str);
+	self->from_has = TRUE;
+	self->from_len = len;
+	self->from_valid = nm_utils_parse_inaddr_bin (_ip_routing_rule_get_addr_family (self),
+	                                              from,
+	                                              NULL,
+	                                              &self->from_bin);
+	if (!self->from_valid)
+		self->from_str = g_strdup (from);
+}
+
+/**
+ * nm_ip_routing_rule_get_to_len:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the set prefix length for the to/dst parameter.
+ *
+ * Since: 1.18
+ */
+guint8
+nm_ip_routing_rule_get_to_len (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->to_len;
+}
+
+/**
+ * nm_ip_routing_rule_get_to:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: (transfer none): the set to/dst parameter or
+ *   %NULL, if no value is set.
+ *
+ * Since: 1.18
+ */
+const char *
+nm_ip_routing_rule_get_to (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	if (!self->to_has)
+		return NULL;
+	if (!self->to_str) {
+		nm_assert (self->to_valid);
+		((NMIPRoutingRule *) self)->to_str = nm_utils_inet_ntop_dup (_ip_routing_rule_get_addr_family (self),
+		                                                             &self->to_bin);
+	}
+	return self->to_str;
+}
+
+const NMIPAddr *
+nm_ip_routing_rule_get_to_bin (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	return   (   self->to_has
+	          && self->to_valid)
+	       ? &self->to_bin
+	       : NULL;
+}
+
+void
+nm_ip_routing_rule_set_to_bin (NMIPRoutingRule *self,
+                               gconstpointer to,
+                               guint8 len)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	nm_clear_g_free (&self->to_str);
+
+	if (!to) {
+		self->to_has = FALSE;
+		self->to_len = len;
+		return;
+	}
+
+	self->to_has = TRUE;
+	self->to_len = len;
+	self->to_valid = TRUE;
+	nm_ip_addr_set (_ip_routing_rule_get_addr_family (self),
+	                &self->to_bin,
+	                to);
+}
+
+/**
+ * nm_ip_routing_rule_set_to:
+ * @self: the #NMIPRoutingRule instance
+ * @to: (allow-none): the to/dst address to set.
+ *   The address family must match.
+ * @len: the corresponding prefix length of the address.
+ *   If @to is %NULL, this valid is ignored.
+ *
+ * Setting invalid values is accepted, but will later fail
+ * during nm_ip_routing_rule_validate().
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_to (NMIPRoutingRule *self,
+                           const char *to,
+                           guint8 len)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	if (!to) {
+		nm_clear_g_free (&self->to_str);
+		self->to_has = FALSE;
+		self->to_len = len;
+		return;
+	}
+
+	nm_clear_g_free (&self->to_str);
+	self->to_has = TRUE;
+	self->to_len = len;
+	self->to_valid = nm_utils_parse_inaddr_bin (_ip_routing_rule_get_addr_family (self),
+	                                            to,
+	                                            NULL,
+	                                            &self->to_bin);
+	if (!self->to_valid)
+		self->to_str = g_strdup (to);
+}
+
+/**
+ * nm_ip_routing_rule_get_tos:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the tos of the rule.
+ *
+ * Since: 1.18
+ */
+guint8
+nm_ip_routing_rule_get_tos (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->tos;
+}
+
+/**
+ * nm_ip_routing_rule_set_tos:
+ * @self: the #NMIPRoutingRule instance
+ * @tos: the tos to set
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_tos (NMIPRoutingRule *self, guint8 tos)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->tos = tos;
+}
+
+/**
+ * nm_ip_routing_rule_get_ipproto:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the ipproto of the rule.
+ *
+ * Since: 1.18
+ */
+guint8
+nm_ip_routing_rule_get_ipproto (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->ipproto;
+}
+
+/**
+ * nm_ip_routing_rule_set_ipproto:
+ * @self: the #NMIPRoutingRule instance
+ * @ipproto: the ipproto to set
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_ipproto (NMIPRoutingRule *self, guint8 ipproto)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->ipproto = ipproto;
+}
+
+/**
+ * nm_ip_routing_rule_get_source_port_start:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the source port start setting.
+ *
+ * Since: 1.18
+ */
+guint16
+nm_ip_routing_rule_get_source_port_start (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->sport_start;
+}
+
+/**
+ * nm_ip_routing_rule_get_source_port_end:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the source port end setting.
+ *
+ * Since: 1.18
+ */
+guint16
+nm_ip_routing_rule_get_source_port_end (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->sport_end;
+}
+
+/**
+ * nm_ip_routing_rule_set_source_port:
+ * @self: the #NMIPRoutingRule instance
+ * @start: the start port to set.
+ * @end: the end port to set.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_source_port (NMIPRoutingRule *self, guint16 start, guint16 end)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->sport_start = start;
+	self->sport_end   = end;
+}
+
+/**
+ * nm_ip_routing_rule_get_destination_port_start:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the destination port start setting.
+ *
+ * Since: 1.18
+ */
+guint16
+nm_ip_routing_rule_get_destination_port_start (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->dport_start;
+}
+
+/**
+ * nm_ip_routing_rule_get_destination_port_end:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the destination port end setting.
+ *
+ * Since: 1.18
+ */
+guint16
+nm_ip_routing_rule_get_destination_port_end (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->dport_end;
+}
+
+/**
+ * nm_ip_routing_rule_set_destination_port:
+ * @self: the #NMIPRoutingRule instance
+ * @start: the start port to set.
+ * @end: the end port to set.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_destination_port (NMIPRoutingRule *self, guint16 start, guint16 end)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->dport_start = start;
+	self->dport_end   = end;
+}
+
+/**
+ * nm_ip_routing_rule_get_fwmark:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the fwmark setting.
+ *
+ * Since: 1.18
+ */
+guint32
+nm_ip_routing_rule_get_fwmark (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->fwmark;
+}
+
+/**
+ * nm_ip_routing_rule_get_fwmask:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the fwmask setting.
+ *
+ * Since: 1.18
+ */
+guint32
+nm_ip_routing_rule_get_fwmask (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->fwmask;
+}
+
+/**
+ * nm_ip_routing_rule_set_fwmark:
+ * @self: the #NMIPRoutingRule instance
+ * @fwmark: the fwmark
+ * @fwmask: the fwmask
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_fwmark (NMIPRoutingRule *self, guint32 fwmark, guint32 fwmask)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->fwmark = fwmark;
+	self->fwmask = fwmask;
+}
+
+/**
+ * nm_ip_routing_rule_get_iifname:
+ * @self: the #NMIPRoutingRule instance.
+ *
+ * Returns: (transfer none): the set iifname or %NULL if unset.
+ *
+ * Since: 1.18
+ */
+const char *
+nm_ip_routing_rule_get_iifname (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	return self->iifname;
+}
+
+gboolean
+nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self,
+                                    gboolean iif /* or else oif */,
+                                    char out_xifname[static 16 /* IFNAMSIZ */])
+{
+	gs_free gpointer bin_to_free = NULL;
+	const char *xifname;
+	gconstpointer bin;
+	gsize len;
+
+	nm_assert (NM_IS_IP_ROUTING_RULE (self, TRUE));
+	nm_assert (out_xifname);
+
+	xifname = iif ? self->iifname : self->oifname;
+
+	if (!xifname)
+		return FALSE;
+
+	bin = nm_utils_buf_utf8safe_unescape (xifname, &len, &bin_to_free);
+
+	strncpy (out_xifname, bin, 16 /* IFNAMSIZ */);
+	out_xifname[15] = '\0';
+	return TRUE;
+}
+
+/**
+ * nm_ip_routing_rule_set_iifname:
+ * @self: the #NMIPRoutingRule instance.
+ * @iifname: (allow-none): the iifname to set or %NULL to unset.
+ *
+ * The name supports C backslash escaping for non-UTF-8 characters.
+ * Note that nm_ip_routing_rule_from_string() too uses backslash
+ * escaping when tokenizing the words by whitespace. So, in string
+ * representation you'd get double backslashs.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_iifname (NMIPRoutingRule *self, const char *iifname)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	g_free (self->iifname);
+	self->iifname = g_strdup (iifname);
+}
+
+/**
+ * nm_ip_routing_rule_get_oifname:
+ * @self: the #NMIPRoutingRule instance.
+ *
+ * Returns: (transfer none): the set oifname or %NULL if unset.
+ *
+ * Since: 1.18
+ */
+const char *
+nm_ip_routing_rule_get_oifname (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	return self->oifname;
+}
+
+/**
+ * nm_ip_routing_rule_set_oifname:
+ * @self: the #NMIPRoutingRule instance.
+ * @oifname: (allow-none): the oifname to set or %NULL to unset.
+ *
+ * The name supports C backslash escaping for non-UTF-8 characters.
+ * Note that nm_ip_routing_rule_from_string() too uses backslash
+ * escaping when tokenizing the words by whitespace. So, in string
+ * representation you'd get double backslashs.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_oifname (NMIPRoutingRule *self, const char *oifname)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	g_free (self->oifname);
+	self->oifname = g_strdup (oifname);
+}
+
+/**
+ * nm_ip_routing_rule_get_action:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the set action.
+ *
+ * Since: 1.18
+ */
+guint8
+nm_ip_routing_rule_get_action (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->action;
+}
+
+/**
+ * nm_ip_routing_rule_set_action:
+ * @self: the #NMIPRoutingRule instance
+ * @action: the action to set
+ *
+ * Note that currently only certain actions are allowed. nm_ip_routing_rule_validate()
+ * will reject unsupported actions as invalid.
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_action (NMIPRoutingRule *self, guint8 action)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->action = action;
+}
+
+/**
+ * nm_ip_routing_rule_get_table:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the set table.
+ *
+ * Since: 1.18
+ */
+guint32
+nm_ip_routing_rule_get_table (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), 0);
+
+	return self->table;
+}
+
+/**
+ * nm_ip_routing_rule_set_table:
+ * @self: the #NMIPRoutingRule instance
+ * @table: the table to set
+ *
+ * Since: 1.18
+ */
+void
+nm_ip_routing_rule_set_table (NMIPRoutingRule *self, guint32 table)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->table = table;
+}
+
+/**
+ * nm_ip_routing_rule_cmp:
+ * @rule: (allow-none): the #NMIPRoutingRule instance to compare
+ * @other: (allow-none): the other #NMIPRoutingRule instance to compare
+ *
+ * Returns: zero, a positive, or a negative integer to indicate
+ *   equality or how the arguments compare.
+ *
+ * Since: 1.18
+ */
+int
+nm_ip_routing_rule_cmp (const NMIPRoutingRule *rule,
+                        const NMIPRoutingRule *other)
+{
+	NM_CMP_SELF (rule, other);
+
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (rule, TRUE), 0);
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (other, TRUE), 0);
+
+	NM_CMP_FIELD_UNSAFE (rule, other, priority_has);
+	if (rule->priority_has)
+		NM_CMP_FIELD (rule, other, priority);
+
+	NM_CMP_FIELD_UNSAFE (rule, other, is_v4);
+
+	NM_CMP_FIELD_UNSAFE (rule, other, invert);
+
+	NM_CMP_FIELD (rule, other, tos);
+
+	NM_CMP_FIELD (rule, other, fwmark);
+	NM_CMP_FIELD (rule, other, fwmask);
+
+	NM_CMP_FIELD (rule, other, action);
+
+	NM_CMP_FIELD (rule, other, table);
+
+	NM_CMP_FIELD (rule, other, sport_start);
+	NM_CMP_FIELD (rule, other, sport_end);
+	NM_CMP_FIELD (rule, other, dport_start);
+	NM_CMP_FIELD (rule, other, dport_end);
+
+	NM_CMP_FIELD (rule, other, ipproto);
+
+	/* We compare the plain strings, not the binary values after utf8safe unescaping.
+	 *
+	 * The reason is, that the rules differ already when the direct strings differ, not
+	 * only when the unescaped names differ. */
+	NM_CMP_FIELD_STR0 (rule, other, iifname);
+	NM_CMP_FIELD_STR0 (rule, other, oifname);
+
+	NM_CMP_FIELD (rule, other, from_len);
+
+	NM_CMP_FIELD_UNSAFE (rule, other, from_has);
+	if (rule->from_has) {
+		NM_CMP_FIELD_UNSAFE (rule, other, from_valid);
+		if (rule->from_valid) {
+			NM_CMP_RETURN (memcmp (&rule->from_bin,
+			                       &other->from_bin,
+			                       _ip_routing_rule_get_addr_size (rule)));
+		} else
+			NM_CMP_FIELD_STR (rule, other, from_str);
+	}
+
+	NM_CMP_FIELD (rule, other, to_len);
+
+	NM_CMP_FIELD_UNSAFE (rule, other, to_has);
+	if (rule->to_has) {
+		NM_CMP_FIELD_UNSAFE (rule, other, to_valid);
+		if (rule->to_valid) {
+			NM_CMP_RETURN (memcmp (&rule->to_bin,
+			                       &other->to_bin,
+			                       _ip_routing_rule_get_addr_size (rule)));
+		} else
+			NM_CMP_FIELD_STR (rule, other, to_str);
+	}
+
+	return 0;
+}
+
+static gboolean
+_rr_xport_range_valid (guint16 xport_start, guint16 xport_end)
+{
+	if (xport_start == 0)
+		return (xport_end == 0);
+
+	return    xport_start <= xport_end
+	       && xport_end < 0xFFFFu;
+}
+
+static gboolean
+_rr_xport_range_parse (char *str, gint64 *out_start, guint16 *out_end)
+{
+	guint16 start, end;
+	gint64 i64;
+	char *s;
+
+	s = strchr (str, '-');
+	if (s)
+		*(s++) = '\0';
+
+	i64 = _nm_utils_ascii_str_to_int64 (str, 10, 0, 0xFFFF, -1);
+	if (i64 == -1)
+		return FALSE;
+
+	start = i64;
+	if (s) {
+		i64 = _nm_utils_ascii_str_to_int64 (s, 10, 0, 0xFFFF, -1);
+		if (i64 == -1)
+			return FALSE;
+		end = i64;
+	} else
+		end = start;
+
+	*out_start = start;
+	*out_end = end;
+	return TRUE;
+}
+
+/**
+ * nm_ip_routing_rule_validate:
+ * @self: the #NMIPRoutingRule instance to validate
+ * @error: (allow-none) (out): the error result if validation fails.
+ *
+ * Returns: %TRUE if the rule validates.
+ *
+ * Since: 1.18
+ */
+gboolean
+nm_ip_routing_rule_validate (const NMIPRoutingRule *self,
+                             GError **error)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	/* Kernel may be more flexible about validating. We do a strict validation
+	 * here and reject certain settings eagerly. We can always relax it later. */
+
+	if (!self->priority_has) {
+		/* iproute2 accepts not specifying the priority, in which case kernel will select
+		 * an unused priority. We don't allow for that, and will always require the user to
+		 * select a priority.
+		 *
+		 * Note that if the user selects priority 0 or a non-unique priority, this is problematic
+		 * due to kernel bugs rh#1685816 and rh#1685816. It may result in NetworkManager wrongly being
+		 * unable to add a rule or deleting the wrong rule.
+		 * This problem is not at all specific to the priority, it affects all rules that
+		 * have default values which confuse kernel. But setting a unique priority avoids
+		 * this problem nicely. */
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid priority"));
+		return FALSE;
+	}
+
+	if (NM_IN_SET (self->action, FR_ACT_TO_TBL)) {
+		if (self->table == 0) {
+			/* with IPv4, kernel allows a table (in RTM_NEWRULE) of zero to automatically select
+			 * an unused table. We don't. The user needs to specify the table.
+			 *
+			 * For IPv6, kernel doesn't allow a table of zero, so we are consistent here. */
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("missing table"));
+			return FALSE;
+		}
+	} else {
+		/* whitelist the actions that we currently. */
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid action"));
+		return FALSE;
+	}
+
+	if (self->from_len == 0) {
+		if (self->from_has) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("has from/src but the prefix-length is zero"));
+			return FALSE;
+		}
+	} else if (   self->from_len > 0
+	           && self->from_len <= 8 * _ip_routing_rule_get_addr_size (self)) {
+		if (!self->from_has) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("missing from/src for a non zero prefix-length"));
+			return FALSE;
+		}
+		if (!self->from_valid) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("invalid from/src"));
+			return FALSE;
+		}
+	} else {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid prefix length for from/src"));
+		return FALSE;
+	}
+
+	if (self->to_len == 0) {
+		if (self->to_has) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("has to/dst but the prefix-length is zero"));
+			return FALSE;
+		}
+	} else if (   self->to_len > 0
+	           && self->to_len <= 8 * _ip_routing_rule_get_addr_size (self)) {
+		if (!self->to_has) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("missing to/dst for a non zero prefix-length"));
+			return FALSE;
+		}
+		if (!self->to_valid) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("invalid to/dst"));
+			return FALSE;
+		}
+	} else {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid prefix length for to/dst"));
+		return FALSE;
+	}
+
+	if (   self->iifname
+	    && (   !g_utf8_validate (self->iifname, -1, NULL)
+	        || !nm_utils_is_valid_iface_name_utf8safe (self->iifname))) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid iifname"));
+		return FALSE;
+	}
+
+	if (   self->oifname
+	    && (   !g_utf8_validate (self->oifname, -1, NULL)
+	        || !nm_utils_is_valid_iface_name_utf8safe (self->oifname))) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid oifname"));
+		return FALSE;
+	}
+
+	if (!_rr_xport_range_valid (self->sport_start, self->sport_end)) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid source port range"));
+		return FALSE;
+	}
+
+	if (!_rr_xport_range_valid (self->dport_start, self->dport_end)) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid destination port range"));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef enum {
+	RR_DBUS_ATTR_ACTION,
+	RR_DBUS_ATTR_DPORT_END,
+	RR_DBUS_ATTR_DPORT_START,
+	RR_DBUS_ATTR_FAMILY,
+	RR_DBUS_ATTR_FROM,
+	RR_DBUS_ATTR_FROM_LEN,
+	RR_DBUS_ATTR_FWMARK,
+	RR_DBUS_ATTR_FWMASK,
+	RR_DBUS_ATTR_IIFNAME,
+	RR_DBUS_ATTR_INVERT,
+	RR_DBUS_ATTR_IPPROTO,
+	RR_DBUS_ATTR_OIFNAME,
+	RR_DBUS_ATTR_PRIORITY,
+	RR_DBUS_ATTR_SPORT_END,
+	RR_DBUS_ATTR_SPORT_START,
+	RR_DBUS_ATTR_TABLE,
+	RR_DBUS_ATTR_TO,
+	RR_DBUS_ATTR_TOS,
+	RR_DBUS_ATTR_TO_LEN,
+
+	_RR_DBUS_ATTR_NUM,
+} RRDbusAttr;
+
+typedef struct {
+	const char *name;
+	const GVariantType *dbus_type;
+} RRDbusData;
+
+static const RRDbusData rr_dbus_data[_RR_DBUS_ATTR_NUM] = {
+#define _D(attr, _name, type) [attr] = { .name = _name, .dbus_type = type, }
+	_D (RR_DBUS_ATTR_ACTION,      NM_IP_ROUTING_RULE_ATTR_ACTION,      G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_DPORT_END,   NM_IP_ROUTING_RULE_ATTR_DPORT_END,   G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_DPORT_START, NM_IP_ROUTING_RULE_ATTR_DPORT_START, G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_FAMILY,      NM_IP_ROUTING_RULE_ATTR_FAMILY,      G_VARIANT_TYPE_INT32),
+	_D (RR_DBUS_ATTR_FROM,        NM_IP_ROUTING_RULE_ATTR_FROM,        G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_FROM_LEN,    NM_IP_ROUTING_RULE_ATTR_FROM_LEN,    G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_FWMARK,      NM_IP_ROUTING_RULE_ATTR_FWMARK,      G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_FWMASK,      NM_IP_ROUTING_RULE_ATTR_FWMASK,      G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_IIFNAME,     NM_IP_ROUTING_RULE_ATTR_IIFNAME,     G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_INVERT,      NM_IP_ROUTING_RULE_ATTR_INVERT,      G_VARIANT_TYPE_BOOLEAN),
+	_D (RR_DBUS_ATTR_IPPROTO,     NM_IP_ROUTING_RULE_ATTR_IPPROTO,     G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_OIFNAME,     NM_IP_ROUTING_RULE_ATTR_OIFNAME,     G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_PRIORITY,    NM_IP_ROUTING_RULE_ATTR_PRIORITY,    G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_SPORT_END,   NM_IP_ROUTING_RULE_ATTR_SPORT_END,   G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_SPORT_START, NM_IP_ROUTING_RULE_ATTR_SPORT_START, G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_TABLE,       NM_IP_ROUTING_RULE_ATTR_TABLE,       G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_TO,          NM_IP_ROUTING_RULE_ATTR_TO,          G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_TOS,         NM_IP_ROUTING_RULE_ATTR_TOS,         G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_TO_LEN,      NM_IP_ROUTING_RULE_ATTR_TO_LEN,      G_VARIANT_TYPE_BYTE),
+#undef _D
+};
+
+static void
+_rr_variants_free (GVariant *(*p_variants)[])
+{
+	int i;
+
+	for (i = 0; i < _RR_DBUS_ATTR_NUM; i++) {
+		if ((*p_variants)[i])
+			g_variant_unref ((*p_variants)[i]);
+	}
+}
+
+NMIPRoutingRule *
+nm_ip_routing_rule_from_dbus (GVariant *variant,
+                              gboolean strict,
+                              GError **error)
+{
+	nm_auto (_rr_variants_free) GVariant *variants[_RR_DBUS_ATTR_NUM] = { };
+	nm_auto_unref_ip_routing_rule NMIPRoutingRule *self = NULL;
+	RRDbusAttr attr;
+	GVariantIter iter;
+	const char *iter_key;
+	GVariant *iter_val;
+	int addr_family;
+	int i;
+
+	g_variant_iter_init (&iter, variant);
+
+#if NM_MORE_ASSERTS > 10
+	for (attr = 0; attr < _RR_DBUS_ATTR_NUM; attr++) {
+		nm_assert (rr_dbus_data[attr].name);
+		nm_assert (g_variant_type_string_is_valid ((const char *) rr_dbus_data[attr].dbus_type));
+	}
+#endif
+
+	while (g_variant_iter_next (&iter, "{&sv}", &iter_key, &iter_val)) {
+		gs_unref_variant GVariant *iter_val2 = iter_val;
+
+		for (attr = 0; attr < _RR_DBUS_ATTR_NUM; attr++) {
+			if (nm_streq (iter_key, rr_dbus_data[attr].name)) {
+				if (variants[attr]) {
+					if (strict) {
+						g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+						             _("duplicate key %s"),
+						             iter_key);
+						return NULL;
+					}
+					g_variant_unref (variants[attr]);
+				}
+				variants[attr] = g_steal_pointer (&iter_val2);
+				break;
+			}
+		}
+
+		if (   attr >= _RR_DBUS_ATTR_NUM
+		    && strict) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("invalid key \"%s\""),
+			             iter_key);
+			return NULL;
+		}
+	}
+
+	for (attr = 0; attr < _RR_DBUS_ATTR_NUM; attr++) {
+		if (!variants[attr])
+			continue;
+		if (!g_variant_is_of_type (variants[attr], rr_dbus_data[attr].dbus_type)) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("invalid variant type '%s' for \"%s\""),
+			             (const char *) rr_dbus_data[attr].dbus_type,
+			             rr_dbus_data[attr].name);
+			return NULL;
+		}
+	}
+
+	if (!variants[RR_DBUS_ATTR_FAMILY]) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("missing \""NM_IP_ROUTING_RULE_ATTR_FAMILY"\""));
+		return NULL;
+	}
+	addr_family = g_variant_get_int32 (variants[RR_DBUS_ATTR_FAMILY]);
+	if (!NM_IN_SET (addr_family, AF_INET, AF_INET6)) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("invalid \""NM_IP_ROUTING_RULE_ATTR_FAMILY"\""));
+		return NULL;
+	}
+
+	self = nm_ip_routing_rule_new (addr_family);
+
+	if (variants[RR_DBUS_ATTR_PRIORITY])
+		nm_ip_routing_rule_set_priority (self, g_variant_get_uint32 (variants[RR_DBUS_ATTR_PRIORITY]));
+
+	if (variants[RR_DBUS_ATTR_INVERT])
+		nm_ip_routing_rule_set_invert (self, g_variant_get_boolean (variants[RR_DBUS_ATTR_INVERT]));
+
+	if (variants[RR_DBUS_ATTR_TOS])
+		nm_ip_routing_rule_set_tos (self, g_variant_get_byte (variants[RR_DBUS_ATTR_TOS]));
+
+	if (variants[RR_DBUS_ATTR_IPPROTO])
+		nm_ip_routing_rule_set_ipproto (self, g_variant_get_byte (variants[RR_DBUS_ATTR_IPPROTO]));
+
+	for (i = 0; i < 2; i++) {
+		GVariant *v_start = variants[i ? RR_DBUS_ATTR_SPORT_START : RR_DBUS_ATTR_DPORT_START];
+		GVariant *v_end   = variants[i ? RR_DBUS_ATTR_SPORT_END   : RR_DBUS_ATTR_DPORT_END];
+		guint16 start, end;
+
+		if (!v_start && !v_end)
+			continue;
+
+		/* if start or end is missing, it defaults to the other parameter, respectively. */
+		if (v_start)
+			start = g_variant_get_uint16 (v_start);
+		else
+			start = g_variant_get_uint16 (v_end);
+		if (v_end)
+			end = g_variant_get_uint16 (v_end);
+		else
+			end = g_variant_get_uint16 (v_start);
+
+		if (i)
+			nm_ip_routing_rule_set_source_port (self, start, end);
+		else
+			nm_ip_routing_rule_set_destination_port (self, start, end);
+	}
+
+	if (   variants[RR_DBUS_ATTR_FWMARK]
+	    || variants[RR_DBUS_ATTR_FWMASK]) {
+		nm_ip_routing_rule_set_fwmark (self,
+		                               variants[RR_DBUS_ATTR_FWMARK] ? g_variant_get_uint32 (variants[RR_DBUS_ATTR_FWMARK]) : 0u,
+		                               variants[RR_DBUS_ATTR_FWMASK] ? g_variant_get_uint32 (variants[RR_DBUS_ATTR_FWMASK]) : 0u);
+	}
+
+	if (   variants[RR_DBUS_ATTR_FROM]
+	    || variants[RR_DBUS_ATTR_FROM_LEN]) {
+		nm_ip_routing_rule_set_from (self,
+		                             variants[RR_DBUS_ATTR_FROM]     ? g_variant_get_string (variants[RR_DBUS_ATTR_FROM], NULL) : NULL,
+		                             variants[RR_DBUS_ATTR_FROM_LEN] ? g_variant_get_byte   (variants[RR_DBUS_ATTR_FROM_LEN])   : 0u);
+	}
+
+	if (   variants[RR_DBUS_ATTR_TO]
+	    || variants[RR_DBUS_ATTR_TO_LEN]) {
+		nm_ip_routing_rule_set_to (self,
+		                           variants[RR_DBUS_ATTR_TO]     ? g_variant_get_string (variants[RR_DBUS_ATTR_TO], NULL) : NULL,
+		                           variants[RR_DBUS_ATTR_TO_LEN] ? g_variant_get_byte   (variants[RR_DBUS_ATTR_TO_LEN])   : 0u);
+	}
+
+	if (variants[RR_DBUS_ATTR_IIFNAME])
+		nm_ip_routing_rule_set_iifname (self, g_variant_get_string (variants[RR_DBUS_ATTR_IIFNAME], NULL));
+
+	if (variants[RR_DBUS_ATTR_OIFNAME])
+		nm_ip_routing_rule_set_oifname (self, g_variant_get_string (variants[RR_DBUS_ATTR_OIFNAME], NULL));
+
+	if (variants[RR_DBUS_ATTR_ACTION])
+		nm_ip_routing_rule_set_action (self, g_variant_get_byte (variants[RR_DBUS_ATTR_ACTION]));
+
+	if (variants[RR_DBUS_ATTR_TABLE])
+		nm_ip_routing_rule_set_table (self, g_variant_get_uint32 (variants[RR_DBUS_ATTR_TABLE]));
+
+	if (   strict
+	    && !nm_ip_routing_rule_validate (self, error))
+		return NULL;
+
+	return g_steal_pointer (&self);
+}
+
+static void
+_rr_to_dbus_add (GVariantBuilder *builder,
+                 RRDbusAttr attr,
+                 GVariant *value)
+{
+	nm_assert (builder);
+	nm_assert (value);
+	nm_assert (g_variant_is_floating (value));
+	nm_assert (g_variant_is_of_type (value, rr_dbus_data[attr].dbus_type));
+
+	g_variant_builder_add (builder,
+	                       "{sv}",
+	                       rr_dbus_data[attr].name,
+	                       value);
+}
+
+GVariant *
+nm_ip_routing_rule_to_dbus (const NMIPRoutingRule *self)
+{
+	GVariantBuilder builder;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	_rr_to_dbus_add (&builder, RR_DBUS_ATTR_FAMILY, g_variant_new_int32 (_ip_routing_rule_get_addr_family (self)));
+
+	if (self->invert)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_INVERT, g_variant_new_boolean (TRUE));
+
+	if (self->priority_has)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_PRIORITY, g_variant_new_uint32 (self->priority));
+
+	if (self->tos != 0)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_TOS, g_variant_new_byte (self->tos));
+
+	if (self->ipproto != 0)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_IPPROTO, g_variant_new_byte (self->ipproto));
+
+	if (self->fwmark != 0)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_FWMARK, g_variant_new_uint32 (self->fwmark));
+
+	if (self->fwmask != 0)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_FWMASK, g_variant_new_uint32 (self->fwmask));
+
+	if (   self->sport_start != 0
+	    || self->sport_end != 0) {
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_SPORT_START, g_variant_new_uint16 (self->sport_start));
+		if (self->sport_start != self->sport_end)
+			_rr_to_dbus_add (&builder, RR_DBUS_ATTR_SPORT_END, g_variant_new_uint16 (self->sport_end));
+	}
+
+	if (   self->dport_start != 0
+	    || self->dport_end != 0) {
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_DPORT_START, g_variant_new_uint16 (self->dport_start));
+		if (self->dport_start != self->dport_end)
+			_rr_to_dbus_add (&builder, RR_DBUS_ATTR_DPORT_END, g_variant_new_uint16 (self->dport_end));
+	}
+
+	if (   self->from_has
+	    || self->from_len != 0) {
+		_rr_to_dbus_add (&builder,
+		                 RR_DBUS_ATTR_FROM,
+		                 g_variant_new_string (   self->from_str
+		                                       ?: nm_utils_inet_ntop (_ip_routing_rule_get_addr_family (self),
+		                                                              &self->from_bin,
+		                                                              addr_str)));
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_FROM_LEN, g_variant_new_byte (self->from_len));
+	}
+
+	if (   self->to_has
+	    || self->to_len != 0) {
+		_rr_to_dbus_add (&builder,
+		                 RR_DBUS_ATTR_TO,
+		                 g_variant_new_string (   self->to_str
+		                                       ?: nm_utils_inet_ntop (_ip_routing_rule_get_addr_family (self),
+		                                                              &self->to_bin,
+		                                                              addr_str)));
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_TO_LEN, g_variant_new_byte (self->to_len));
+	}
+
+	if (self->iifname)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_IIFNAME, g_variant_new_string (self->iifname));
+
+	if (self->oifname)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_OIFNAME, g_variant_new_string (self->oifname));
+
+	if (self->action != FR_ACT_TO_TBL)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_ACTION, g_variant_new_byte (self->action));
+
+	if (self->table != 0)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_TABLE, g_variant_new_uint32 (self->table));
+
+	return g_variant_builder_end (&builder);;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_rr_string_validate (gboolean for_from /* or else to-string */,
+                     NMIPRoutingRuleAsStringFlags to_string_flags,
+                     GHashTable *extra_args,
+                     GError **error)
+{
+	if (NM_FLAGS_ANY (to_string_flags, ~(  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET
+	                                     | NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6
+	                                     | NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE))) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		                     _("Unsupported to-string-flags argument"));
+		return FALSE;
+	}
+
+	if (   extra_args
+	    && g_hash_table_size (extra_args) > 0) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		                     _("Unsupported extra-argument"));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int
+_rr_string_addr_family_from_flags (NMIPRoutingRuleAsStringFlags to_string_flags)
+{
+	if (NM_FLAGS_HAS (to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)) {
+		if (!NM_FLAGS_HAS (to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6))
+			return AF_INET;
+	} else if (NM_FLAGS_HAS (to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6))
+		return AF_INET6;
+	return AF_UNSPEC;
+}
+
+/**
+ * nm_ip_routing_rule_from_string:
+ * @str: the string representation to convert to an #NMIPRoutingRule
+ * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the
+ *   string conversion.
+ * @extra_args: (allow-none): extra arguments for controlling the string
+ *   conversion. Currently not extra arguments are supported.
+ * @error: (allow-none) (out): the error reason.
+ *
+ * Returns: (transfer full): the new #NMIPRoutingRule or %NULL on error.
+ *
+ * Since: 1.18
+ */
+NMIPRoutingRule *
+nm_ip_routing_rule_from_string (const char *str,
+                                NMIPRoutingRuleAsStringFlags to_string_flags,
+                                GHashTable *extra_args,
+                                GError **error)
+{
+	nm_auto_unref_ip_routing_rule NMIPRoutingRule *self = NULL;
+	gs_free const char **tokens = NULL;
+	gsize i_token;
+	gboolean any_words = FALSE;
+	char *word0 = NULL;
+	char *word1 = NULL;
+	char *word_from = NULL;
+	char *word_to = NULL;
+	char *word_iifname = NULL;
+	char *word_oifname = NULL;
+	gint64 i64_priority = -1;
+	gint64 i64_table = -1;
+	gint64 i64_tos = -1;
+	gint64 i64_fwmark = -1;
+	gint64 i64_fwmask = -1;
+	gint64 i64_sport_start = -1;
+	gint64 i64_ipproto = -1;
+	guint16 sport_end = 0;
+	gint64 i64_dport_start = -1;
+	guint16 dport_end = 0;
+	gboolean val_invert = FALSE;
+	int addr_family = AF_UNSPEC;
+	NMIPAddr val_from = { };
+	NMIPAddr val_to = { };
+	int val_from_len = -1;
+	int val_to_len = -1;
+	char *s;
+
+	g_return_val_if_fail (str, NULL);
+
+	if (!_rr_string_validate (TRUE, to_string_flags, extra_args, error))
+		return NULL;
+
+	/* NM_IP_ROUTING_RULE_TO_STRING_TYPE_IPROUTE gives a string representation that is
+	 * partly compatibly with iproute2. That is, the part after `ip -[46] rule add $ARGS`.
+	 * There are differences though:
+	 *
+	 * - trying to convert an invalid rule to string may not be possible. The reason is for
+	 *   example that an invalid rule can have nm_ip_routing_rule_get_from() like "bogus",
+	 *   but we don't write that as "from bogus". In general, if you try to convert an invalid
+	 *   rule to string, the operation may fail or the result may itself not be parsable.
+	 *   Of course, valid rules can be converted to string and read back the same (round-trip).
+	 *
+	 * - iproute2 in may regards is flexible about the command lines. For example
+	 *   - for tables it accepts table names from /etc/iproute2/rt_tables
+	 *   - key names like "preference" can be abbreviated to "prefe", we don't do that.
+	 *   - the "preference"/"priority" may be unspecified, in which kernel automatically
+	 *     chooses an unused priority (during `ip rule add`). We don't allow for that, the
+	 *     priority must be explicitly set.
+	 *
+	 * - iproute2 does not support any escaping. Well, it's the shell that supports quoting
+	 *   and escaping and splits the command line. We need to split the command line ourself,
+	 *   but we don't support full shell quotation.
+	 *   from-string tokenizes words at (ASCII) whitespaces (removing the whitespaces).
+	 *   It also supports backslash escaping (e.g. to contain whitespace), but it does
+	 *   not support special escape sequences. Values are taken literally, meaning
+	 *   "\n\ \111" gives results in "n 111".
+	 *   The strings really shouldn't contain any special characters that require escaping,
+	 *   but that's the rule.
+	 *   This also goes together with the @allow_escaping parameter of nm_utils_strsplit_set().
+	 *   If you concatenate multiple rule expressions with a delimiter, the delimiter inside
+	 *   each word can be backslash escaped, and nm_utils_strsplit_set(allow_escaping=TRUE) will
+	 *   properly split the words, preserving the backslashes, which then will be removed by
+	 *   nm_ip_routing_rule_from_string().
+	 */
+
+	addr_family = _rr_string_addr_family_from_flags (to_string_flags);
+
+	tokens = nm_utils_escaped_tokens_split (str, NM_ASCII_SPACES);
+	for (i_token = 0; tokens && tokens[i_token]; i_token++) {
+		char *str_word = (char *) tokens[i_token];
+
+		any_words = TRUE;
+		if (!word0)
+			word0 = str_word;
+		else {
+			nm_assert (!word1);
+			word1 = str_word;
+		}
+
+		/* iproute2 matches keywords with any partial prefix. We don't allow
+		 * for that flexiblity. */
+
+		if (NM_IN_STRSET (word0, "from")) {
+			if (!word1)
+				continue;
+			if (word_from)
+				goto next_fail_word0_duplicate_key;
+			word_from = word1;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "to")) {
+			if (!word1)
+				continue;
+			if (word_to)
+				goto next_fail_word0_duplicate_key;
+			word_to = word1;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "not")) {
+			/* we accept multiple "not" specifiers. */
+			val_invert = TRUE;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "priority",
+		                         "order",
+		                         "pref",
+		                         "preference")) {
+			if (!word1)
+				continue;
+			if (i64_priority != -1)
+				goto next_fail_word0_duplicate_key;
+			i64_priority = _nm_utils_ascii_str_to_int64 (word1, 0, 0, G_MAXUINT32, -1);
+			if (i64_priority == -1)
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "table",
+		                         "lookup")) {
+			if (!word1)
+				continue;
+			if (i64_table != -1)
+				goto next_fail_word0_duplicate_key;
+			i64_table = _nm_utils_ascii_str_to_int64 (word1, 0, 1, G_MAXUINT32, -1);
+			if (i64_table == -1)
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "tos",
+		                         "dsfield")) {
+			if (!word1)
+				continue;
+			if (i64_tos != -1)
+				goto next_fail_word0_duplicate_key;
+			i64_tos = _nm_utils_ascii_str_to_int64 (word1, 16, 0, G_MAXUINT8, -1);
+			if (i64_tos == -1)
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "ipproto")) {
+			if (!word1)
+				continue;
+			if (i64_ipproto != -1)
+				goto next_fail_word0_duplicate_key;
+			i64_ipproto = _nm_utils_ascii_str_to_int64 (word1, 10, 0, G_MAXUINT8, -1);
+			if (i64_ipproto == -1)
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "sport")) {
+			if (!word1)
+				continue;
+			if (i64_sport_start != -1)
+				goto next_fail_word0_duplicate_key;
+			if (!_rr_xport_range_parse (word1, &i64_sport_start, &sport_end))
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "dport")) {
+			if (!word1)
+				continue;
+			if (i64_dport_start != -1)
+				goto next_fail_word0_duplicate_key;
+			if (!_rr_xport_range_parse (word1, &i64_dport_start, &dport_end))
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "fwmark")) {
+			if (!word1)
+				continue;
+			if (i64_fwmark != -1)
+				goto next_fail_word0_duplicate_key;
+			s = strchr (word1, '/');
+			if (s)
+				*(s++) = '\0';
+			i64_fwmark = _nm_utils_ascii_str_to_int64 (word1, 0, 0, G_MAXUINT32, -1);
+			if (i64_fwmark == -1)
+				goto next_fail_word1_invalid_value;
+			if (s) {
+				i64_fwmask = _nm_utils_ascii_str_to_int64 (s, 0, 0, G_MAXUINT32, -1);
+				if (i64_fwmask == -1)
+					goto next_fail_word1_invalid_value;
+			} else
+				i64_fwmask = 0xFFFFFFFFu;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "iif",
+		                         "dev")) {
+			if (!word1)
+				continue;
+			if (word_iifname)
+				goto next_fail_word0_duplicate_key;
+			word_iifname = word1;
+			goto next_words_consumed;
+		}
+		if (NM_IN_STRSET (word0, "oif")) {
+			if (!word1)
+				continue;
+			if (word_oifname)
+				goto next_fail_word0_duplicate_key;
+			word_oifname = word1;
+			goto next_words_consumed;
+		}
+
+		/* also the action is still unsupported. For the moment, we only support
+		 * FR_ACT_TO_TBL, which is the default (by not expressing it on the command
+		 * line). */
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("unsupported key \"%s\""),
+		             word0);
+		return FALSE;
+next_fail_word0_duplicate_key:
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("duplicate key \"%s\""),
+		             word0);
+		return FALSE;
+next_fail_word1_invalid_value:
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("invalid value for \"%s\""),
+		             word0);
+		return FALSE;
+next_words_consumed:
+		word0 = NULL;
+		word1 = NULL;
+	}
+
+	if (!any_words) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		                     _("empty text does not describe a rule"));
+		return FALSE;
+	}
+
+	if (word0) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("missing argument for \"%s\""),
+		             word0);
+		return FALSE;
+	}
+
+	if (!NM_IN_STRSET (word_from, NULL, "all")) {
+		if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
+		                                       word_from,
+		                                       &addr_family,
+		                                       &val_from,
+		                                       &val_from_len)) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+			                     _("invalid \"from\" part"));
+			return FALSE;
+		}
+		if (val_from_len == -1)
+			val_from_len = nm_utils_addr_family_to_size (addr_family) * 8;
+	}
+
+	if (!NM_IN_STRSET (word_to, NULL, "all")) {
+		if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
+		                                       word_to,
+		                                       &addr_family,
+		                                       &val_to,
+		                                       &val_to_len)) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+			                     _("invalid \"to\" part"));
+			return FALSE;
+		}
+		if (val_to_len == -1)
+			val_to_len = nm_utils_addr_family_to_size (addr_family) * 8;
+	}
+
+	if (!NM_IN_SET (addr_family, AF_INET, AF_INET6)) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("cannot detect address family for rule"));
+		return FALSE;
+	}
+
+	self = nm_ip_routing_rule_new (addr_family);
+
+	if (val_invert)
+		self->invert = TRUE;
+
+	if (i64_priority != -1)
+		nm_ip_routing_rule_set_priority (self, i64_priority);
+
+	if (i64_tos != -1)
+		nm_ip_routing_rule_set_tos (self, i64_tos);
+
+	if (i64_ipproto != -1)
+		nm_ip_routing_rule_set_ipproto (self, i64_ipproto);
+
+	if (i64_fwmark != -1)
+		nm_ip_routing_rule_set_fwmark (self, i64_fwmark, i64_fwmask);
+
+	if (i64_sport_start != -1)
+		nm_ip_routing_rule_set_source_port (self, i64_sport_start, sport_end);
+
+	if (i64_dport_start != -1)
+		nm_ip_routing_rule_set_destination_port (self, i64_dport_start, dport_end);
+
+	if (   val_from_len > 0
+	    || (   val_from_len == 0
+	        && !nm_ip_addr_is_null (addr_family, &val_from))) {
+		nm_ip_routing_rule_set_from_bin (self,
+		                                 &val_from,
+		                                 val_from_len);
+	}
+
+	if (   val_to_len > 0
+	    || (   val_to_len == 0
+	        && !nm_ip_addr_is_null (addr_family, &val_to))) {
+		nm_ip_routing_rule_set_to_bin (self,
+		                               &val_to,
+		                               val_to_len);
+	}
+
+	if (word_iifname)
+		nm_ip_routing_rule_set_iifname (self, word_iifname);
+
+	if (word_oifname)
+		nm_ip_routing_rule_set_oifname (self, word_oifname);
+
+	if (i64_table != -1)
+		nm_ip_routing_rule_set_table (self, i64_table);
+
+	if (NM_FLAGS_HAS (to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE)) {
+		gs_free_error GError *local = NULL;
+
+		if (!nm_ip_routing_rule_validate (self, &local)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+			             _("rule is invalid: %s"),
+			             local->message);
+			return NULL;
+		}
+	}
+
+	return g_steal_pointer (&self);
+}
+
+static void
+_rr_string_append_inet_addr (GString *str,
+                             gboolean is_from /* or else is-to */,
+                             gboolean required,
+                             int addr_family,
+                             const NMIPAddr *addr_bin,
+                             guint8 addr_len)
+{
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+
+	if (addr_len == 0) {
+		if (required) {
+			g_string_append_printf (nm_gstring_add_space_delimiter (str),
+			                        "%s %s/0",
+			                        is_from ? "from" : "to",
+			                          (addr_family == AF_INET)
+			                        ? "0.0.0.0"
+			                        : "::");
+		}
+		return;
+	}
+
+	g_string_append_printf (nm_gstring_add_space_delimiter (str),
+	                        "%s %s",
+	                        is_from ? "from" : "to",
+	                        nm_utils_inet_ntop (addr_family,
+	                                            addr_bin,
+	                                            addr_str));
+	if (addr_len != nm_utils_addr_family_to_size (addr_family) * 8) {
+		g_string_append_printf (str,
+		                        "/%u",
+		                        addr_len);
+	}
+}
+
+/**
+ * nm_ip_routing_rule_to_string:
+ * @self: the #NMIPRoutingRule instance to convert to string.
+ * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the
+ *   string conversion.
+ * @extra_args: (allow-none): extra arguments for controlling the string
+ *   conversion. Currently not extra arguments are supported.
+ * @error: (allow-none) (out): the error reason.
+ *
+ * Returns: (transfer full): the string representation or %NULL on error.
+ *
+ * Since: 1.18
+ */
+char *
+nm_ip_routing_rule_to_string (const NMIPRoutingRule *self,
+                              NMIPRoutingRuleAsStringFlags to_string_flags,
+                              GHashTable *extra_args,
+                              GError **error)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	int addr_family;
+
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), NULL);
+
+	if (!_rr_string_validate (FALSE, to_string_flags, extra_args, error))
+		return NULL;
+
+	addr_family = nm_ip_routing_rule_get_addr_family (self);
+
+	if (!NM_IN_SET (_rr_string_addr_family_from_flags (to_string_flags),
+	                AF_UNSPEC,
+	                addr_family)) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		                     _("invalid address family"));
+		return NULL;
+	}
+
+	/* It is only guaranteed that valid rules can be expressed as string.
+	 *
+	 * Still, unless requested proceed to convert to string without validating and
+	 * hope for the best.
+	 *
+	 * That is, because self->from_str might contain an invalid IP address (indicated
+	 * by self->from_valid). But we don't support serializing such arbitrary strings
+	 * as "from %s". */
+	if (NM_FLAGS_HAS (to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE)) {
+		gs_free_error GError *local = NULL;
+
+		if (!nm_ip_routing_rule_validate (self, &local)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+			             _("rule is invalid: %s"),
+			             local->message);
+			return NULL;
+		}
+	}
+
+	str = g_string_sized_new (30);
+
+	if (self->invert)
+		g_string_append (str, "not");
+
+	if (self->priority_has) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "priority %u",
+		                        (guint) self->priority);
+	}
+
+	_rr_string_append_inet_addr (str,
+	                             TRUE,
+	                             (   !self->to_has
+	                              || !self->to_valid),
+	                             addr_family,
+	                             &self->from_bin,
+	                               (self->from_has && self->from_valid)
+	                             ? self->from_len
+	                             : 0);
+
+	_rr_string_append_inet_addr (str,
+	                             FALSE,
+	                             FALSE,
+	                             addr_family,
+	                             &self->to_bin,
+	                               (self->to_has && self->to_valid)
+	                             ? self->to_len
+	                             : 0);
+
+	if (self->tos != 0) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "tos 0x%02x",
+		                        (guint) self->tos);
+	}
+
+	if (self->ipproto != 0) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "ipproto %u",
+		                        (guint) self->ipproto);
+	}
+
+	if (   self->fwmark != 0
+	    || self->fwmask != 0) {
+		if (self->fwmark != 0) {
+			g_string_append_printf (nm_gstring_add_space_delimiter (str),
+			                        "fwmark 0x%x",
+			                        self->fwmark);
+		} else {
+			g_string_append_printf (nm_gstring_add_space_delimiter (str),
+			                        "fwmark 0");
+		}
+		if (self->fwmask != 0xFFFFFFFFu) {
+			if (self->fwmask != 0)
+				g_string_append_printf (str, "/0x%x", self->fwmask);
+			else
+				g_string_append_printf (str, "/0");
+		}
+	}
+
+	if (   self->sport_start != 0
+	    || self->sport_end != 0) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "sport %u",
+		                        self->sport_start);
+		if (self->sport_start != self->sport_end) {
+			g_string_append_printf (str,
+			                        "-%u",
+			                        self->sport_end);
+		}
+	}
+
+	if (   self->dport_start != 0
+	    || self->dport_end != 0) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "dport %u",
+		                        self->dport_start);
+		if (self->dport_start != self->dport_end) {
+			g_string_append_printf (str,
+			                        "-%u",
+			                        self->dport_end);
+		}
+	}
+
+	if (self->iifname) {
+		g_string_append (nm_gstring_add_space_delimiter (str),
+		                 "iif ");
+		nm_utils_escaped_tokens_escape_gstr (self->iifname,
+		                                     NM_ASCII_SPACES,
+		                                     str);
+	}
+
+	if (self->oifname) {
+		g_string_append (nm_gstring_add_space_delimiter (str),
+		                 "oif ");
+		nm_utils_escaped_tokens_escape_gstr (self->oifname,
+		                                     NM_ASCII_SPACES,
+		                                     str);
+	}
+
+	if (self->table != 0) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "table %u",
+		                        (guint) self->table);
+	}
+
+	return g_string_free (g_steal_pointer (&str), FALSE);
+}
+
+/*****************************************************************************/
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingIPConfig,
 	PROP_METHOD,
 	PROP_DNS,
@@ -1413,6 +3519,7 @@ typedef struct {
 	int dns_priority;
 	GPtrArray *addresses;  /* array of NMIPAddress */
 	GPtrArray *routes;     /* array of NMIPRoute */
+	GPtrArray *routing_rules;
 	gint64 route_metric;
 	guint32 route_table;
 	char *gateway;
@@ -2314,6 +4421,225 @@ nm_setting_ip_config_get_route_table (NMSettingIPConfig *setting)
 	return NM_SETTING_IP_CONFIG_GET_PRIVATE (setting)->route_table;
 }
 
+/*****************************************************************************/
+
+static void
+_routing_rules_notify (NMSettingIPConfig *setting)
+{
+	_nm_setting_emit_property_changed (NM_SETTING (setting));
+}
+
+/**
+ * nm_setting_ip_config_get_num_routing_rules:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns: the number of configured routing rules
+ *
+ * Since: 1.18
+ **/
+guint
+nm_setting_ip_config_get_num_routing_rules (NMSettingIPConfig *setting)
+{
+	NMSettingIPConfigPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTING_IP_CONFIG (setting), 0);
+
+	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+	return priv->routing_rules ? priv->routing_rules->len : 0u;
+}
+
+/**
+ * nm_setting_ip_config_get_routing_rule:
+ * @setting: the #NMSettingIPConfig
+ * @idx: index number of the routing_rule to return
+ *
+ * Returns: (transfer none): the routing rule at index @idx
+ *
+ * Since: 1.18
+ **/
+NMIPRoutingRule *
+nm_setting_ip_config_get_routing_rule (NMSettingIPConfig *setting, guint idx)
+{
+	NMSettingIPConfigPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTING_IP_CONFIG (setting), NULL);
+
+	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (priv->routing_rules && idx < priv->routing_rules->len, NULL);
+
+	return priv->routing_rules->pdata[idx];
+}
+
+/**
+ * nm_setting_ip_config_add_routing_rule:
+ * @setting: the #NMSettingIPConfig
+ * @routing_rule: the #NMIPRoutingRule to add. The address family
+ *   of the added rule must be compatible with the setting.
+ *
+ * Appends a new routing-rule and associated information to the setting. The
+ * given routing rules gets sealed and the reference count is incremented.
+ * The function does not check whether an identical rule already exists
+ * and always appends the rule to the end of the list.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_ip_config_add_routing_rule (NMSettingIPConfig *setting,
+                                       NMIPRoutingRule *routing_rule)
+{
+	NMSettingIPConfigPrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTING_IP_CONFIG (setting));
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (routing_rule, TRUE));
+	g_return_if_fail (_ip_routing_rule_get_addr_family (routing_rule) == NM_SETTING_IP_CONFIG_GET_FAMILY (setting));
+
+	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+
+	if (!priv->routing_rules)
+		priv->routing_rules = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_routing_rule_unref);
+
+	nm_ip_routing_rule_seal (routing_rule);
+	g_ptr_array_add (priv->routing_rules, nm_ip_routing_rule_ref (routing_rule));
+	_routing_rules_notify (setting);
+}
+
+/**
+ * nm_setting_ip_config_remove_routing_rule:
+ * @setting: the #NMSettingIPConfig
+ * @idx: index number of the routing_rule
+ *
+ * Removes the routing_rule at index @idx.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_ip_config_remove_routing_rule (NMSettingIPConfig *setting,
+                                          guint idx)
+{
+	NMSettingIPConfigPrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTING_IP_CONFIG (setting));
+
+	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+	g_return_if_fail (priv->routing_rules && idx < priv->routing_rules->len);
+
+	g_ptr_array_remove_index (priv->routing_rules, idx);
+	_routing_rules_notify (setting);
+}
+
+/**
+ * nm_setting_ip_config_clear_routing_rules:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Removes all configured routing rules.
+ *
+ * Since: 1.18
+ **/
+void
+nm_setting_ip_config_clear_routing_rules (NMSettingIPConfig *setting)
+{
+	NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+
+	g_return_if_fail (NM_IS_SETTING_IP_CONFIG (setting));
+
+	if (   priv->routing_rules
+	    && priv->routing_rules->len > 0) {
+		g_ptr_array_set_size (priv->routing_rules, 0);
+		_routing_rules_notify (setting);
+	}
+}
+
+static GVariant *
+_routing_rules_dbus_only_synth (const NMSettInfoSetting *sett_info,
+                                guint property_idx,
+                                NMConnection *connection,
+                                NMSetting *setting,
+                                NMConnectionSerializationFlags flags)
+{
+	NMSettingIPConfig *self = NM_SETTING_IP_CONFIG (setting);
+	NMSettingIPConfigPrivate *priv;
+	GVariantBuilder builder;
+	gboolean any = FALSE;
+	guint i;
+
+	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (self);
+
+	if (   !priv->routing_rules
+	    || priv->routing_rules->len == 0)
+		return NULL;
+
+	for (i = 0; i < priv->routing_rules->len; i++) {
+		GVariant *variant;
+
+		variant = nm_ip_routing_rule_to_dbus (priv->routing_rules->pdata[i]);
+		if (!variant)
+			continue;
+
+		if (!any) {
+			any = TRUE;
+			g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+		}
+		g_variant_builder_add (&builder, "@a{sv}", variant);
+	}
+
+	return any ? g_variant_builder_end (&builder) : NULL;
+}
+
+static gboolean
+_routing_rules_dbus_only_set (NMSetting     *setting,
+                              GVariant      *connection_dict,
+                              const char    *property,
+                              GVariant      *value,
+                              NMSettingParseFlags parse_flags,
+                              GError       **error)
+{
+	GVariantIter iter_rules;
+	GVariant *rule_var;
+	guint i_rule;
+	gboolean success = FALSE;
+	gboolean rules_changed = FALSE;
+
+	nm_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")));
+
+	g_variant_iter_init (&iter_rules, value);
+
+	i_rule = 0;
+	while (g_variant_iter_next (&iter_rules, "@a{sv}", &rule_var)) {
+		_nm_unused gs_unref_variant GVariant *rule_var_unref = rule_var;
+		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+		gs_free_error GError *local = NULL;
+
+		i_rule++;
+
+		rule = nm_ip_routing_rule_from_dbus (rule_var,
+		                                     NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT),
+		                                     &local);
+		if (!rule) {
+			if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+				             _("rule #%u is invalid: %s"),
+				             i_rule,
+				             local->message);
+				goto out;
+			}
+			continue;
+		}
+
+		nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule);
+		rules_changed = TRUE;
+	}
+
+	success = TRUE;
+
+out:
+	if (rules_changed)
+		_routing_rules_notify (NM_SETTING_IP_CONFIG (setting));
+	return success;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_setting_ip_config_get_ignore_auto_routes:
  * @setting: the #NMSettingIPConfig
@@ -2600,6 +4926,33 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
+	if (priv->routing_rules) {
+		for (i = 0; i < priv->routing_rules->len; i++) {
+			NMIPRoutingRule *rule = priv->routing_rules->pdata[i];
+			gs_free_error GError *local = NULL;
+
+			if (_ip_routing_rule_get_addr_family (rule) != NM_SETTING_IP_CONFIG_GET_FAMILY (setting)) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("%u. rule has wrong address-family"),
+				             i + 1);
+				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_IP_CONFIG_ROUTING_RULES);
+				return FALSE;
+			}
+			if (!nm_ip_routing_rule_validate (rule, &local)) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("%u. rule is invalid: %s"),
+				             i + 1,
+				             local->message);
+				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_IP_CONFIG_ROUTES);
+				return FALSE;
+			}
+		}
+	}
+
 	if (priv->gateway && priv->never_default) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
@@ -2654,6 +5007,24 @@ compare_property (const NMSettInfoSetting *sett_info,
 		return TRUE;
 	}
 
+	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ROUTING_RULES)) {
+		if (other) {
+			guint n;
+
+			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+
+			n = (a_priv->routing_rules) ? a_priv->routing_rules->len : 0u;
+			if (n != (b_priv->routing_rules ? b_priv->routing_rules->len : 0u))
+				return FALSE;
+			for (i = 0; i < n; i++) {
+				if (nm_ip_routing_rule_cmp (a_priv->routing_rules->pdata[i], b_priv->routing_rules->pdata[i]) != 0)
+					return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
 	return NM_SETTING_CLASS (nm_setting_ip_config_parent_class)->compare_property (sett_info,
 	                                                                               property_idx,
 	                                                                               setting,
@@ -2661,6 +5032,72 @@ compare_property (const NMSettInfoSetting *sett_info,
 	                                                                               flags);
 }
 
+static void
+duplicate_copy_properties (const NMSettInfoSetting *sett_info,
+                           NMSetting *src,
+                           NMSetting *dst)
+{
+	NMSettingIPConfigPrivate *priv_src = NM_SETTING_IP_CONFIG_GET_PRIVATE (src);
+	NMSettingIPConfigPrivate *priv_dst = NM_SETTING_IP_CONFIG_GET_PRIVATE (dst);
+	guint i;
+	gboolean changed = FALSE;
+
+	NM_SETTING_CLASS (nm_setting_ip_config_parent_class)->duplicate_copy_properties (sett_info,
+	                                                                                 src,
+	                                                                                 dst);
+
+	if (   priv_dst->routing_rules
+	    && priv_dst->routing_rules->len > 0) {
+		changed = TRUE;
+		g_ptr_array_set_size (priv_dst->routing_rules, 0);
+	}
+	if (   priv_src->routing_rules
+	    && priv_src->routing_rules->len > 0) {
+		changed = TRUE;
+		if (!priv_dst->routing_rules)
+			priv_dst->routing_rules = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_routing_rule_unref);
+		for (i = 0; i < priv_src->routing_rules->len; i++) {
+			g_ptr_array_add (priv_dst->routing_rules,
+			                 nm_ip_routing_rule_ref (priv_src->routing_rules->pdata[i]));
+		}
+	}
+	if (changed)
+		_routing_rules_notify (NM_SETTING_IP_CONFIG (dst));
+}
+
+static void
+enumerate_values (const NMSettInfoProperty *property_info,
+                  NMSetting *setting,
+                  NMSettingValueIterFn func,
+                  gpointer user_data)
+{
+	if (nm_streq (property_info->name, NM_SETTING_IP_CONFIG_ROUTING_RULES)) {
+		NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+		nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+		GPtrArray *ptr = NULL;
+		guint i;
+
+		if (priv->routing_rules && priv->routing_rules->len > 0) {
+			ptr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_routing_rule_unref);
+			for (i = 0; i < priv->routing_rules->len; i++)
+				g_ptr_array_add (ptr, nm_ip_routing_rule_ref (priv->routing_rules->pdata[i]));
+		}
+		g_value_init (&value, G_TYPE_PTR_ARRAY);
+		g_value_take_boxed (&value, ptr);
+		func (setting,
+		      property_info->name,
+		      &value,
+		      0,
+		      user_data);
+		return;
+	}
+
+	NM_SETTING_CLASS (nm_setting_ip_config_parent_class)->enumerate_values (property_info,
+	                                                                        setting,
+	                                                                        func,
+	                                                                        user_data);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2693,6 +5130,18 @@ _nm_sett_info_property_override_create_array_ip_config (void)
 	                                   ip_gateway_set,
 	                                   NULL);
 
+	/* ---dbus---
+	 * property: routing-rules
+	 * format: array of 'a{sv}'
+	 * description: Array of dictionaries for routing rules.
+	 * ---end---
+	 */
+	_properties_override_add_dbus_only (properties_override,
+	                                    NM_SETTING_IP_CONFIG_ROUTING_RULES,
+	                                    G_VARIANT_TYPE ("aa{sv}"),
+	                                    _routing_rules_dbus_only_synth,
+	                                    _routing_rules_dbus_only_set);
+
 	return properties_override;
 }
 
@@ -2900,6 +5349,8 @@ finalize (GObject *object)
 		g_ptr_array_unref (priv->dns_options);
 	g_ptr_array_unref (priv->addresses);
 	g_ptr_array_unref (priv->routes);
+	if (priv->routing_rules)
+		g_ptr_array_unref (priv->routing_rules);
 
 	G_OBJECT_CLASS (nm_setting_ip_config_parent_class)->finalize (object);
 }
@@ -2916,8 +5367,10 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	object_class->set_property = set_property;
 	object_class->finalize     = finalize;
 
-	setting_class->verify           = verify;
-	setting_class->compare_property = compare_property;
+	setting_class->verify                    = verify;
+	setting_class->compare_property          = compare_property;
+	setting_class->duplicate_copy_properties = duplicate_copy_properties;
+	setting_class->enumerate_values          = enumerate_values;
 
 	/**
 	 * NMSettingIPConfig:method: