diff options
author | Thomas Haller <thaller@redhat.com> | 2019-03-13 09:18:49 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-03-27 16:23:30 +0100 |
commit | 7fb23b0a62a0dfe038622a1559e60c97a6739aa0 (patch) | |
tree | 4d5e3ff9ec39d30f6dfaa7d5f14176bdbcc418fc | |
parent | 71e40f519d65ae8781db2b92d0651f7275922311 (diff) |
libnm: add NMIPRoutingRule API
Add NMIPRoutingRule API with a few basic rule properties. More
properties will be added later as we want to support them.
Also, add to/from functions for string/GVariant representations.
These will be needed to persist/load/exchange rules.
The to-string format follows the `ip rule add` syntax, with the aim
to be partially compatible. Full compatibility is not possible though,
for various reasons (see code comment).
-rw-r--r-- | libnm-core/nm-core-internal.h | 41 | ||||
-rw-r--r-- | libnm-core/nm-setting-ip-config.c | 2120 | ||||
-rw-r--r-- | libnm-core/nm-setting-ip-config.h | 147 | ||||
-rw-r--r-- | libnm-core/tests/test-setting.c | 200 | ||||
-rw-r--r-- | libnm/libnm.ver | 46 | ||||
-rw-r--r-- | shared/nm-libnm-core-utils.h | 3 |
6 files changed, 2556 insertions, 1 deletions
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 68f453a17f..95ec86a059 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -613,6 +613,47 @@ void _nm_wireguard_peer_set_public_key_bin (NMWireGuardPeer *self, /*****************************************************************************/ +const NMIPAddr *nm_ip_routing_rule_get_from_bin (const NMIPRoutingRule *self); +void nm_ip_routing_rule_set_from_bin (NMIPRoutingRule *self, + gconstpointer from, + guint8 len); + +const NMIPAddr *nm_ip_routing_rule_get_to_bin (const NMIPRoutingRule *self); +void nm_ip_routing_rule_set_to_bin (NMIPRoutingRule *self, + gconstpointer to, + guint8 len); + +gboolean nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self, + gboolean iif /* or else oif */, + char out_xifname[static 16]); + +#define NM_IP_ROUTING_RULE_ATTR_ACTION "action" +#define NM_IP_ROUTING_RULE_ATTR_DPORT_END "dport-end" +#define NM_IP_ROUTING_RULE_ATTR_DPORT_START "dport-start" +#define NM_IP_ROUTING_RULE_ATTR_FAMILY "family" +#define NM_IP_ROUTING_RULE_ATTR_FROM "from" +#define NM_IP_ROUTING_RULE_ATTR_FROM_LEN "from-len" +#define NM_IP_ROUTING_RULE_ATTR_FWMARK "fwmark" +#define NM_IP_ROUTING_RULE_ATTR_FWMASK "fwmask" +#define NM_IP_ROUTING_RULE_ATTR_IIFNAME "iifname" +#define NM_IP_ROUTING_RULE_ATTR_INVERT "invert" +#define NM_IP_ROUTING_RULE_ATTR_IPPROTO "ipproto" +#define NM_IP_ROUTING_RULE_ATTR_OIFNAME "oifname" +#define NM_IP_ROUTING_RULE_ATTR_PRIORITY "priority" +#define NM_IP_ROUTING_RULE_ATTR_SPORT_END "sport-end" +#define NM_IP_ROUTING_RULE_ATTR_SPORT_START "sport-start" +#define NM_IP_ROUTING_RULE_ATTR_TABLE "table" +#define NM_IP_ROUTING_RULE_ATTR_TO "to" +#define NM_IP_ROUTING_RULE_ATTR_TOS "tos" +#define NM_IP_ROUTING_RULE_ATTR_TO_LEN "to-len" + +NMIPRoutingRule *nm_ip_routing_rule_from_dbus (GVariant *variant, + gboolean strict, + GError **error); +GVariant *nm_ip_routing_rule_to_dbus (const NMIPRoutingRule *self); + +/*****************************************************************************/ + typedef struct _NMSettInfoSetting NMSettInfoSetting; typedef struct _NMSettInfoProperty NMSettInfoProperty; diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c index 5250ae436d..b4b50e51c1 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" @@ -1384,6 +1385,2125 @@ _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 char *str_clone = NULL; + char *str_remainder; + char *str_word; + 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); + + str_clone = g_strdup (str); + str_remainder = str_clone; + + while ((str_word = nm_utils_str_simpletokens_extract_next (&str_remainder))) { + + 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); + } +} + +static void +_rr_string_append_escaped (GString *str, + const char *s) +{ + for (; s[0]; s++) { + /* We need to escape spaces and '\\', because that + * is what nm_utils_str_simpletokens_extract_next() uses to split + * words. + * We also escape ',' because nmcli uses that to concatenate values. + * We also escape ';', in case somebody wants to use ';' instead of ','. + */ + if ( NM_IN_SET (s[0], '\\', ',', ';') + || g_ascii_isspace (s[0])) + g_string_append_c (str, '\\'); + g_string_append_c (str, s[0]); + } +} + +/** + * 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 "); + _rr_string_append_escaped (str, self->iifname); + } + + if (self->oifname) { + g_string_append (nm_gstring_add_space_delimiter (str), + "oif "); + _rr_string_append_escaped (str, self->oifname); + } + + 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, diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h index 39cb36a43d..0522ea1991 100644 --- a/libnm-core/nm-setting-ip-config.h +++ b/libnm-core/nm-setting-ip-config.h @@ -159,6 +159,153 @@ gboolean nm_ip_route_attribute_validate (const char *name, #define NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND "lock-initrwnd" #define NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU "lock-mtu" +/*****************************************************************************/ + +typedef struct NMIPRoutingRule NMIPRoutingRule; + +NM_AVAILABLE_IN_1_18 +GType nm_ip_routing_rule_get_type (void); + +NM_AVAILABLE_IN_1_18 +NMIPRoutingRule *nm_ip_routing_rule_new (int addr_family); + +NM_AVAILABLE_IN_1_18 +NMIPRoutingRule *nm_ip_routing_rule_new_clone (const NMIPRoutingRule *rule); + +NM_AVAILABLE_IN_1_18 +NMIPRoutingRule *nm_ip_routing_rule_ref (NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_unref (NMIPRoutingRule *self); + +NM_AVAILABLE_IN_1_18 +gboolean nm_ip_routing_rule_is_sealed (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_seal (NMIPRoutingRule *self); + +NM_AVAILABLE_IN_1_18 +int nm_ip_routing_rule_get_addr_family (const NMIPRoutingRule *self); + +NM_AVAILABLE_IN_1_18 +gboolean nm_ip_routing_rule_get_invert (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_invert (NMIPRoutingRule *self, gboolean invert); + +NM_AVAILABLE_IN_1_18 +gint64 nm_ip_routing_rule_get_priority (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_priority (NMIPRoutingRule *self, gint64 priority); + +NM_AVAILABLE_IN_1_18 +guint8 nm_ip_routing_rule_get_tos (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_tos (NMIPRoutingRule *self, guint8 tos); + +NM_AVAILABLE_IN_1_18 +guint8 nm_ip_routing_rule_get_ipproto (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_ipproto (NMIPRoutingRule *self, guint8 ipproto); + +NM_AVAILABLE_IN_1_18 +guint16 nm_ip_routing_rule_get_source_port_start (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +guint16 nm_ip_routing_rule_get_source_port_end (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_source_port (NMIPRoutingRule *self, guint16 start, guint16 end); + +NM_AVAILABLE_IN_1_18 +guint16 nm_ip_routing_rule_get_destination_port_start (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +guint16 nm_ip_routing_rule_get_destination_port_end (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_destination_port (NMIPRoutingRule *self, guint16 start, guint16 end); + +NM_AVAILABLE_IN_1_18 +guint32 nm_ip_routing_rule_get_fwmark (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +guint32 nm_ip_routing_rule_get_fwmask (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_fwmark (NMIPRoutingRule *self, guint32 fwmark, guint32 fwmask); + +NM_AVAILABLE_IN_1_18 +guint8 nm_ip_routing_rule_get_from_len (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +const char *nm_ip_routing_rule_get_from (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_from (NMIPRoutingRule *self, + const char *from, + guint8 len); + +NM_AVAILABLE_IN_1_18 +guint8 nm_ip_routing_rule_get_to_len (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +const char *nm_ip_routing_rule_get_to (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_to (NMIPRoutingRule *self, + const char *to, + guint8 len); + +NM_AVAILABLE_IN_1_18 +const char *nm_ip_routing_rule_get_iifname (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_iifname (NMIPRoutingRule *self, const char *iifname); + +NM_AVAILABLE_IN_1_18 +const char *nm_ip_routing_rule_get_oifname (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_oifname (NMIPRoutingRule *self, const char *oifname); + +NM_AVAILABLE_IN_1_18 +guint8 nm_ip_routing_rule_get_action (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_action (NMIPRoutingRule *self, guint8 action); + +NM_AVAILABLE_IN_1_18 +guint32 nm_ip_routing_rule_get_table (const NMIPRoutingRule *self); +NM_AVAILABLE_IN_1_18 +void nm_ip_routing_rule_set_table (NMIPRoutingRule *self, guint32 table); + +NM_AVAILABLE_IN_1_18 +int nm_ip_routing_rule_cmp (const NMIPRoutingRule *rule, + const NMIPRoutingRule *other); + +NM_AVAILABLE_IN_1_18 +gboolean nm_ip_routing_rule_validate (const NMIPRoutingRule *self, + GError **error); + +/** + * NMIPRoutingRuleAsStringFlags: + * @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE: no flags selected. + * @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET: whether to allow parsing + * IPv4 addresses. + * @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6: whether to allow parsing + * IPv6 addresses. If both @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET and + * @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 are unset, it's the same + * as setting them both. + * @NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE: if set, ensure that the + * rule verfies or fail. + * + * Since: 1.18 + */ +typedef enum { /*< flags >*/ + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE = 0, + + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET = 0x1, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 = 0x2, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE = 0x4, +} NMIPRoutingRuleAsStringFlags; + +NMIPRoutingRule *nm_ip_routing_rule_from_string (const char *str, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable *extra_args, + GError **error); + +char *nm_ip_routing_rule_to_string (const NMIPRoutingRule *self, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable *extra_args, + GError **error); + +/*****************************************************************************/ + #define NM_TYPE_SETTING_IP_CONFIG (nm_setting_ip_config_get_type ()) #define NM_SETTING_IP_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_IP_CONFIG, NMSettingIPConfig)) #define NM_SETTING_IP_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_IPCONFIG, NMSettingIPConfigClass)) diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c index dc624d1ba3..f9d87eb6c2 100644 --- a/libnm-core/tests/test-setting.c +++ b/libnm-core/tests/test-setting.c @@ -20,6 +20,7 @@ #include "nm-default.h" #include <linux/pkt_sched.h> +#include <net/if.h> #include "nm-utils.h" #include "nm-utils-private.h" @@ -2675,6 +2676,203 @@ test_roundtrip_conversion (gconstpointer test_data) /*****************************************************************************/ +static NMIPRoutingRule * +_rr_from_str_get_impl (const char *str, const char *const*aliases) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr = NULL; + gs_free_error GError *error = NULL; + gboolean vbool; + int addr_family; + int i; + NMIPRoutingRuleAsStringFlags to_string_flags; + + rr = nm_ip_routing_rule_from_string (str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE, + NULL, + &error); + nmtst_assert_success (rr, error); + + addr_family = nm_ip_routing_rule_get_addr_family (rr); + g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6)); + + if (addr_family == AF_INET) + to_string_flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET; + else + to_string_flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6; + + for (i = 0; TRUE; i++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr2 = NULL; + gs_free char *str1 = NULL; + gs_unref_variant GVariant *variant1 = NULL; + const char *cstr1; + + switch (i) { + case 0: + rr2 = nm_ip_routing_rule_ref (rr); + break; + + case 1: + rr2 = nm_ip_routing_rule_from_string (str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool () ? to_string_flags : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success (rr, error); + break; + + case 2: + str1 = nm_ip_routing_rule_to_string (rr, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool () ? to_string_flags : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success (str1 && str1[0], error); + + g_assert_cmpstr (str, ==, str1); + + rr2 = nm_ip_routing_rule_from_string (str1, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool () ? to_string_flags : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success (rr, error); + break; + + case 3: + variant1 = nm_ip_routing_rule_to_dbus (rr); + g_assert (variant1); + g_assert (g_variant_is_floating (variant1)); + g_assert (g_variant_is_of_type (variant1, G_VARIANT_TYPE_VARDICT)); + + rr2 = nm_ip_routing_rule_from_dbus (variant1, + TRUE, + &error); + nmtst_assert_success (rr, error); + break; + + default: + if (!aliases || !aliases[0]) + goto done; + cstr1 = (aliases++)[0]; + rr2 = nm_ip_routing_rule_from_string (cstr1, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool () ? to_string_flags : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success (rr, error); + break; + } + + g_assert (rr2); + vbool = nm_ip_routing_rule_validate (rr, &error); + nmtst_assert_success (vbool, error); + vbool = nm_ip_routing_rule_validate (rr2, &error); + nmtst_assert_success (vbool, error); + + g_assert_cmpint (nm_ip_routing_rule_cmp (rr, rr2), ==, 0); + g_assert_cmpint (nm_ip_routing_rule_cmp (rr2, rr), ==, 0); + } + +done: + return g_steal_pointer (&rr); +} +#define _rr_from_str_get(a, ...) _rr_from_str_get_impl (a, &(NM_MAKE_STRV (NULL, ##__VA_ARGS__))[1]) + +#define _rr_from_str(...) \ + G_STMT_START { \ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *_rr = NULL; \ + \ + _rr = _rr_from_str_get (__VA_ARGS__); \ + g_assert (_rr); \ + } G_STMT_END + +static void +test_routing_rule (gconstpointer test_data) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr1 = NULL; + gboolean success; + char ifname_buf[16]; + + _rr_from_str ("priority 5 from 0.0.0.0 table 1", + " from 0.0.0\\.0 \\priority 5 lookup 1 "); + _rr_from_str ("priority 5 from 0.0.0.0/0 table 4"); + _rr_from_str ("priority 5 to 0.0.0.0 table 6"); + _rr_from_str ("priority 5 to 0.0.0.0 table 254", + "priority 5 to 0.0.0.0/32"); + _rr_from_str ("priority 5 from 1.2.3.4 table 15", + "priority 5 from 1.2.3.4/32 table 0xF ", + "priority 5 from 1.2.3.4/32 to 0.0.0.0/0 lookup 15 "); + _rr_from_str ("priority 5 from 1.2.3.4 to 0.0.0.0 table 8"); + _rr_from_str ("priority 5 to a:b:c:: tos 0x16 table 25", + "priority 5 to a:b:c::/128 table 0x19 tos 16", + "priority 5 to a:b:c::/128 lookup 0x19 dsfield 16", + "priority 5 to a:b:c::/128 lookup 0x19 dsfield 16 fwmark 0/0x00", + "priority 5 to a:b:c:: from all lookup 0x19 dsfield 16 fwmark 0x0/0"); + _rr_from_str ("priority 5 from :: fwmark 0 table 25", + "priority 5 from ::/128 to all table 0x19 fwmark 0/0xFFFFFFFF", + "priority 5 from :: to ::/0 table 0x19 fwmark 0x00/4294967295"); + _rr_from_str ("priority 5 from :: iif aab table 25"); + _rr_from_str ("priority 5 from :: iif aab oif er table 25", + "priority 5 from :: table 0x19 dev \\a\\a\\b oif er"); + _rr_from_str ("priority 5 from :: iif a\\\\303b table 25"); + _rr_from_str ("priority 5 to 0.0.0.0 sport 10 table 6", + "priority 5 to 0.0.0.0 sport 10-10 table 6"); + _rr_from_str ("not priority 5 to 0.0.0.0 dport 10-133 table 6", + "priority 5 to 0.0.0.0 not dport 10-133 not table 6", + "priority 5 to 0.0.0.0 not dport 10-\\ 133 not table 6"); + _rr_from_str ("priority 5 to 0.0.0.0 ipproto 10 sport 10 table 6"); + + rr1 = _rr_from_str_get ("priority 5 from :: iif aab table 25"); + g_assert_cmpstr (nm_ip_routing_rule_get_iifname (rr1), ==, "aab"); + success = nm_ip_routing_rule_get_xifname_bin (rr1, FALSE, ifname_buf); + g_assert (!success); + success = nm_ip_routing_rule_get_xifname_bin (rr1, TRUE, ifname_buf); + g_assert_cmpstr (ifname_buf, ==, "aab"); + g_assert (success); + + rr1 = _rr_from_str_get ("priority 5 from :: iif a\\\\303\\\\261xb table 254"); + g_assert_cmpstr (nm_ip_routing_rule_get_iifname (rr1), ==, "a\\303\\261xb"); + success = nm_ip_routing_rule_get_xifname_bin (rr1, FALSE, ifname_buf); + g_assert (!success); + success = nm_ip_routing_rule_get_xifname_bin (rr1, TRUE, ifname_buf); + g_assert_cmpstr (ifname_buf, ==, "a\303\261xb"); + g_assert (success); + nm_clear_pointer (&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get ("priority 5 from :: oif \\\\101=\\\\303\\\\261xb table 7"); + g_assert_cmpstr (nm_ip_routing_rule_get_oifname (rr1), ==, "\\101=\\303\\261xb"); + success = nm_ip_routing_rule_get_xifname_bin (rr1, FALSE, ifname_buf); + g_assert_cmpstr (ifname_buf, ==, "A=\303\261xb"); + g_assert (success); + success = nm_ip_routing_rule_get_xifname_bin (rr1, TRUE, ifname_buf); + g_assert (!success); + nm_clear_pointer (&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get ("priority 5 to 0.0.0.0 tos 0x10 table 7"); + g_assert_cmpstr (NULL, ==, nm_ip_routing_rule_get_from (rr1)); + g_assert (!nm_ip_routing_rule_get_from_bin (rr1)); + g_assert_cmpint (0, ==, nm_ip_routing_rule_get_from_len (rr1)); + g_assert_cmpstr ("0.0.0.0", ==, nm_ip_routing_rule_get_to (rr1)); + g_assert (nm_ip_addr_is_null (AF_INET, nm_ip_routing_rule_get_to_bin (rr1))); + g_assert_cmpint (32, ==, nm_ip_routing_rule_get_to_len (rr1)); + g_assert_cmpint (7, ==, nm_ip_routing_rule_get_table (rr1)); + g_assert_cmpint (0x10, ==, nm_ip_routing_rule_get_tos (rr1)); + nm_clear_pointer (&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get ("priority 5 from :: iif a\\\\303\\\\261\\,x\\;b table 254", + "priority 5 from :: iif a\\\\303\\\\261,x;b table 254"); + g_assert_cmpstr (nm_ip_routing_rule_get_iifname (rr1), ==, "a\\303\\261,x;b"); + success = nm_ip_routing_rule_get_xifname_bin (rr1, FALSE, ifname_buf); + g_assert (!success); + success = nm_ip_routing_rule_get_xifname_bin (rr1, TRUE, ifname_buf); + g_assert_cmpstr (ifname_buf, ==, "a\303\261,x;b"); + g_assert (success); + nm_clear_pointer (&rr1, nm_ip_routing_rule_unref); + +} + +/*****************************************************************************/ + NMTST_DEFINE (); int @@ -2757,5 +2955,7 @@ main (int argc, char **argv) g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/1", GINT_TO_POINTER (1), test_roundtrip_conversion); g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/2", GINT_TO_POINTER (2), test_roundtrip_conversion); + g_test_add_data_func ("/libnm/settings/routing-rule/1", GINT_TO_POINTER (0), test_routing_rule); + return g_test_run (); } diff --git a/libnm/libnm.ver b/libnm/libnm.ver index c2780cca81..20adc91c78 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -1536,13 +1536,57 @@ global: nm_bridge_vlan_set_untagged; nm_bridge_vlan_to_str; nm_bridge_vlan_unref; + nm_ip_routing_rule_as_string_flags_get_type; + nm_ip_routing_rule_cmp; + nm_ip_routing_rule_from_string; + nm_ip_routing_rule_get_action; + nm_ip_routing_rule_get_addr_family; + nm_ip_routing_rule_get_destination_port_end; + nm_ip_routing_rule_get_destination_port_start; + nm_ip_routing_rule_get_from; + nm_ip_routing_rule_get_from_len; + nm_ip_routing_rule_get_fwmark; + nm_ip_routing_rule_get_fwmask; + nm_ip_routing_rule_get_iifname; + nm_ip_routing_rule_get_invert; + nm_ip_routing_rule_get_ipproto; + nm_ip_routing_rule_get_oifname; + nm_ip_routing_rule_get_priority; + nm_ip_routing_rule_get_source_port_end; + nm_ip_routing_rule_get_source_port_start; + nm_ip_routing_rule_get_table; + nm_ip_routing_rule_get_to; + nm_ip_routing_rule_get_to_len; + nm_ip_routing_rule_get_tos; + nm_ip_routing_rule_get_type; + nm_ip_routing_rule_is_sealed; + nm_ip_routing_rule_new; + nm_ip_routing_rule_new_clone; + nm_ip_routing_rule_ref; + nm_ip_routing_rule_seal; + nm_ip_routing_rule_set_action; + nm_ip_routing_rule_set_destination_port; + nm_ip_routing_rule_set_from; + nm_ip_routing_rule_set_fwmark; + nm_ip_routing_rule_set_iifname; + nm_ip_routing_rule_set_invert; + nm_ip_routing_rule_set_ipproto; + nm_ip_routing_rule_set_oifname; + nm_ip_routing_rule_set_priority; + nm_ip_routing_rule_set_source_port; + nm_ip_routing_rule_set_table; + nm_ip_routing_rule_set_to; + nm_ip_routing_rule_set_tos; + nm_ip_routing_rule_to_string; + nm_ip_routing_rule_unref; + nm_ip_routing_rule_validate; nm_lldp_neighbor_get_attr_value; nm_setting_bridge_add_vlan; nm_setting_bridge_clear_vlans; nm_setting_bridge_get_num_vlans; nm_setting_bridge_get_vlan; - nm_setting_bridge_get_vlan_filtering; nm_setting_bridge_get_vlan_default_pvid; + nm_setting_bridge_get_vlan_filtering; nm_setting_bridge_port_add_vlan; nm_setting_bridge_port_clear_vlans; nm_setting_bridge_port_get_num_vlans; diff --git a/shared/nm-libnm-core-utils.h b/shared/nm-libnm-core-utils.h index 85dd3b9b2c..f55d6b3cc0 100644 --- a/shared/nm-libnm-core-utils.h +++ b/shared/nm-libnm-core-utils.h @@ -38,6 +38,9 @@ NM_AUTO_DEFINE_FCN0 (NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref) #define nm_auto_unref_ip_route nm_auto (_nm_auto_unref_ip_route) NM_AUTO_DEFINE_FCN0 (NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref) +#define nm_auto_unref_ip_routing_rule nm_auto(_nm_auto_unref_ip_routing_rule) +NM_AUTO_DEFINE_FCN0 (NMIPRoutingRule *, _nm_auto_unref_ip_routing_rule, nm_ip_routing_rule_unref) + #define nm_auto_unref_sriov_vf nm_auto (_nm_auto_unref_sriov_vf) NM_AUTO_DEFINE_FCN0 (NMSriovVF *, _nm_auto_unref_sriov_vf, nm_sriov_vf_unref) |