From 85563b7fc7ec2cd21e38debb9b28db342e2e8e7c Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Sun, 21 Apr 2019 21:09:51 +0200 Subject: New upstream version 1.18.0 --- libnm-core/meson.build | 92 +- libnm-core/nm-connection.c | 51 +- libnm-core/nm-core-internal.h | 81 +- libnm-core/nm-crypto-gnutls.c | 2 +- libnm-core/nm-crypto-nss.c | 2 +- libnm-core/nm-crypto.c | 4 +- libnm-core/nm-dbus-interface.h | 18 +- libnm-core/nm-json.h | 2 +- libnm-core/nm-keyfile.c | 614 ++++++--- libnm-core/nm-setting-8021x.c | 2 +- libnm-core/nm-setting-bridge-port.c | 319 ++++- libnm-core/nm-setting-bridge-port.h | 18 + libnm-core/nm-setting-bridge.c | 863 +++++++++++- libnm-core/nm-setting-bridge.h | 59 + libnm-core/nm-setting-connection.c | 7 +- libnm-core/nm-setting-ethtool.c | 2 +- libnm-core/nm-setting-infiniband.c | 25 +- libnm-core/nm-setting-ip-config.c | 2465 ++++++++++++++++++++++++++++++++++- libnm-core/nm-setting-ip-config.h | 166 +++ libnm-core/nm-setting-ip-tunnel.c | 4 +- libnm-core/nm-setting-macsec.c | 2 +- libnm-core/nm-setting-private.h | 4 + libnm-core/nm-setting-sriov.c | 6 +- libnm-core/nm-setting-tc-config.c | 197 ++- libnm-core/nm-setting-tc-config.h | 10 + libnm-core/nm-setting-team-port.c | 27 +- libnm-core/nm-setting-team.c | 123 +- libnm-core/nm-setting-vlan.c | 122 +- libnm-core/nm-setting-vpn.c | 16 +- libnm-core/nm-setting-wifi-p2p.c | 2 +- libnm-core/nm-setting-wired.c | 18 +- libnm-core/nm-setting-wireguard.c | 2 +- libnm-core/nm-setting-wireless.c | 2 +- libnm-core/nm-utils-private.h | 80 +- libnm-core/nm-utils.c | 422 ++++-- libnm-core/nm-version.h | 14 + libnm-core/tests/meson.build | 2 +- libnm-core/tests/test-general.c | 516 ++++++-- libnm-core/tests/test-keyfile.c | 98 ++ libnm-core/tests/test-setting.c | 368 +++++- 40 files changed, 6156 insertions(+), 671 deletions(-) (limited to 'libnm-core') diff --git a/libnm-core/meson.build b/libnm-core/meson.build index d10dd1c5..bc2d087a 100644 --- a/libnm-core/meson.build +++ b/libnm-core/meson.build @@ -139,14 +139,57 @@ libnm_core_enum = gnome.mkenums( install_dir: libnm_pkgincludedir, ) +libnm_core_enum_dep = declare_dependency( + sources: libnm_core_enum[1], +) + +############################################################################### + +shared_nm_libnm_core_intern = static_library( + 'nm-libnm-core-intern', + sources: files('../shared/nm-libnm-core-intern/nm-ethtool-utils.c', + '../shared/nm-libnm-core-intern/nm-libnm-core-utils.c'), + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', + ], + include_directories: [ + top_inc, + shared_inc, + libnm_core_inc, + ], + dependencies: [ + glib_dep, + shared_c_siphash_dep, + shared_nm_glib_aux_dep, + libnm_core_enum_dep, + ], +) + +shared_nm_libnm_core_intern_dep = declare_dependency( + link_with: shared_nm_libnm_core_intern, + include_directories: [ + top_inc, + shared_inc, + ], + dependencies: [ + glib_dep, + shared_nm_glib_aux_dep, + libnm_systemd_shared_dep, + ], +) + +############################################################################### + deps = [ dl_dep, libudev_dep, uuid_dep, glib_dep, shared_c_siphash_dep, - shared_nm_utils_base_dep, - shared_nm_utils_udev_dep, + shared_nm_glib_aux_dep, + shared_nm_udev_aux_dep, + shared_nm_libnm_core_intern_dep, ] cflags = [ @@ -188,7 +231,6 @@ endif libnm_core_sources_all = libnm_core_sources libnm_core_sources_all += libnm_core_enum libnm_core_sources_all += shared_nm_meta_setting_c -libnm_core_sources_all += shared_nm_ethtool_utils_c libnm_core_sources_all += [version_header] libnm_core = static_library( @@ -199,16 +241,54 @@ libnm_core = static_library( c_args: cflags, ) -nm_core_dep = declare_dependency( +libnm_core_dep = declare_dependency( sources: libnm_core_enum[1], include_directories: libnm_core_inc, dependencies: [ - shared_nm_utils_base_dep, + shared_nm_libnm_core_intern_dep, + shared_nm_glib_aux_dep, shared_c_siphash_dep, libnm_systemd_shared_dep, ], ) +############################################################################### + +shared_nm_libnm_core_aux = static_library( + 'nm-libnm-core-aux', + sources: files('../shared/nm-libnm-core-aux/nm-dispatcher-api.h'), + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', + ], + include_directories: [ + top_inc, + shared_inc, + libnm_core_inc, + ], + dependencies: [ + glib_dep, + shared_c_siphash_dep, + shared_nm_glib_aux_dep, + ], +) + +shared_nm_libnm_core_aux_dep = declare_dependency( + link_with: shared_nm_libnm_core_aux, + include_directories: [ + top_inc, + shared_inc, + ], + dependencies: [ + libnm_core_dep, + glib_dep, + shared_nm_glib_aux_dep, + libnm_systemd_shared_dep, + ], +) + +############################################################################### + enums_to_docbook = join_paths(meson.source_root(), 'tools', 'enums-to-docbook.pl') docbooks = [ @@ -233,6 +313,8 @@ foreach docbook: docbooks content_files += xml.full_path() endforeach +############################################################################### + if enable_tests subdir('tests') endif diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c index ea7730bf..3182e346 100644 --- a/libnm-core/nm-connection.c +++ b/libnm-core/nm-connection.c @@ -1059,23 +1059,14 @@ _normalize_infiniband_mtu (NMConnection *self, GHashTable *parameters) { NMSettingInfiniband *s_infini = nm_connection_get_setting_infiniband (self); - if (s_infini) { - const char *transport_mode = nm_setting_infiniband_get_transport_mode (s_infini); - guint32 max_mtu = 0; - - if (transport_mode) { - if (!strcmp (transport_mode, "datagram")) - max_mtu = 2044; - else if (!strcmp (transport_mode, "connected")) - max_mtu = 65520; - - if (max_mtu && nm_setting_infiniband_get_mtu (s_infini) > max_mtu) { - g_object_set (s_infini, NM_SETTING_INFINIBAND_MTU, (guint) max_mtu, NULL); - return TRUE; - } - } - } - return FALSE; + if ( !s_infini + || nm_setting_infiniband_get_mtu (s_infini) <= NM_INFINIBAND_MAX_MTU + || !NM_IN_STRSET (nm_setting_infiniband_get_transport_mode (s_infini), "datagram", + "connected")) + return FALSE; + + g_object_set (s_infini, NM_SETTING_INFINIBAND_MTU, (guint) NM_INFINIBAND_MAX_MTU, NULL); + return TRUE; } static gboolean @@ -1297,6 +1288,30 @@ _normalize_sriov_vf_order (NMConnection *self, GHashTable *parameters) return _nm_setting_sriov_sort_vfs (s_sriov); } +static gboolean +_normalize_bridge_vlan_order (NMConnection *self, GHashTable *parameters) +{ + NMSettingBridge *s_bridge; + + s_bridge = nm_connection_get_setting_bridge (self); + if (!s_bridge) + return FALSE; + + return _nm_setting_bridge_sort_vlans (s_bridge); +} + +static gboolean +_normalize_bridge_port_vlan_order (NMConnection *self, GHashTable *parameters) +{ + NMSettingBridgePort *s_port; + + s_port = nm_connection_get_setting_bridge_port (self); + if (!s_port) + return FALSE; + + return _nm_setting_bridge_port_sort_vlans (s_port); +} + static gboolean _normalize_required_settings (NMConnection *self, GHashTable *parameters) { @@ -1645,6 +1660,8 @@ nm_connection_normalize (NMConnection *connection, was_modified |= _normalize_ovs_interface_type (connection, parameters); was_modified |= _normalize_ip_tunnel_wired_setting (connection, parameters); was_modified |= _normalize_sriov_vf_order (connection, parameters); + was_modified |= _normalize_bridge_vlan_order (connection, parameters); + was_modified |= _normalize_bridge_port_vlan_order (connection, parameters); /* Verify anew. */ success = _nm_connection_verify (connection, error); diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index f2332f76..3e33dd93 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -88,6 +88,7 @@ #include "nm-utils.h" #include "nm-vpn-dbus-interface.h" #include "nm-vpn-editor-plugin.h" +#include "nm-libnm-core-intern/nm-libnm-core-utils.h" /* IEEE 802.1D-1998 timer values */ #define NM_BR_MIN_HELLO_TIME 1 @@ -249,6 +250,8 @@ guint nm_setting_ethtool_init_features (NMSettingEthtool *setting, guint8 *_nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length); const char *nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_case, char *buf, gsize buf_len); +gboolean nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name); + GSList * _nm_utils_hash_values_to_slist (GHashTable *hash); GHashTable *_nm_utils_copy_strdict (GHashTable *strdict); @@ -259,19 +262,11 @@ const char **_nm_ip_address_get_attribute_names (const NMIPAddress *addr, gboole gboolean _nm_ip_route_attribute_validate_all (const NMIPRoute *route); const char **_nm_ip_route_get_attribute_names (const NMIPRoute *route, gboolean sorted, guint *out_length); -GHashTable *_nm_ip_route_get_attributes_direct (NMIPRoute *route); +GHashTable *_nm_ip_route_get_attributes (NMIPRoute *route); NMSriovVF *_nm_utils_sriov_vf_from_strparts (const char *index, const char *detail, gboolean ignore_unknown, GError **error); gboolean _nm_sriov_vf_attribute_validate_all (const NMSriovVF *vf, GError **error); -static inline void -_nm_auto_ip_route_unref (NMIPRoute **v) -{ - if (*v) - nm_ip_route_unref (*v); -} -#define nm_auto_ip_route_unref nm_auto (_nm_auto_ip_route_unref) - GPtrArray *_nm_utils_copy_array (const GPtrArray *array, NMUtilsCopyFunc copy_func, GDestroyNotify free_func); @@ -284,7 +279,6 @@ char ** _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy); GPtrArray * _nm_utils_strv_to_ptrarray (char **strv); char ** _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray); -gboolean _nm_utils_strv_equal (char **strv1, char **strv2); gboolean _nm_utils_check_file (const char *filename, gint64 check_owner, @@ -538,6 +532,8 @@ gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr); /*****************************************************************************/ +gboolean _nm_team_link_watchers_equal (GPtrArray *a, GPtrArray *b, gboolean ignore_order); + gboolean _nm_utils_team_config_equal (const char *conf1, const char *conf2, gboolean port); GValue *_nm_utils_team_config_get (const char *conf, const char *key, @@ -553,18 +549,6 @@ gboolean _nm_utils_team_config_set (char **conf, /*****************************************************************************/ -static inline int -nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip) -{ - if (NM_IS_SETTING_IP4_CONFIG (s_ip)) - return AF_INET; - if (NM_IS_SETTING_IP6_CONFIG (s_ip)) - return AF_INET6; - g_return_val_if_reached (AF_UNSPEC); -} - -/*****************************************************************************/ - guint32 _nm_utils_parse_tc_handle (const char *str, GError **error); void _nm_utils_string_append_tc_parent (GString *string, @@ -576,6 +560,9 @@ gboolean _nm_utils_string_append_tc_tfilter_rest (GString *string, NMTCTfilter *tfilter, GError **error); +GHashTable *_nm_tc_qdisc_get_attributes (NMTCQdisc *qdisc); +GHashTable *_nm_tc_action_get_attributes (NMTCAction *action); + /*****************************************************************************/ static inline gboolean @@ -596,6 +583,8 @@ gboolean _nm_utils_dhcp_duid_valid (const char *duid, GBytes **out_duid_bin); /*****************************************************************************/ gboolean _nm_setting_sriov_sort_vfs (NMSettingSriov *setting); +gboolean _nm_setting_bridge_port_sort_vlans (NMSettingBridgePort *setting); +gboolean _nm_setting_bridge_sort_vlans (NMSettingBridge *setting); /*****************************************************************************/ @@ -627,6 +616,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; @@ -762,13 +792,14 @@ gboolean _nm_connection_find_secret (NMConnection *self, /*****************************************************************************/ -#define nm_auto_unref_wgpeer nm_auto(_nm_auto_unref_wgpeer) -NM_AUTO_DEFINE_FCN0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref) - gboolean nm_utils_base64secret_normalize (const char *base64_key, gsize required_key_len, char **out_base64_key_norm); /*****************************************************************************/ +void _nm_bridge_vlan_str_append_rest (const NMBridgeVlan *vlan, + GString *string, + gboolean leading_space); + #endif diff --git a/libnm-core/nm-crypto-gnutls.c b/libnm-core/nm-crypto-gnutls.c index 6c897e6d..6b2f7587 100644 --- a/libnm-core/nm-crypto-gnutls.c +++ b/libnm-core/nm-crypto-gnutls.c @@ -30,7 +30,7 @@ #include #include -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "nm-errors.h" /*****************************************************************************/ diff --git a/libnm-core/nm-crypto-nss.c b/libnm-core/nm-crypto-nss.c index 711dde4b..25cc7777 100644 --- a/libnm-core/nm-crypto-nss.c +++ b/libnm-core/nm-crypto-nss.c @@ -35,7 +35,7 @@ #include #include -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "nm-errors.h" /*****************************************************************************/ diff --git a/libnm-core/nm-crypto.c b/libnm-core/nm-crypto.c index 67f206c4..c7142216 100644 --- a/libnm-core/nm-crypto.c +++ b/libnm-core/nm-crypto.c @@ -29,8 +29,8 @@ #include #include -#include "nm-utils/nm-secret-utils.h" -#include "nm-utils/nm-io-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-glib-aux/nm-io-utils.h" #include "nm-crypto-impl.h" #include "nm-utils.h" diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index 038d2c9e..2e127c40 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -837,12 +837,26 @@ typedef enum /*< flags >*/ { #define NM_LLDP_ATTR_SYSTEM_NAME "system-name" #define NM_LLDP_ATTR_SYSTEM_DESCRIPTION "system-description" #define NM_LLDP_ATTR_SYSTEM_CAPABILITIES "system-capabilities" +#define NM_LLDP_ATTR_MANAGEMENT_ADDRESSES "management-addresses" + #define NM_LLDP_ATTR_IEEE_802_1_PVID "ieee-802-1-pvid" -#define NM_LLDP_ATTR_IEEE_802_1_PPVID "ieee-802-1-ppvid" -#define NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS "ieee-802-1-ppvid-flags" +#define NM_LLDP_ATTR_IEEE_802_1_VLANS "ieee-802-1-vlans" +#define NM_LLDP_ATTR_IEEE_802_1_PPVIDS "ieee-802-1-ppvids" + +#define NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF "ieee-802-3-mac-phy-conf" +#define NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI "ieee-802-3-power-via-mdi" +#define NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE "ieee-802-3-max-frame-size" + +/* These are deprecated in favor of NM_LLDP_ATTR_IEEE_802_1_VLANS, + * which can report multiple VLANs */ #define NM_LLDP_ATTR_IEEE_802_1_VID "ieee-802-1-vid" #define NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME "ieee-802-1-vlan-name" +/* These are deprecated in favor of NM_LLDP_ATTR_IEEE_802_1_PPVIDS, + * which can report multiple PPVIDs */ +#define NM_LLDP_ATTR_IEEE_802_1_PPVID "ieee-802-1-ppvid" +#define NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS "ieee-802-1-ppvid-flags" + #define NM_LLDP_DEST_NEAREST_BRIDGE "nearest-bridge" #define NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE "nearest-non-tpmr-bridge" #define NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE "nearest-customer-bridge" diff --git a/libnm-core/nm-json.h b/libnm-core/nm-json.h index 560b827a..b75a4f8e 100644 --- a/libnm-core/nm-json.h +++ b/libnm-core/nm-json.h @@ -45,7 +45,7 @@ gboolean nm_jansson_load (void); #define json_integer_value (*_nm_jansson_json_integer_value) #define json_string_value (*_nm_jansson_json_string_value) -#include "nm-utils/nm-jansson.h" +#include "nm-glib-aux/nm-jansson.h" #endif #endif /* __NM_JSON_H__ */ diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c index 05c6bf97..bf8d2193 100644 --- a/libnm-core/nm-keyfile.c +++ b/libnm-core/nm-keyfile.c @@ -31,9 +31,9 @@ #include #include -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "systemd/nm-sd-utils-shared.h" -#include "nm-common-macros.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-core-internal.h" #include "nm-keyfile-utils.h" @@ -41,6 +41,8 @@ /*****************************************************************************/ +typedef struct _ParseInfoProperty ParseInfoProperty; + typedef struct { NMConnection *connection; GKeyFile *keyfile; @@ -534,13 +536,19 @@ typedef struct { const char *s_key; gint32 key_idx; gint8 key_type; -} IPAddrRouteBuildListData; +} BuildListData; + +typedef enum { + BUILD_LIST_TYPE_ADDRESSES, + BUILD_LIST_TYPE_ROUTES, + BUILD_LIST_TYPE_ROUTING_RULES, +} BuildListType; static int -_ip_addrroute_build_lst_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data) { - const IPAddrRouteBuildListData *a = p_a; - const IPAddrRouteBuildListData *b = p_b; + const BuildListData *a = p_a; + const BuildListData *b = p_b; NM_CMP_FIELD (a, b, key_idx); NM_CMP_FIELD (a, b, key_type); @@ -549,10 +557,25 @@ _ip_addrroute_build_lst_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer } static gboolean -ip_addrroute_match_key_w_name_ (const char *key, - const char *base_name, - gsize base_name_l, - gint32 *out_key_idx) +_build_list_data_is_shadowed (const BuildListData *build_list, + gsize build_list_len, + gsize idx) +{ + /* the keyfile contains duplicate keys, which are both returned + * by g_key_file_get_keys() (WHY??). + * + * Skip the earlier one. */ + return idx + 1 < build_list_len + && build_list[idx].key_idx == build_list[idx + 1].key_idx + && build_list[idx].key_type == build_list[idx + 1].key_type + && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key); +} + +static gboolean +_build_list_match_key_w_name_impl (const char *key, + const char *base_name, + gsize base_name_l, + gint32 *out_key_idx) { gint64 v; @@ -595,100 +618,125 @@ ip_addrroute_match_key_w_name_ (const char *key, return TRUE; } -static gboolean -ip_addrroute_match_key (const char *key, - gboolean is_routes, - gint32 *out_key_idx, - gint8 *out_key_type) -{ -#define ip_addrroute_match_key_w_name(key, base_name, out_key_idx) \ - ip_addrroute_match_key_w_name_ (key, base_name, NM_STRLEN (base_name), out_key_idx) - - if (is_routes) { - if (ip_addrroute_match_key_w_name (key, "route", out_key_idx)) - NM_SET_OUT (out_key_type, 0); - else if (ip_addrroute_match_key_w_name (key, "routes", out_key_idx)) - NM_SET_OUT (out_key_type, 1); - else - return FALSE; - } else { - if (ip_addrroute_match_key_w_name (key, "address", out_key_idx)) - NM_SET_OUT (out_key_type, 0); - else if (ip_addrroute_match_key_w_name (key, "addresses", out_key_idx)) - NM_SET_OUT (out_key_type, 1); - else - return FALSE; - } - return TRUE; -} +#define _build_list_match_key_w_name(key, base_name, out_key_idx) \ + _build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx) -static void -ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) +static BuildListData * +_build_list_create (GKeyFile *keyfile, + const char *group_name, + BuildListType build_list_type, + gsize *out_build_list_len, + char ***out_keys_strv) { - const char *setting_name = nm_setting_get_name (setting); - gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); - gboolean is_routes = nm_streq (setting_key, "routes"); - gs_free char *gateway = NULL; - gs_unref_ptrarray GPtrArray *list = NULL; gs_strfreev char **keys = NULL; gsize i_keys, n_keys; - gs_free IPAddrRouteBuildListData *build_list = NULL; - gsize i_build_list, build_list_len = 0; + gs_free BuildListData *build_list = NULL; + gsize build_list_len = 0; - keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + nm_assert (out_build_list_len && *out_build_list_len == 0); + nm_assert (out_keys_strv && !*out_keys_strv); + + keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL); if (n_keys == 0) - return; + return NULL; - /* first create a list of all relevant keys, and sort them. */ for (i_keys = 0; i_keys < n_keys; i_keys++) { const char *s_key = keys[i_keys]; gint32 key_idx; - gint8 key_type; - - if (!ip_addrroute_match_key (s_key, is_routes, &key_idx, &key_type)) - continue; + gint8 key_type = 0; + + switch (build_list_type) { + case BUILD_LIST_TYPE_ROUTES: + if (_build_list_match_key_w_name (s_key, "route", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "routes", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ADDRESSES: + if (_build_list_match_key_w_name (s_key, "address", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ROUTING_RULES: + if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx)) + key_type = 0; + else + continue; + break; + default: + nm_assert_not_reached (); + break; + } if (G_UNLIKELY (!build_list)) - build_list = g_new (IPAddrRouteBuildListData, n_keys - i_keys); + build_list = g_new (BuildListData, n_keys - i_keys); - build_list[build_list_len].s_key = s_key; - build_list[build_list_len].key_idx = key_idx; - build_list[build_list_len].key_type = key_type; - build_list_len++; + build_list[build_list_len++] = (BuildListData) { + .s_key = s_key, + .key_idx = key_idx, + .key_type = key_type, + }; } if (build_list_len == 0) - return; + return NULL; + + if (build_list_len > 1) { + g_qsort_with_data (build_list, + build_list_len, + sizeof (BuildListData), + _build_list_data_cmp, + NULL); + } + + *out_build_list_len = build_list_len; + *out_keys_strv = g_steal_pointer (&keys); + return g_steal_pointer (&build_list); +} + +static void +ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gboolean is_routes = nm_streq (setting_key, "routes"); + gs_free char *gateway = NULL; + gs_unref_ptrarray GPtrArray *list = NULL; + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; - g_qsort_with_data (build_list, - build_list_len, - sizeof (IPAddrRouteBuildListData), - _ip_addrroute_build_lst_data_cmp, - NULL); + build_list = _build_list_create (info->keyfile, + setting_name, + is_routes + ? BUILD_LIST_TYPE_ROUTES + : BUILD_LIST_TYPE_ADDRESSES, + &build_list_len, + &keys); + if (!build_list) + return; list = g_ptr_array_new_with_free_func (is_routes ? (GDestroyNotify) nm_ip_route_unref : (GDestroyNotify) nm_ip_address_unref); for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { - const IPAddrRouteBuildListData *build_data = &build_list[i_build_list]; + const char *s_key; gpointer item; - if ( i_build_list + 1 < build_list_len - && build_data->key_idx == build_data[1].key_idx - && build_data->key_type == build_data[1].key_type - && nm_streq (build_data->s_key, build_data[1].s_key)) { - /* the keyfile contains duplicate keys, which are both returned - * by g_key_file_get_keys() (WHY??). - * - * Skip the earlier one. */ + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) continue; - } + s_key = build_list[i_build_list].s_key; item = read_one_ip_address_or_route (info, setting_key, setting_name, - build_data->s_key, + s_key, is_ipv6, is_routes, gateway ? NULL : &gateway, @@ -696,7 +744,7 @@ ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const c if (item && is_routes) { char options_key[128]; - nm_sprintf_buf (options_key, "%s_options", build_data->s_key); + nm_sprintf_buf (options_key, "%s_options", s_key); fill_route_attributes (info->keyfile, item, setting_name, @@ -718,6 +766,63 @@ ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const c g_object_set (setting, "gateway", gateway, NULL); } +static void +ip_routing_rule_parser_full (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create (info->keyfile, + setting_name, + BUILD_LIST_TYPE_ROUTING_RULES, + &build_list_len, + &keys); + if (!build_list) + return; + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free char *value = NULL; + gs_free_error GError *local = NULL; + + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) + continue; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + setting_name, + build_list[i_build_list].s_key, + NULL); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string (value, + ( NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | ( is_ipv6 + ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)), + NULL, + &local); + if (!rule) { + handle_warn (info, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid value for \"%s\": %s"), + build_list[i_build_list].s_key, + local->message); + if (info->error) + return; + continue; + } + + nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule); + } +} + static void ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { @@ -1518,21 +1623,58 @@ team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key g_object_set (G_OBJECT (setting), key, conf, NULL); } +static void +bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free char *value = NULL; + gs_free const char **strv = NULL; + const char *const *iter; + GError *local = NULL; + NMBridgeVlan *vlan; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + nm_setting_get_name (setting), + key, + NULL); + if (!value || !value[0]) + return; + + vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); + + strv = nm_utils_escaped_tokens_split (value, ","); + if (strv) { + for (iter = strv; *iter; iter++) { + vlan = nm_bridge_vlan_from_str (*iter, &local); + if (!vlan) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + "invalid bridge VLAN: %s", local->message); + g_clear_error (&local); + continue; + } + g_ptr_array_add (vlans, vlan); + } + } + + if (vlans->len > 0) + g_object_set (setting, key, vlans, NULL); +} + static void qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); - GPtrArray *qdiscs; + gs_unref_ptrarray GPtrArray *qdiscs = NULL; gs_strfreev char **keys = NULL; gsize n_keys = 0; int i; - qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref); - keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); if (n_keys == 0) return; + qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref); + for (i = 0; i < n_keys; i++) { NMTCQdisc *qdisc; const char *qdisc_parent; @@ -1562,25 +1704,23 @@ qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) if (qdiscs->len >= 1) g_object_set (setting, key, qdiscs, NULL); - - g_ptr_array_unref (qdiscs); } static void tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); - GPtrArray *tfilters; + gs_unref_ptrarray GPtrArray *tfilters = NULL; gs_strfreev char **keys = NULL; gsize n_keys = 0; int i; - tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref); - keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); if (n_keys == 0) return; + tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref); + for (i = 0; i < n_keys; i++) { NMTCTfilter *tfilter; const char *tfilter_parent; @@ -1610,8 +1750,6 @@ tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) if (tfilters->len >= 1) g_object_set (setting, key, tfilters, NULL); - - g_ptr_array_unref (tfilters); } /*****************************************************************************/ @@ -1805,10 +1943,9 @@ write_ip_values (GKeyFile *file, if (is_route) { gs_free char *attributes = NULL; - GHashTable *hash; - hash = _nm_ip_route_get_attributes_direct (array->pdata[i]); - attributes = nm_utils_format_variant_attributes (hash, ',', '='); + attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]), + ',', '='); if (attributes) { g_strlcat (key_name, "_options", sizeof (key_name)); nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes); @@ -1846,6 +1983,76 @@ route_writer (KeyfileWriterInfo *info, write_ip_values (info->keyfile, setting_name, array, NULL, TRUE); } +static void +bridge_vlan_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + NMBridgeVlan *vlan; + GPtrArray *vlans; + GString *string; + guint i; + + vlans = (GPtrArray *) g_value_get_boxed (value); + if (!vlans || !vlans->len) + return; + + string = g_string_new (""); + for (i = 0; i < vlans->len; i++) { + gs_free char *vlan_str = NULL; + + vlan = vlans->pdata[i]; + vlan_str = nm_bridge_vlan_to_str (vlan, NULL); + if (!vlan_str) + continue; + if (string->len > 0) + g_string_append (string, ","); + nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string); + } + + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + "vlans", + string->str); + + g_string_free (string, TRUE); +} + +static void +ip_routing_rule_writer_full (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting); + guint i, j, n; + char key_name_full[100] = "routing-rule"; + char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")]; + + n = nm_setting_ip_config_get_num_routing_rules (s_ip); + j = 0; + for (i = 0; i < n; i++) { + NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i); + gs_free char *str = NULL; + + str = nm_ip_routing_rule_to_string (rule, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, + NULL, + NULL); + if (!str) + continue; + + sprintf (key_name_num, "%u", ++j); + nm_keyfile_plugin_kf_set_string (info->keyfile, + setting_name, + key_name_full, + str); + } +} + static void qdisc_writer (KeyfileWriterInfo *info, NMSetting *setting, @@ -2157,24 +2364,40 @@ cert_writer (KeyfileWriterInfo *info, /*****************************************************************************/ -typedef struct { +struct _ParseInfoProperty { const char *property_name; - void (*parser) (KeyfileReaderInfo *info, - NMSetting *setting, - const char *key); - void (*writer) (KeyfileWriterInfo *info, - NMSetting *setting, - const char *key, - const GValue *value); + union { + void (*parser) (KeyfileReaderInfo *info, + NMSetting *setting, + const char *key); + void (*parser_full) (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; + union { + void (*writer) (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value); + void (*writer_full) (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; bool parser_skip; bool parser_no_check_key:1; bool writer_skip:1; + bool has_writer_full:1; + bool has_parser_full:1; /* usually, we skip to write values that have their * default value. By setting this flag to TRUE, also * default values are written. */ bool writer_persist_default:1; -} ParseInfoProperty; +}; #define PARSE_INFO_PROPERTY(_property_name, ...) \ (&((const ParseInfoProperty) { \ @@ -2276,6 +2499,20 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), ), ), PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION, @@ -2317,6 +2554,13 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { .parser = ip_address_or_route_parser, .writer = route_writer, ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), ), ), PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG, @@ -2345,6 +2589,13 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { .parser = ip_address_or_route_parser, .writer = route_writer, ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), ), ), PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL, @@ -2523,7 +2774,7 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { static const ParseInfoProperty * _parse_info_find (NMSetting *setting, const char *property_name, - const char **out_setting_name) + const NMMetaSettingInfo **out_setting_info) { const NMMetaSettingInfo *setting_info; const ParseInfoSetting *pis; @@ -2562,11 +2813,13 @@ _parse_info_find (NMSetting *setting, if ( !NM_IS_SETTING (setting) || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) { /* handle invalid setting objects gracefully. */ - *out_setting_name = NULL; + *out_setting_info = NULL; return NULL; } - *out_setting_name = setting_info->setting_name; + nm_assert (setting_info->setting_name); + + *out_setting_info = setting_info; if ((pis = parse_infos[setting_info->meta_type])) { G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0); @@ -2593,32 +2846,42 @@ read_one_setting_value (KeyfileReaderInfo *info, { GKeyFile *keyfile = info->keyfile; gs_free_error GError *err = NULL; + const NMMetaSettingInfo *setting_info; const ParseInfoProperty *pip; gs_free char *tmp_str = NULL; - const char *setting_name; const char *key; GType type; guint64 u64; gint64 i64; nm_assert (!info->error); - nm_assert (property_info->param_spec); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); - if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE) - return; + key = property_info->name; - key = property_info->param_spec->name; + pip = _parse_info_find (setting, key, &setting_info); - pip = _parse_info_find (setting, key, &setting_name); + nm_assert (setting_info); - nm_assert (setting_name); - - if ( !pip - && nm_streq (key, NM_SETTING_NAME)) - return; + if (!pip) { + if (nm_streq (key, NM_SETTING_NAME)) + return; + if (!property_info->param_spec) + return; + if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE) + return; + } else { + if (pip->parser_skip) + return; + if (pip->has_parser_full) { + pip->parser_full (info, setting_info, property_info, pip, setting); + return; + } + } - if (pip && pip->parser_skip) - return; + nm_assert (property_info->param_spec); + nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE); /* Check for the exact key in the GKeyFile if required. Most setting * properties map 1:1 to a key in the GKeyFile, but for those properties @@ -2626,7 +2889,7 @@ read_one_setting_value (KeyfileReaderInfo *info, * encoded by the setting property, this won't be true. */ if ( (!pip || !pip->parser_no_check_key) - && !nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, &err)) { + && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) { /* Key doesn't exist or an error occurred, thus nothing to do. */ if (err) { if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, @@ -2637,7 +2900,8 @@ read_one_setting_value (KeyfileReaderInfo *info, return; } - if (pip && pip->parser) { + if ( pip + && pip->parser) { pip->parser (info, setting, key); return; } @@ -2647,11 +2911,11 @@ read_one_setting_value (KeyfileReaderInfo *info, if (type == G_TYPE_STRING) { gs_free char *str_val = NULL; - str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, &err); + str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err); if (!err) nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err); } else if (type == G_TYPE_UINT) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); if ( u64 == G_MAXUINT64 @@ -2662,7 +2926,7 @@ read_one_setting_value (KeyfileReaderInfo *info, nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err); } } else if (type == G_TYPE_INT) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); if ( i64 == G_MININT64 @@ -2675,11 +2939,11 @@ read_one_setting_value (KeyfileReaderInfo *info, } else if (type == G_TYPE_BOOLEAN) { gboolean bool_val; - bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_name, key, &err); + bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err); if (!err) nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err); } else if (type == G_TYPE_CHAR) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { /* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */ i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64); @@ -2691,7 +2955,7 @@ read_one_setting_value (KeyfileReaderInfo *info, nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err); } } else if (type == G_TYPE_UINT64) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64); if ( u64 == G_MAXUINT64 @@ -2702,7 +2966,7 @@ read_one_setting_value (KeyfileReaderInfo *info, nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err); } } else if (type == G_TYPE_INT64) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64); if ( i64 == G_MAXINT64 @@ -2720,7 +2984,7 @@ read_one_setting_value (KeyfileReaderInfo *info, int i; gboolean already_warned = FALSE; - tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_info->setting_name, key, &length, NULL); array = g_byte_array_sized_new (length); for (i = 0; i < length; i++) { @@ -2747,14 +3011,14 @@ read_one_setting_value (KeyfileReaderInfo *info, gs_strfreev char **sa = NULL; gsize length; - sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); + sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL); g_object_set (setting, key, sa, NULL); } else if (type == G_TYPE_HASH_TABLE) { read_hash_of_string (keyfile, setting, key); } else if (type == G_TYPE_ARRAY) { read_array_of_uint (keyfile, setting, key); } else if (G_TYPE_IS_FLAGS (type)) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); if ( u64 == G_MAXUINT64 @@ -2765,7 +3029,7 @@ read_one_setting_value (KeyfileReaderInfo *info, nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err); } } else if (G_TYPE_IS_ENUM (type)) { - tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64); if ( i64 == G_MAXINT64 @@ -2887,13 +3151,11 @@ _read_setting (KeyfileReaderInfo *info) } for (i = 0; i < sett_info->property_infos_len; i++) { - const NMSettInfoProperty *property_info = &sett_info->property_infos[i]; - - if (property_info->param_spec) { - read_one_setting_value (info, setting, property_info); - if (info->error) - goto out; - } + read_one_setting_value (info, + setting, + &sett_info->property_infos[i]); + if (info->error) + goto out; } out: @@ -2937,7 +3199,7 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info) if (str) { if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) { if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, - _("key '%s.%s' is not not a valid 256 bit key in base64 encoding"), + _("key '%s.%s' is not a valid 256 bit key in base64 encoding"), info->group, key)) return; } @@ -2950,7 +3212,7 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info) if ( i64 == -1 || !_nm_setting_secret_flags_valid (i64)) { if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, - _("key '%s.%s' is not not a valid secret flag"), + _("key '%s.%s' is not a valid secret flag"), info->group, key)) return; } else @@ -2962,7 +3224,7 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info) if (errno != ENODATA) { if (i64 == -1) { if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, - _("key '%s.%s' is not not a integer in range 0 to 2^32"), + _("key '%s.%s' is not a integer in range 0 to 2^32"), info->group, key)) return; } else @@ -2974,7 +3236,7 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info) if (str && str[0]) { if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) { if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, - _("key '%s.%s' is not not a valid endpoint"), + _("key '%s.%s' is not a valid endpoint"), info->group, key)) return; } @@ -3202,40 +3464,47 @@ write_setting_value (KeyfileWriterInfo *info, NMSetting *setting, const NMSettInfoProperty *property_info) { + const NMMetaSettingInfo *setting_info; const ParseInfoProperty *pip; - const char *setting_name; const char *key; char numstr[64]; GValue value; GType type; nm_assert (!info->error); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); - if (!property_info->param_spec) - return; - - key = property_info->param_spec->name; + key = property_info->name; - pip = _parse_info_find (setting, key, &setting_name); + pip = _parse_info_find (setting, key, &setting_info); - if (!setting_name) { - /* the setting type is unknown. That is highly unexpected - * (and as this is currently only called from NetworkManager - * daemon, not possible). - * - * Still, handle it gracefully, because later keyfile writer will become - * public API of libnm, where @setting is (untrusted) user input. - * - * Gracefully here just means: ignore the setting. */ - return; + if (!pip) { + if (!setting_info) { + /* the setting type is unknown. That is highly unexpected + * (and as this is currently only called from NetworkManager + * daemon, not possible). + * + * Still, handle it gracefully, because later keyfile writer will become + * public API of libnm, where @setting is (untrusted) user input. + * + * Gracefully here just means: ignore the setting. */ + return; + } + if (!property_info->param_spec) + return; + if (nm_streq (key, NM_SETTING_NAME)) + return; + } else { + if (pip->has_writer_full) { + pip->writer_full (info, setting_info, property_info, pip, setting); + return; + } + if (pip->writer_skip) + return; } - if ( !pip - && nm_streq (key, NM_SETTING_NAME)) - return; - - if (pip && pip->writer_skip) - return; + nm_assert (property_info->param_spec); /* Don't write secrets that are owned by user secret agents or aren't * supposed to be saved. VPN secrets are handled specially though since @@ -3259,11 +3528,12 @@ write_setting_value (KeyfileWriterInfo *info, if ( (!pip || !pip->writer_persist_default) && g_param_value_defaults (property_info->param_spec, &value)) { - nm_assert (!g_key_file_has_key (info->keyfile, setting_name, key, NULL)); + nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL)); goto out_unset_value; } - if (pip && pip->writer) { + if ( pip + && pip->writer) { pip->writer (info, setting, key, &value); goto out_unset_value; } @@ -3274,27 +3544,27 @@ write_setting_value (KeyfileWriterInfo *info, str = g_value_get_string (&value); if (str) - nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, str); + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str); } else if (type == G_TYPE_UINT) { nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_INT) { nm_sprintf_buf (numstr, "%d", g_value_get_int (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_UINT64) { nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_INT64) { nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_BOOLEAN) { - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, g_value_get_boolean (&value) ? "true" : "false"); } else if (type == G_TYPE_CHAR) { nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_BYTES) { GBytes *bytes; const guint8 *data; @@ -3304,22 +3574,22 @@ write_setting_value (KeyfileWriterInfo *info, data = bytes ? g_bytes_get_data (bytes, &len) : NULL; if (data != NULL && len > 0) - nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len); + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len); } else if (type == G_TYPE_STRV) { char **array; array = (char **) g_value_get_boxed (&value); - nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_name, key, (const char **const) array, g_strv_length (array)); + nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array)); } else if (type == G_TYPE_HASH_TABLE) { write_hash_of_string (info->keyfile, setting, key, &value); } else if (type == G_TYPE_ARRAY) { write_array_of_uint (info->keyfile, setting, key, &value); } else if (G_VALUE_HOLDS_FLAGS (&value)) { nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else if (G_VALUE_HOLDS_ENUM (&value)) { nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); } else g_return_if_reached (); diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 8a23fb34..5114ff74 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -24,7 +24,7 @@ #include "nm-setting-8021x.h" -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-crypto.h" #include "nm-utils-private.h" diff --git a/libnm-core/nm-setting-bridge-port.c b/libnm-core/nm-setting-bridge-port.c index 614c883c..7a8b345f 100644 --- a/libnm-core/nm-setting-bridge-port.c +++ b/libnm-core/nm-setting-bridge-port.c @@ -42,22 +42,60 @@ /*****************************************************************************/ -NM_GOBJECT_PROPERTIES_DEFINE_BASE ( +NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridgePort, PROP_PRIORITY, PROP_PATH_COST, PROP_HAIRPIN_MODE, + PROP_VLANS, ); typedef struct { guint16 priority; guint16 path_cost; gboolean hairpin_mode; + GPtrArray *vlans; } NMSettingBridgePortPrivate; G_DEFINE_TYPE (NMSettingBridgePort, nm_setting_bridge_port, NM_TYPE_SETTING) #define NM_SETTING_BRIDGE_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING_BRIDGE_PORT, NMSettingBridgePortPrivate)) +static int +vlan_ptr_cmp (gconstpointer a, gconstpointer b) +{ + const NMBridgeVlan *vlan_a = *(const NMBridgeVlan **) a; + const NMBridgeVlan *vlan_b = *(const NMBridgeVlan **) b; + + return nm_bridge_vlan_cmp (vlan_a, vlan_b); +} + +gboolean +_nm_setting_bridge_port_sort_vlans (NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + gboolean need_sort = FALSE; + guint i; + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + for (i = 1; i < priv->vlans->len; i++) { + NMBridgeVlan *vlan_prev = priv->vlans->pdata[i - 1]; + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + + if (nm_bridge_vlan_cmp (vlan_prev, vlan) > 0) { + need_sort = TRUE; + break; + } + } + + if (need_sort) { + g_ptr_array_sort (priv->vlans, vlan_ptr_cmp); + _notify (setting, PROP_VLANS); + } + + return need_sort; +} + /*****************************************************************************/ /** @@ -102,11 +140,171 @@ nm_setting_bridge_port_get_hairpin_mode (NMSettingBridgePort *setting) return NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting)->hairpin_mode; } +/** + * nm_setting_bridge_port_add_vlan: + * @setting: the #NMSettingBridgePort + * @vlan: the vlan to add + * + * Appends a new vlan and associated information to the setting. The + * given vlan gets sealed and a reference to it is added. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_add_vlan (NMSettingBridgePort *setting, + NMBridgeVlan *vlan) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting)); + g_return_if_fail (vlan); + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + nm_bridge_vlan_seal (vlan); + nm_bridge_vlan_ref (vlan); + + g_ptr_array_add (priv->vlans, vlan); + _notify (setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_port_get_num_vlans: + * @setting: the #NMSettingBridgePort + * + * Returns: the number of VLANs + * + * Since: 1.18 + **/ +guint +nm_setting_bridge_port_get_num_vlans (NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting), 0); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + return priv->vlans->len; +} + +/** + * nm_setting_bridge_port_get_vlan: + * @setting: the #NMSettingBridgePort + * @idx: index number of the VLAN to return + * + * Returns: (transfer none): the VLAN at index @idx + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_setting_bridge_port_get_vlan (NMSettingBridgePort *setting, guint idx) +{ + NMSettingBridgePortPrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting), NULL); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + g_return_val_if_fail (idx < priv->vlans->len, NULL); + + return priv->vlans->pdata[idx]; +} + +/** + * nm_setting_bridge_port_remove_vlan: + * @setting: the #NMSettingBridgePort + * @idx: index number of the VLAN. + * + * Removes the vlan at index @idx. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_remove_vlan (NMSettingBridgePort *setting, guint idx) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting)); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + g_return_if_fail (idx < priv->vlans->len); + + g_ptr_array_remove_index (priv->vlans, idx); + _notify (setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_port_remove_vlan_by_vid: + * @setting: the #NMSettingBridgePort + * @vid_start: the vlan start index + * @vid_end: the vlan end index + * + * Remove the VLAN with range @vid_start to @vid_end. + * If @vid_end is zero, it is assumed to be equal to @vid_start + * and so the single-id VLAN with id @vid_start is removed. + * + * Returns: %TRUE if the vlan was found and removed; %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_port_remove_vlan_by_vid (NMSettingBridgePort *setting, + guint16 vid_start, + guint16 vid_end) +{ + NMSettingBridgePortPrivate *priv; + guint16 v_start, v_end; + NMBridgeVlan *vlan; + guint i; + + if (vid_end == 0) + vid_end = vid_start; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting), FALSE); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + for (i = 0; i < priv->vlans->len; i++) { + vlan = (NMBridgeVlan *) priv->vlans->pdata[i]; + nm_bridge_vlan_get_vid_range (vlan, &v_start, &v_end); + if (v_start == vid_start && v_end == vid_end) { + g_ptr_array_remove_index (priv->vlans, i); + _notify (setting, PROP_VLANS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_bridge_port_clear_vlans: + * @setting: the #NMSettingBridgePort + * + * Removes all configured VLANs. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_clear_vlans (NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting)); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + if (priv->vlans->len != 0) { + g_ptr_array_set_size (priv->vlans, 0); + _notify (setting, PROP_VLANS); + } +} + /*****************************************************************************/ static gboolean verify (NMSetting *setting, NMConnection *connection, GError **error) { + NMSettingBridgePortPrivate *priv; + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + if (connection) { NMSettingConnection *s_con; const char *slave_type; @@ -136,9 +334,58 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) } } + if (!_nm_utils_bridge_vlan_verify_list (priv->vlans, + FALSE, + error, + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + NM_SETTING_BRIDGE_PORT_VLANS)) + return FALSE; + + /* Failures from here on are NORMALIZABLE... */ + + if (!_nm_utils_bridge_vlan_verify_list (priv->vlans, + TRUE, + error, + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + NM_SETTING_BRIDGE_PORT_VLANS)) + return NM_SETTING_VERIFY_NORMALIZABLE; + return TRUE; } +static NMTernary +compare_property (const NMSettInfoSetting *sett_info, + guint property_idx, + NMSetting *setting, + NMSetting *other, + NMSettingCompareFlags flags) +{ + NMSettingBridgePortPrivate *priv_a; + NMSettingBridgePortPrivate *priv_b; + guint i; + + if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_PORT_VLANS)) { + if (other) { + priv_a = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + priv_b = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (other); + + if (priv_a->vlans->len != priv_b->vlans->len) + return FALSE; + for (i = 0; i < priv_a->vlans->len; i++) { + if (nm_bridge_vlan_cmp (priv_a->vlans->pdata[i], priv_b->vlans->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS (nm_setting_bridge_port_parent_class)->compare_property (sett_info, + property_idx, + setting, + other, + flags); +} + /*****************************************************************************/ static void @@ -157,6 +404,11 @@ get_property (GObject *object, guint prop_id, case PROP_HAIRPIN_MODE: g_value_set_boolean (value, priv->hairpin_mode); break; + case PROP_VLANS: + g_value_take_boxed (value, _nm_utils_copy_array (priv->vlans, + (NMUtilsCopyFunc) nm_bridge_vlan_ref, + (GDestroyNotify) nm_bridge_vlan_unref)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -179,6 +431,12 @@ set_property (GObject *object, guint prop_id, case PROP_HAIRPIN_MODE: priv->hairpin_mode = g_value_get_boolean (value); break; + case PROP_VLANS: + g_ptr_array_unref (priv->vlans); + priv->vlans = _nm_utils_copy_array (g_value_get_boxed (value), + (NMUtilsCopyFunc) _nm_bridge_vlan_dup_and_seal, + (GDestroyNotify) nm_bridge_vlan_unref); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -190,6 +448,9 @@ set_property (GObject *object, guint prop_id, static void nm_setting_bridge_port_init (NMSettingBridgePort *setting) { + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); + + priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); } /** @@ -205,17 +466,30 @@ nm_setting_bridge_port_new (void) return (NMSetting *) g_object_new (NM_TYPE_SETTING_BRIDGE_PORT, NULL); } +static void +finalize (GObject *object) +{ + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (object); + + g_ptr_array_unref (priv->vlans); + + G_OBJECT_CLASS (nm_setting_bridge_port_parent_class)->finalize (object); +} + static void nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMSettingClass *setting_class = NM_SETTING_CLASS (klass); + GArray *properties_override = _nm_sett_info_property_override_create_array (); g_type_class_add_private (klass, sizeof (NMSettingBridgePortPrivate)); + object_class->finalize = finalize; object_class->get_property = get_property; object_class->set_property = set_property; + setting_class->compare_property = compare_property; setting_class->verify = verify; /** @@ -280,7 +554,48 @@ nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass) NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingBridgePort:vlans: (type GPtrArray(NMBridgeVlan)) + * + * Array of bridge VLAN objects. In addition to the VLANs + * specified here, the port will also have the default-pvid + * VLAN configured on the bridge by the bridge.vlan-default-pvid + * property. + * + * In nmcli the VLAN list can be specified with the following + * syntax: + * + * $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... + * + * where $vid is either a single id between 1 and 4094 or a + * range, represented as a couple of ids separated by a dash. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlans + * variable: BRIDGE_PORT_VLANS + * description: List of VLANs on the bridge port + * example: BRIDGE_PORT_VLANS="1 pvid untagged,20,300-400 untagged" + * ---end--- + */ + obj_properties[PROP_VLANS] = + g_param_spec_boxed (NM_SETTING_BRIDGE_PORT_VLANS, "", "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + _properties_override_add_override (properties_override, + obj_properties[PROP_VLANS], + G_VARIANT_TYPE ("aa{sv}"), + _nm_utils_bridge_vlans_to_dbus, + _nm_utils_bridge_vlans_from_dbus, + NULL); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); - _nm_setting_class_commit (setting_class, NM_META_SETTING_TYPE_BRIDGE_PORT); + _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_BRIDGE_PORT, + NULL, properties_override); + } diff --git a/libnm-core/nm-setting-bridge-port.h b/libnm-core/nm-setting-bridge-port.h index 0ff6d9f1..5b75c1ec 100644 --- a/libnm-core/nm-setting-bridge-port.h +++ b/libnm-core/nm-setting-bridge-port.h @@ -27,6 +27,7 @@ #endif #include "nm-setting.h" +#include "nm-setting-bridge.h" G_BEGIN_DECLS @@ -42,6 +43,7 @@ G_BEGIN_DECLS #define NM_SETTING_BRIDGE_PORT_PRIORITY "priority" #define NM_SETTING_BRIDGE_PORT_PATH_COST "path-cost" #define NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE "hairpin-mode" +#define NM_SETTING_BRIDGE_PORT_VLANS "vlans" /** * NMSettingBridgePort: @@ -69,6 +71,22 @@ guint16 nm_setting_bridge_port_get_path_cost (NMSettingBridgePort *settin gboolean nm_setting_bridge_port_get_hairpin_mode (NMSettingBridgePort *setting); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_port_add_vlan (NMSettingBridgePort *setting, + NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +guint nm_setting_bridge_port_get_num_vlans (NMSettingBridgePort *setting); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan *nm_setting_bridge_port_get_vlan (NMSettingBridgePort *setting, guint idx); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_port_remove_vlan (NMSettingBridgePort *setting, guint idx); +NM_AVAILABLE_IN_1_18 +gboolean nm_setting_bridge_port_remove_vlan_by_vid (NMSettingBridgePort *setting, + guint16 vid_start, + guint16 vid_end); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_port_clear_vlans (NMSettingBridgePort *setting); + G_END_DECLS #endif /* __NM_SETTING_BRIDGE_PORT_H__ */ diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c index e418013c..c6aca021 100644 --- a/libnm-core/nm-setting-bridge.c +++ b/libnm-core/nm-setting-bridge.c @@ -40,7 +40,7 @@ /*****************************************************************************/ -NM_GOBJECT_PROPERTIES_DEFINE_BASE ( +NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge, PROP_MAC_ADDRESS, PROP_STP, PROP_PRIORITY, @@ -50,6 +50,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE ( PROP_AGEING_TIME, PROP_GROUP_FORWARD_MASK, PROP_MULTICAST_SNOOPING, + PROP_VLAN_FILTERING, + PROP_VLAN_DEFAULT_PVID, + PROP_VLANS, ); typedef struct { @@ -62,6 +65,9 @@ typedef struct { guint32 ageing_time; guint16 group_forward_mask; gboolean multicast_snooping; + gboolean vlan_filtering; + guint16 vlan_default_pvid; + GPtrArray *vlans; } NMSettingBridgePrivate; G_DEFINE_TYPE (NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING) @@ -70,6 +76,506 @@ G_DEFINE_TYPE (NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING) /*****************************************************************************/ +G_DEFINE_BOXED_TYPE (NMBridgeVlan, nm_bridge_vlan, _nm_bridge_vlan_dup, nm_bridge_vlan_unref) + +struct _NMBridgeVlan { + guint refcount; + guint16 vid_start; + guint16 vid_end; + bool untagged:1; + bool pvid:1; + bool sealed:1; +}; + +static gboolean +NM_IS_BRIDGE_VLAN (const NMBridgeVlan *self, gboolean also_sealed) +{ + return self + && self->refcount > 0 + && (also_sealed || !self->sealed); +} + +/** + * nm_bridge_vlan_new: + * @vid_start: the start VLAN id, must be between 1 and 4094. + * @vid_end: the end VLAN id, must be 0 or between @vid_start and 4094. + * + * Creates a new #NMBridgeVlan object for the given VLAN id range. + * Setting @vid_end to 0 is equivalent to setting it to @vid_start + * and creates a single-id VLAN. + * + * Returns: (transfer full): the new #NMBridgeVlan object. + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_bridge_vlan_new (guint16 vid_start, guint16 vid_end) +{ + NMBridgeVlan *vlan; + + if (vid_end == 0) + vid_end = vid_start; + + g_return_val_if_fail (vid_start >= NM_BRIDGE_VLAN_VID_MIN, NULL); + g_return_val_if_fail (vid_end <= NM_BRIDGE_VLAN_VID_MAX, NULL); + g_return_val_if_fail (vid_start <= vid_end, NULL); + + vlan = g_slice_new0 (NMBridgeVlan); + vlan->refcount = 1; + vlan->vid_start = vid_start; + vlan->vid_end = vid_end; + + return vlan; +} + +/** + * nm_bridge_vlan_ref: + * @vlan: the #NMBridgeVlan + * + * Increases the reference count of the object. + * + * Returns: the input argument @vlan object. + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_bridge_vlan_ref (NMBridgeVlan *vlan) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL); + + nm_assert (vlan->refcount < G_MAXUINT); + + vlan->refcount++; + return vlan; +} + +/** + * nm_bridge_vlan_unref: + * @vlan: the #NMBridgeVlan + * + * Decreases the reference count of the object. If the reference count + * reaches zero the object will be destroyed. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_unref (NMBridgeVlan *vlan) +{ + g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE)); + + if (--vlan->refcount == 0) + g_slice_free (NMBridgeVlan, vlan); +} + +/** + * nm_bridge_vlan_cmp: + * @a: a #NMBridgeVlan + * @b: another #NMBridgeVlan + * + * Compare two bridge VLAN objects. + * + * Returns: zero of the two instances are equivalent or + * a non-zero integer otherwise. This defines a total ordering + * over the VLANs. Whether a VLAN is sealed or not does not + * affect the comparison. + * + * Since: 1.18 + **/ +int +nm_bridge_vlan_cmp (const NMBridgeVlan *a, const NMBridgeVlan *b) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (a, TRUE), 0); + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (b, TRUE), 0); + + NM_CMP_SELF (a, b); + NM_CMP_FIELD (a, b, vid_start); + NM_CMP_FIELD (a, b, vid_end); + NM_CMP_FIELD_BOOL (a, b, untagged); + NM_CMP_FIELD_BOOL (a, b, pvid); + + return 0; +} + +NMBridgeVlan * +_nm_bridge_vlan_dup (const NMBridgeVlan *vlan) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL); + + if (vlan->sealed) { + nm_bridge_vlan_ref ((NMBridgeVlan *) vlan); + return (NMBridgeVlan *) vlan; + } + + return nm_bridge_vlan_new_clone (vlan); +} + +NMBridgeVlan * +_nm_bridge_vlan_dup_and_seal (const NMBridgeVlan *vlan) +{ + NMBridgeVlan *new; + + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL); + + new = _nm_bridge_vlan_dup (vlan); + nm_bridge_vlan_seal (new); + + return new; +} + +/** + * nm_bridge_vlan_get_vid_range: + * @vlan: the #NMBridgeVlan + * @vid_start: location to store the VLAN id range start. + * @vid_end: location to store the VLAN id range end + * + * Gets the VLAN id range. + * + * Returns: %TRUE is the VLAN specifies a range, %FALSE if it is + * a single-id VLAN. + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_get_vid_range (const NMBridgeVlan *vlan, + guint16 *vid_start, + guint16 *vid_end) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), 0); + + NM_SET_OUT (vid_start, vlan->vid_start); + NM_SET_OUT (vid_end, vlan->vid_end); + + return vlan->vid_start != vlan->vid_end; +} + +/** + * nm_bridge_vlan_is_untagged: + * @vlan: the #NMBridgeVlan + * + * Returns whether the VLAN is untagged. + * + * Returns: %TRUE if the VLAN is untagged, %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_is_untagged (const NMBridgeVlan *vlan) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE); + + return vlan->untagged; +} + +/** + * nm_bridge_vlan_is_pvid: + * @vlan: the #NMBridgeVlan + * + * Returns whether the VLAN is the PVID for the port. + * + * Returns: %TRUE if the VLAN is the PVID + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_is_pvid (const NMBridgeVlan *vlan) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE); + + return vlan->pvid; +} + +/** + * nm_bridge_vlan_set_untagged: + * @vlan: the #NMBridgeVlan + * @value: the new value + * + * Change the value of the untagged property of the VLAN. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_set_untagged (NMBridgeVlan *vlan, gboolean value) +{ + g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, FALSE)); + + vlan->untagged = value; +} + +/** + * nm_bridge_vlan_set_pvid: + * @vlan: the #NMBridgeVlan + * @value: the new value + * + * Change the value of the PVID property of the VLAN. It + * is invalid to set the value to %TRUE for non-single-id + * VLANs. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_set_pvid (NMBridgeVlan *vlan, gboolean value) +{ + g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, FALSE)); + g_return_if_fail (!value || vlan->vid_start == vlan->vid_end); + + vlan->pvid = value; +} + +/** + * nm_bridge_vlan_is_sealed: + * @vlan: the #NMBridgeVlan instance + * + * Returns: whether @self is sealed or not. + * + * Since: 1.18 + */ +gboolean +nm_bridge_vlan_is_sealed (const NMBridgeVlan *vlan) +{ + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), FALSE); + + return vlan->sealed; +} + +/** + * nm_bridge_vlan_seal: + * @vlan: the #NMBridgeVlan instance + * + * Seal the #NMBridgeVlan instance. Afterwards, it is a bug + * to call all functions that modify the instance (except ref/unref). + * A sealed instance cannot be unsealed again, but you can create + * an unsealed copy with nm_bridge_vlan_new_clone(). + * + * Since: 1.18 + */ +void +nm_bridge_vlan_seal (NMBridgeVlan *vlan) +{ + g_return_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE)); + + vlan->sealed = TRUE; +} + +/** + * nm_bridge_vlan_new_clone: + * @vlan: the #NMBridgeVlan instance to copy + * + * Returns: (transfer full): a clone of @vlan. This instance + * is always unsealed. + * + * Since: 1.18 + */ +NMBridgeVlan * +nm_bridge_vlan_new_clone (const NMBridgeVlan *vlan) +{ + NMBridgeVlan *copy; + + g_return_val_if_fail (NM_IS_BRIDGE_VLAN (vlan, TRUE), NULL); + + copy = nm_bridge_vlan_new (vlan->vid_start, vlan->vid_end); + copy->untagged = vlan->untagged; + copy->pvid = vlan->pvid; + + return copy; +} + +void +_nm_bridge_vlan_str_append_rest (const NMBridgeVlan *vlan, + GString *string, + gboolean leading_space) +{ + if (nm_bridge_vlan_is_pvid (vlan)) { + if (leading_space) + g_string_append_c (string, ' '); + g_string_append (string, "pvid"); + leading_space = TRUE; + } + if (nm_bridge_vlan_is_untagged (vlan)) { + if (leading_space) + g_string_append_c (string, ' '); + g_string_append (string, "untagged"); + leading_space = TRUE; + } +} + +/** + * nm_bridge_vlan_to_str: + * @vlan: the %NMBridgeVlan + * @error: location of the error + * + * Convert a %NMBridgeVlan to a string. + * + * Returns: formatted string or %NULL + * + * Since: 1.18 + */ +char * +nm_bridge_vlan_to_str (const NMBridgeVlan *vlan, GError **error) +{ + GString *string; + + g_return_val_if_fail (vlan, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + /* The function never fails at the moment, but it might in the + * future if more parameters are added to the object that could + * make it invalid. */ + + string = g_string_sized_new (28); + + if (vlan->vid_start == vlan->vid_end) + g_string_append_printf (string, "%u", vlan->vid_start); + else + g_string_append_printf (string, "%u-%u", vlan->vid_start, vlan->vid_end); + + _nm_bridge_vlan_str_append_rest (vlan, string, TRUE); + + return g_string_free (string, FALSE); +} + +/** + * nm_bridge_vlan_from_str: + * @str: the string representation of a bridge VLAN + * @error: location of the error + * + * Parses the string representation of the queueing + * discipline to a %NMBridgeVlan instance. + * + * Returns: the %NMBridgeVlan or %NULL + * + * Since: 1.18 + */ +NMBridgeVlan * +nm_bridge_vlan_from_str (const char *str, GError **error) +{ + NMBridgeVlan *vlan = NULL; + gs_free const char **tokens = NULL; + guint i, vid_start, vid_end = 0; + gboolean pvid = FALSE; + gboolean untagged = FALSE; + char *c; + + g_return_val_if_fail (str, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + tokens = nm_utils_escaped_tokens_split (str, NM_ASCII_SPACES); + if (!tokens || !tokens[0]) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "missing VLAN id"); + return NULL; + } + + c = strchr (tokens[0], '-'); + if (c) + *c = '\0'; + + vid_start = _nm_utils_ascii_str_to_uint64 (tokens[0], + 10, + NM_BRIDGE_VLAN_VID_MIN, + NM_BRIDGE_VLAN_VID_MAX, + G_MAXUINT); + if (vid_start == G_MAXUINT) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range start '%s', must be in [1,4094]", tokens[0]); + return NULL; + } + + if (c) { + vid_end = _nm_utils_ascii_str_to_uint64 (c + 1, + 10, + NM_BRIDGE_VLAN_VID_MIN, + NM_BRIDGE_VLAN_VID_MAX, + G_MAXUINT); + if (vid_end == G_MAXUINT) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range end '%s', must be in [1,4094]", c + 1); + return NULL; + } + if (vid_end < vid_start) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range %u-%u, start VLAN id must be less than end VLAN id", + vid_start, vid_end); + return NULL; + } + } else + vid_end = vid_start; + + for (i = 1; tokens[i]; i++) { + if (nm_streq (tokens[i], "pvid")) { + if (vid_start != vid_end) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "a VLAN range can't be a PVID"); + return NULL; + } + pvid = TRUE; + } else if (nm_streq (tokens[i], "untagged")) + untagged = TRUE; + else { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid option '%s'", tokens[i]); + return NULL; + } + } + + vlan = nm_bridge_vlan_new (vid_start, vid_end); + nm_bridge_vlan_set_pvid (vlan, pvid); + nm_bridge_vlan_set_untagged (vlan, untagged); + + return vlan; +} + +/*****************************************************************************/ + +static int +vlan_ptr_cmp (gconstpointer a, gconstpointer b) +{ + const NMBridgeVlan *vlan_a = *(const NMBridgeVlan **) a; + const NMBridgeVlan *vlan_b = *(const NMBridgeVlan **) b; + + return nm_bridge_vlan_cmp (vlan_a, vlan_b); +} + +gboolean +_nm_setting_bridge_sort_vlans (NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + gboolean need_sort = FALSE; + guint i; + + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + for (i = 1; i < priv->vlans->len; i++) { + NMBridgeVlan *vlan_prev = priv->vlans->pdata[i - 1]; + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + + if (nm_bridge_vlan_cmp (vlan_prev, vlan) > 0) { + need_sort = TRUE; + break; + } + } + + if (need_sort) { + g_ptr_array_sort (priv->vlans, vlan_ptr_cmp); + _notify (setting, PROP_VLANS); + } + + return need_sort; +} + +/*****************************************************************************/ + /** * nm_setting_bridge_get_mac_address: * @setting: the #NMSettingBridge @@ -200,6 +706,194 @@ nm_setting_bridge_get_multicast_snooping (NMSettingBridge *setting) return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_snooping; } +/** + * nm_setting_bridge_get_vlan_filtering: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-filtering property of the setting + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_get_vlan_filtering (NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_filtering; +} + +/** + * nm_setting_bridge_get_vlan_default_pvid: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-default-pvid property of the setting + * + * Since: 1.18 + **/ +guint16 +nm_setting_bridge_get_vlan_default_pvid (NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 1); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_default_pvid; +} + +/** + * nm_setting_bridge_add_vlan: + * @setting: the #NMSettingBridge + * @vlan: the vlan to add + * + * Appends a new vlan and associated information to the setting. The + * given vlan gets sealed and a reference to it is added. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_add_vlan (NMSettingBridge *setting, + NMBridgeVlan *vlan) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE (setting)); + g_return_if_fail (vlan); + + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + nm_bridge_vlan_seal (vlan); + nm_bridge_vlan_ref (vlan); + + g_ptr_array_add (priv->vlans, vlan); + _notify (setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_get_num_vlans: + * @setting: the #NMSettingBridge + * + * Returns: the number of VLANs + * + * Since: 1.18 + **/ +guint +nm_setting_bridge_get_num_vlans (NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + return priv->vlans->len; +} + +/** + * nm_setting_bridge_get_vlan: + * @setting: the #NMSettingBridge + * @idx: index number of the VLAN to return + * + * Returns: (transfer none): the VLAN at index @idx + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_setting_bridge_get_vlan (NMSettingBridge *setting, guint idx) +{ + NMSettingBridgePrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), NULL); + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + g_return_val_if_fail (idx < priv->vlans->len, NULL); + + return priv->vlans->pdata[idx]; +} + +/** + * nm_setting_bridge_remove_vlan: + * @setting: the #NMSettingBridge + * @idx: index number of the VLAN. + * + * Removes the vlan at index @idx. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_remove_vlan (NMSettingBridge *setting, guint idx) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE (setting)); + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + g_return_if_fail (idx < priv->vlans->len); + + g_ptr_array_remove_index (priv->vlans, idx); + _notify (setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_remove_vlan_by_vid: + * @setting: the #NMSettingBridge + * @vid_start: the vlan start index + * @vid_end: the vlan end index + * + * Remove the VLAN with range @vid_start to @vid_end. + * If @vid_end is zero, it is assumed to be equal to @vid_start + * and so the single-id VLAN with id @vid_start is removed. + * + * Returns: %TRUE if the vlan was found and removed; %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_remove_vlan_by_vid (NMSettingBridge *setting, + guint16 vid_start, + guint16 vid_end) +{ + NMSettingBridgePrivate *priv; + NMBridgeVlan *vlan; + guint i; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE); + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + if (vid_end == 0) + vid_end = vid_start; + + for (i = 0; i < priv->vlans->len; i++) { + vlan = (NMBridgeVlan *) priv->vlans->pdata[i]; + if (vlan->vid_start == vid_start && vlan->vid_end == vid_end) { + g_ptr_array_remove_index (priv->vlans, i); + _notify (setting, PROP_VLANS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_bridge_clear_vlans: + * @setting: the #NMSettingBridge + * + * Removes all configured VLANs. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_clear_vlans (NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail (NM_IS_SETTING_BRIDGE (setting)); + priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + if (priv->vlans->len != 0) { + g_ptr_array_set_size (priv->vlans, 0); + _notify (setting, PROP_VLANS); + } +} + +/*****************************************************************************/ + static gboolean check_range (guint32 val, guint32 min, @@ -278,7 +972,59 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) return FALSE; } - return _nm_connection_verify_required_interface_name (connection, error); + if (!_nm_connection_verify_required_interface_name (connection, error)) + return FALSE; + + if (!_nm_utils_bridge_vlan_verify_list (priv->vlans, + FALSE, + error, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_VLANS)) + return FALSE; + + /* Failures from here on are NORMALIZABLE... */ + + if (!_nm_utils_bridge_vlan_verify_list (priv->vlans, + TRUE, + error, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_VLANS)) + return NM_SETTING_VERIFY_NORMALIZABLE; + + return TRUE; +} + +static NMTernary +compare_property (const NMSettInfoSetting *sett_info, + guint property_idx, + NMSetting *setting, + NMSetting *other, + NMSettingCompareFlags flags) +{ + NMSettingBridgePrivate *priv_a; + NMSettingBridgePrivate *priv_b; + guint i; + + if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_VLANS)) { + if (other) { + priv_a = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + priv_b = NM_SETTING_BRIDGE_GET_PRIVATE (other); + + if (priv_a->vlans->len != priv_b->vlans->len) + return FALSE; + for (i = 0; i < priv_a->vlans->len; i++) { + if (nm_bridge_vlan_cmp (priv_a->vlans->pdata[i], priv_b->vlans->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS (nm_setting_bridge_parent_class)->compare_property (sett_info, + property_idx, + setting, + other, + flags); } /*****************************************************************************/ @@ -318,6 +1064,17 @@ get_property (GObject *object, guint prop_id, case PROP_MULTICAST_SNOOPING: g_value_set_boolean (value, priv->multicast_snooping); break; + case PROP_VLAN_FILTERING: + g_value_set_boolean (value, priv->vlan_filtering); + break; + case PROP_VLAN_DEFAULT_PVID: + g_value_set_uint (value, priv->vlan_default_pvid); + break; + case PROP_VLANS: + g_value_take_boxed (value, _nm_utils_copy_array (priv->vlans, + (NMUtilsCopyFunc) nm_bridge_vlan_ref, + (GDestroyNotify) nm_bridge_vlan_unref)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -360,6 +1117,18 @@ set_property (GObject *object, guint prop_id, case PROP_MULTICAST_SNOOPING: priv->multicast_snooping = g_value_get_boolean (value); break; + case PROP_VLAN_FILTERING: + priv->vlan_filtering = g_value_get_boolean (value); + break; + case PROP_VLAN_DEFAULT_PVID: + priv->vlan_default_pvid = g_value_get_uint (value); + break; + case PROP_VLANS: + g_ptr_array_unref (priv->vlans); + priv->vlans = _nm_utils_copy_array (g_value_get_boxed (value), + (NMUtilsCopyFunc) _nm_bridge_vlan_dup_and_seal, + (GDestroyNotify) nm_bridge_vlan_unref); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -371,6 +1140,9 @@ set_property (GObject *object, guint prop_id, static void nm_setting_bridge_init (NMSettingBridge *setting) { + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting); + + priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); } /** @@ -392,6 +1164,7 @@ finalize (GObject *object) NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (object); g_free (priv->mac_address); + g_ptr_array_unref (priv->vlans); G_OBJECT_CLASS (nm_setting_bridge_parent_class)->finalize (object); } @@ -409,6 +1182,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) object_class->set_property = set_property; object_class->finalize = finalize; + setting_class->compare_property = compare_property; setting_class->verify = verify; /** @@ -627,6 +1401,91 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingBridge:vlan-filtering: + * + * Control whether VLAN filtering is enabled on the bridge. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlan-filtering + * variable: BRIDGING_OPTS: vlan_filtering= + * values: 0 or 1 + * default: 0 + * description: VLAN filtering support. + * ---end--- + */ + obj_properties[PROP_VLAN_FILTERING] = + g_param_spec_boolean (NM_SETTING_BRIDGE_VLAN_FILTERING, "", "", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:vlan-default-pvid: + * + * The default PVID for the ports of the bridge, that is the VLAN id + * assigned to incoming untagged frames. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlan-default-pvid + * variable: BRIDGING_OPTS: default_pvid= + * values: 0 - 4094 + * default: 1 + * description: default VLAN PVID. + * ---end--- + */ + obj_properties[PROP_VLAN_DEFAULT_PVID] = + g_param_spec_uint (NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, "", "", + 0, NM_BRIDGE_VLAN_VID_MAX, 1, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:vlans: (type GPtrArray(NMBridgeVlan)) + * + * Array of bridge VLAN objects. In addition to the VLANs + * specified here, the bridge will also have the default-pvid + * VLAN configured by the bridge.vlan-default-pvid property. + * + * In nmcli the VLAN list can be specified with the following + * syntax: + * + * $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... + * + * where $vid is either a single id between 1 and 4094 or a + * range, represented as a couple of ids separated by a dash. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlans + * variable: BRIDGE_VLANS + * description: List of VLANs on the bridge + * example: BRIDGE_VLANS="1 pvid untagged,20,300-400 untagged" + * ---end--- + */ + obj_properties[PROP_VLANS] = + g_param_spec_boxed (NM_SETTING_BRIDGE_VLANS, "", "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + _properties_override_add_override (properties_override, + obj_properties[PROP_VLANS], + G_VARIANT_TYPE ("aa{sv}"), + _nm_utils_bridge_vlans_to_dbus, + _nm_utils_bridge_vlans_from_dbus, + NULL); + /* ---dbus--- * property: interface-name * format: string diff --git a/libnm-core/nm-setting-bridge.h b/libnm-core/nm-setting-bridge.h index d640dde3..c01ab35c 100644 --- a/libnm-core/nm-setting-bridge.h +++ b/libnm-core/nm-setting-bridge.h @@ -48,6 +48,12 @@ G_BEGIN_DECLS #define NM_SETTING_BRIDGE_AGEING_TIME "ageing-time" #define NM_SETTING_BRIDGE_GROUP_FORWARD_MASK "group-forward-mask" #define NM_SETTING_BRIDGE_MULTICAST_SNOOPING "multicast-snooping" +#define NM_SETTING_BRIDGE_VLAN_FILTERING "vlan-filtering" +#define NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID "vlan-default-pvid" +#define NM_SETTING_BRIDGE_VLANS "vlans" + +#define NM_BRIDGE_VLAN_VID_MIN 1 +#define NM_BRIDGE_VLAN_VID_MAX 4094 /** * NMSettingBridge: @@ -65,6 +71,8 @@ typedef struct { gpointer padding[4]; } NMSettingBridgeClass; +typedef struct _NMBridgeVlan NMBridgeVlan; + GType nm_setting_bridge_get_type (void); NMSetting * nm_setting_bridge_new (void); @@ -86,6 +94,57 @@ NM_AVAILABLE_IN_1_10 guint16 nm_setting_bridge_get_group_forward_mask (NMSettingBridge *setting); gboolean nm_setting_bridge_get_multicast_snooping (NMSettingBridge *setting); +NM_AVAILABLE_IN_1_18 +gboolean nm_setting_bridge_get_vlan_filtering (NMSettingBridge *setting); +NM_AVAILABLE_IN_1_18 +guint16 nm_setting_bridge_get_vlan_default_pvid (NMSettingBridge *setting); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_add_vlan (NMSettingBridge *setting, + NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +guint nm_setting_bridge_get_num_vlans (NMSettingBridge *setting); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan *nm_setting_bridge_get_vlan (NMSettingBridge *setting, guint idx); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_remove_vlan (NMSettingBridge *setting, guint idx); +NM_AVAILABLE_IN_1_18 +gboolean nm_setting_bridge_remove_vlan_by_vid (NMSettingBridge *setting, + guint16 vid_start, + guint16 vid_end); +NM_AVAILABLE_IN_1_18 +void nm_setting_bridge_clear_vlans (NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_18 +GType nm_bridge_vlan_get_type (void); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan * nm_bridge_vlan_new (guint16 vid_start, guint16 vid_end); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan * nm_bridge_vlan_ref (NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +void nm_bridge_vlan_unref (NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan * nm_bridge_vlan_new_clone (const NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +int nm_bridge_vlan_cmp (const NMBridgeVlan *a, const NMBridgeVlan *b); +NM_AVAILABLE_IN_1_18 +void nm_bridge_vlan_seal (NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +gboolean nm_bridge_vlan_is_sealed (const NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +void nm_bridge_vlan_set_untagged (NMBridgeVlan *vlan, gboolean value); +NM_AVAILABLE_IN_1_18 +void nm_bridge_vlan_set_pvid (NMBridgeVlan *vlan, gboolean value); +NM_AVAILABLE_IN_1_18 +gboolean nm_bridge_vlan_get_vid_range (const NMBridgeVlan *vlan, guint16 *vid_start, guint16 *vid_end); +NM_AVAILABLE_IN_1_18 +gboolean nm_bridge_vlan_is_untagged (const NMBridgeVlan *vlan); +NM_AVAILABLE_IN_1_18 +gboolean nm_bridge_vlan_is_pvid (const NMBridgeVlan *vlan); + +NM_AVAILABLE_IN_1_18 +char * nm_bridge_vlan_to_str (const NMBridgeVlan *vlan, GError **error); +NM_AVAILABLE_IN_1_18 +NMBridgeVlan * nm_bridge_vlan_from_str (const char *str, GError **error); G_END_DECLS diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index e8d008e3..e4fb70c2 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -397,8 +397,7 @@ nm_setting_connection_add_permission (NMSettingConnection *setting, GSList *iter; g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), FALSE); - g_return_val_if_fail (ptype, FALSE); - g_return_val_if_fail (strlen (ptype) > 0, FALSE); + g_return_val_if_fail (ptype && ptype[0], FALSE); g_return_val_if_fail (detail == NULL, FALSE); /* Only "user" for now... */ @@ -470,9 +469,9 @@ nm_setting_connection_remove_permission_by_value (NMSettingConnection *setting, GSList *iter; g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), FALSE); - g_return_val_if_fail (ptype, FALSE); - g_return_val_if_fail (strlen (ptype) > 0, FALSE); + g_return_val_if_fail (ptype && ptype[0], FALSE); g_return_val_if_fail (detail == NULL, FALSE); + g_return_val_if_fail (pitem != NULL, FALSE); /* Only "user" for now... */ g_return_val_if_fail (strcmp (ptype, "user") == 0, FALSE); diff --git a/libnm-core/nm-setting-ethtool.c b/libnm-core/nm-setting-ethtool.c index 7bdbcb1a..827e3a62 100644 --- a/libnm-core/nm-setting-ethtool.c +++ b/libnm-core/nm-setting-ethtool.c @@ -24,7 +24,7 @@ #include "nm-setting-ethtool.h" #include "nm-setting-private.h" -#include "nm-ethtool-utils.h" +#include "nm-libnm-core-intern/nm-ethtool-utils.h" /*****************************************************************************/ diff --git a/libnm-core/nm-setting-infiniband.c b/libnm-core/nm-setting-infiniband.c index 9348fa2b..bde7eda9 100644 --- a/libnm-core/nm-setting-infiniband.c +++ b/libnm-core/nm-setting-infiniband.c @@ -170,7 +170,6 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) { NMSettingConnection *s_con = NULL; NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE (setting); - guint32 normerr_max_mtu = 0; if (priv->mac_address && !nm_utils_hwaddr_valid (priv->mac_address, INFINIBAND_ALEN)) { g_set_error_literal (error, @@ -181,13 +180,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) return FALSE; } - if (!g_strcmp0 (priv->transport_mode, "datagram")) { - if (priv->mtu > 2044) - normerr_max_mtu = 2044; - } else if (!g_strcmp0 (priv->transport_mode, "connected")) { - if (priv->mtu > 65520) - normerr_max_mtu = 65520; - } else { + if (!NM_IN_STRSET (priv->transport_mode, "datagram", + "connected")) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -271,12 +265,21 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */ - if (normerr_max_mtu > 0) { + if (priv->mtu > NM_INFINIBAND_MAX_MTU) { + /* Traditionally, MTU for "datagram" mode was limited to 2044 + * and for "connected" mode it was 65520. + * + * This is no longer the case, and both transport modes use the same + * maximum of 65520 (NM_INFINIBAND_MAX_MTU). + * + * Note that this is the MTU in the connection profile. Whether + * we will be able to configure large MTUs later (during activation) + * is unknown at this point. */ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("mtu for transport mode '%s' can be at most %d but it is %d"), - priv->transport_mode, normerr_max_mtu, priv->mtu); + _("mtu can be at most %u but it is %u"), + NM_INFINIBAND_MAX_MTU, priv->mtu); g_prefix_error (error, "%s.%s: ", NM_SETTING_INFINIBAND_SETTING_NAME, NM_SETTING_INFINIBAND_MTU); return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; } 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 +#include #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: diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h index 39cb36a4..76a94914 100644 --- a/libnm-core/nm-setting-ip-config.h +++ b/libnm-core/nm-setting-ip-config.h @@ -159,6 +159,155 @@ 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; + +NM_AVAILABLE_IN_1_18 +NMIPRoutingRule *nm_ip_routing_rule_from_string (const char *str, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable *extra_args, + GError **error); + +NM_AVAILABLE_IN_1_18 +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)) @@ -187,6 +336,9 @@ gboolean nm_ip_route_attribute_validate (const char *name, #define NM_SETTING_IP_CONFIG_DAD_TIMEOUT "dad-timeout" #define NM_SETTING_IP_CONFIG_DHCP_TIMEOUT "dhcp-timeout" +/* these are not real GObject properties. */ +#define NM_SETTING_IP_CONFIG_ROUTING_RULES "routing-rules" + #define NM_SETTING_DNS_OPTION_DEBUG "debug" #define NM_SETTING_DNS_OPTION_NDOTS "ndots" #define NM_SETTING_DNS_OPTION_TIMEOUT "timeout" @@ -289,6 +441,20 @@ gint64 nm_setting_ip_config_get_route_metric (NMSettingIPConfig NM_AVAILABLE_IN_1_10 guint32 nm_setting_ip_config_get_route_table (NMSettingIPConfig *setting); +NM_AVAILABLE_IN_1_18 +guint nm_setting_ip_config_get_num_routing_rules (NMSettingIPConfig *setting); +NM_AVAILABLE_IN_1_18 +NMIPRoutingRule *nm_setting_ip_config_get_routing_rule (NMSettingIPConfig *setting, + guint idx); +NM_AVAILABLE_IN_1_18 +void nm_setting_ip_config_add_routing_rule (NMSettingIPConfig *setting, + NMIPRoutingRule *routing_rule); +NM_AVAILABLE_IN_1_18 +void nm_setting_ip_config_remove_routing_rule (NMSettingIPConfig *setting, + guint idx); +NM_AVAILABLE_IN_1_18 +void nm_setting_ip_config_clear_routing_rules (NMSettingIPConfig *setting); + gboolean nm_setting_ip_config_get_ignore_auto_routes (NMSettingIPConfig *setting); gboolean nm_setting_ip_config_get_ignore_auto_dns (NMSettingIPConfig *setting); diff --git a/libnm-core/nm-setting-ip-tunnel.c b/libnm-core/nm-setting-ip-tunnel.c index 9fdb5481..42c44c1c 100644 --- a/libnm-core/nm-setting-ip-tunnel.c +++ b/libnm-core/nm-setting-ip-tunnel.c @@ -60,7 +60,7 @@ typedef struct { char *output_key; guint encapsulation_limit; guint flow_label; - guint mtu; + guint32 mtu; guint32 flags; } NMSettingIPTunnelPrivate; @@ -819,7 +819,7 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass) **/ obj_properties[PROP_MTU] = g_param_spec_uint (NM_SETTING_IP_TUNNEL_MTU, "", "", - 0, G_MAXUINT, 0, + 0, G_MAXUINT32, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | NM_SETTING_PARAM_FUZZY_IGNORE | diff --git a/libnm-core/nm-setting-macsec.c b/libnm-core/nm-setting-macsec.c index 040aa17d..19d1989e 100644 --- a/libnm-core/nm-setting-macsec.c +++ b/libnm-core/nm-setting-macsec.c @@ -24,7 +24,7 @@ #include -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-core-types-internal.h" diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h index 2af74ec4..b423eb5c 100644 --- a/libnm-core/nm-setting-private.h +++ b/libnm-core/nm-setting-private.h @@ -26,6 +26,7 @@ #endif #include "nm-setting.h" +#include "nm-setting-bridge.h" #include "nm-connection.h" #include "nm-core-enum-types.h" @@ -204,6 +205,9 @@ gboolean _nm_setting_should_compare_secret_property (NMSetting *setting, const char *secret_name, NMSettingCompareFlags flags); +NMBridgeVlan *_nm_bridge_vlan_dup (const NMBridgeVlan *vlan); +NMBridgeVlan *_nm_bridge_vlan_dup_and_seal (const NMBridgeVlan *vlan); + /*****************************************************************************/ #endif /* NM_SETTING_PRIVATE_H */ diff --git a/libnm-core/nm-setting-sriov.c b/libnm-core/nm-setting-sriov.c index 9c7306e0..b662ca2c 100644 --- a/libnm-core/nm-setting-sriov.c +++ b/libnm-core/nm-setting-sriov.c @@ -94,7 +94,7 @@ _vf_vlan_create_hash (void) } /** - * nm_srio_vf_new: + * nm_sriov_vf_new: * @index: the VF index * * Creates a new #NMSriovVF object. @@ -894,8 +894,10 @@ _nm_setting_sriov_sort_vfs (NMSettingSriov *setting) } } - if (need_sort) + if (need_sort) { g_ptr_array_sort (setting->vfs, vf_index_compare); + _notify (setting, PROP_VFS); + } return need_sort; } diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c index 7425ae73..dc2f4f64 100644 --- a/libnm-core/nm-setting-tc-config.c +++ b/libnm-core/nm-setting-tc-config.c @@ -41,6 +41,7 @@ struct NMTCQdisc { char *kind; guint32 handle; guint32 parent; + GHashTable *attributes; }; /** @@ -62,7 +63,15 @@ nm_tc_qdisc_new (const char *kind, { NMTCQdisc *qdisc; - if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) { + if (!kind || !*kind) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr (kind, ' ') || strchr (kind, '\t')) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -122,6 +131,8 @@ nm_tc_qdisc_unref (NMTCQdisc *qdisc) qdisc->refcount--; if (qdisc->refcount == 0) { g_free (qdisc->kind); + if (qdisc->attributes) + g_hash_table_unref (qdisc->attributes); g_slice_free (NMTCQdisc, qdisc); } } @@ -141,6 +152,11 @@ nm_tc_qdisc_unref (NMTCQdisc *qdisc) gboolean nm_tc_qdisc_equal (NMTCQdisc *qdisc, NMTCQdisc *other) { + GHashTableIter iter; + const char *key; + GVariant *value, *value2; + guint n; + g_return_val_if_fail (qdisc != NULL, FALSE); g_return_val_if_fail (qdisc->refcount > 0, FALSE); @@ -152,19 +168,53 @@ nm_tc_qdisc_equal (NMTCQdisc *qdisc, NMTCQdisc *other) || g_strcmp0 (qdisc->kind, other->kind) != 0) return FALSE; + n = qdisc->attributes ? g_hash_table_size (qdisc->attributes) : 0; + if (n != (other->attributes ? g_hash_table_size (other->attributes) : 0)) + return FALSE; + if (n) { + g_hash_table_iter_init (&iter, qdisc->attributes); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup (other->attributes, key); + if (!value2) + return FALSE; + if (!g_variant_equal (value, value2)) + return FALSE; + } + } + return TRUE; } static guint _nm_tc_qdisc_hash (NMTCQdisc *qdisc) { + gs_free const char **names = NULL; + GVariant *variant; NMHashState h; + guint length; + guint i; + + names = nm_utils_strdict_get_keys (qdisc->attributes, TRUE, &length); nm_hash_init (&h, 43869703); nm_hash_update_vals (&h, qdisc->handle, - qdisc->parent); + qdisc->parent, + length); nm_hash_update_str0 (&h, qdisc->kind); + for (i = 0; i < length; i++) { + const GVariantType *vtype; + + variant = g_hash_table_lookup (qdisc->attributes, names[i]); + + vtype = g_variant_get_type (variant); + + nm_hash_update_str (&h, names[i]); + nm_hash_update_str (&h, (const char *) vtype); + if (g_variant_type_is_basic (vtype)) + nm_hash_update_val (&h, g_variant_hash (variant)); + } + return nm_hash_complete (&h); } @@ -189,6 +239,16 @@ nm_tc_qdisc_dup (NMTCQdisc *qdisc) copy = nm_tc_qdisc_new (qdisc->kind, qdisc->parent, NULL); nm_tc_qdisc_set_handle (copy, qdisc->handle); + if (qdisc->attributes) { + GHashTableIter iter; + const char *key; + GVariant *value; + + g_hash_table_iter_init (&iter, qdisc->attributes); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) + nm_tc_qdisc_set_attribute (copy, key, value); + } + return copy; } @@ -261,6 +321,85 @@ nm_tc_qdisc_get_parent (NMTCQdisc *qdisc) return qdisc->parent; } +/** + * nm_tc_qdisc_get_attribute_names: + * @qdisc: the #NMTCQdisc + * + * Gets an array of attribute names defined on @qdisc. + * + * Returns: (transfer container): a %NULL-terminated array of attribute names + * or %NULL if no attributes are set. + * + * Since: 1.18 + **/ +const char ** +nm_tc_qdisc_get_attribute_names (NMTCQdisc *qdisc) +{ + g_return_val_if_fail (qdisc, NULL); + + return nm_utils_strdict_get_keys (qdisc->attributes, TRUE, NULL); +} + +GHashTable * +_nm_tc_qdisc_get_attributes (NMTCQdisc *qdisc) +{ + nm_assert (qdisc); + + return qdisc->attributes; +} + +/** + * nm_tc_qdisc_get_attribute: + * @qdisc: the #NMTCQdisc + * @name: the name of an qdisc attribute + * + * Gets the value of the attribute with name @name on @qdisc + * + * Returns: (transfer none): the value of the attribute with name @name on + * @qdisc, or %NULL if @qdisc has no such attribute. + * + * Since: 1.18 + **/ +GVariant * +nm_tc_qdisc_get_attribute (NMTCQdisc *qdisc, const char *name) +{ + g_return_val_if_fail (qdisc != NULL, NULL); + g_return_val_if_fail (name != NULL && *name != '\0', NULL); + + if (qdisc->attributes) + return g_hash_table_lookup (qdisc->attributes, name); + else + return NULL; +} + +/** + * nm_tc_qdisc_set_attribute: + * @qdisc: the #NMTCQdisc + * @name: the name of an qdisc attribute + * @value: (transfer none) (allow-none): the value + * + * Sets or clears the named attribute on @qdisc to the given value. + * + * Since: 1.18 + **/ +void +nm_tc_qdisc_set_attribute (NMTCQdisc *qdisc, const char *name, GVariant *value) +{ + g_return_if_fail (qdisc != NULL); + g_return_if_fail (name != NULL && *name != '\0'); + g_return_if_fail (strcmp (name, "kind") != 0); + + if (!qdisc->attributes) { + qdisc->attributes = g_hash_table_new_full (nm_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_variant_unref); + } + + if (value) + g_hash_table_insert (qdisc->attributes, g_strdup (name), g_variant_ref_sink (value)); + else + g_hash_table_remove (qdisc->attributes, name); +} + /*****************************************************************************/ G_DEFINE_BOXED_TYPE (NMTCAction, nm_tc_action, nm_tc_action_dup, nm_tc_action_unref) @@ -290,7 +429,15 @@ nm_tc_action_new (const char *kind, { NMTCAction *action; - if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) { + if (!kind || !*kind) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr (kind, ' ') || strchr (kind, '\t')) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -464,6 +611,14 @@ nm_tc_action_get_attribute_names (NMTCAction *action) return nm_utils_strv_make_deep_copied_nonnull (names); } +GHashTable * +_nm_tc_action_get_attributes (NMTCAction *action) +{ + nm_assert (action); + + return action->attributes; +} + /** * nm_tc_action_get_attribute: * @action: the #NMTCAction @@ -544,7 +699,15 @@ nm_tc_tfilter_new (const char *kind, { NMTCTfilter *tfilter; - if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) { + if (!kind || !*kind) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr (kind, ' ') || strchr (kind, '\t')) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -1222,7 +1385,10 @@ _qdiscs_to_variant (GPtrArray *qdiscs) if (qdiscs) { for (i = 0; i < qdiscs->len; i++) { NMTCQdisc *qdisc = qdiscs->pdata[i]; + guint length; + gs_free const char **attrs = nm_utils_strdict_get_keys (qdisc->attributes, TRUE, &length); GVariantBuilder qdisc_builder; + guint y; g_variant_builder_init (&qdisc_builder, G_VARIANT_TYPE_VARDICT); @@ -1235,6 +1401,11 @@ _qdiscs_to_variant (GPtrArray *qdiscs) g_variant_builder_add (&qdisc_builder, "{sv}", "parent", g_variant_new_uint32 (nm_tc_qdisc_get_parent (qdisc))); + for (y = 0; y < length; y++) { + g_variant_builder_add (&qdisc_builder, "{sv}", attrs[y], + g_hash_table_lookup (qdisc->attributes, attrs[y])); + } + g_variant_builder_add (&builder, "a{sv}", &qdisc_builder); } } @@ -1267,9 +1438,11 @@ _qdiscs_from_variant (GVariant *value) while (g_variant_iter_next (&iter, "@a{sv}", &qdisc_var)) { const char *kind; - guint32 handle; guint32 parent; NMTCQdisc *qdisc; + GVariantIter qdisc_iter; + const char *key; + GVariant *attr_value; if ( !g_variant_lookup (qdisc_var, "kind", "&s", &kind) || !g_variant_lookup (qdisc_var, "parent", "u", &parent)) { @@ -1284,8 +1457,18 @@ _qdiscs_from_variant (GVariant *value) goto next; } - if (g_variant_lookup (qdisc_var, "handle", "u", &handle)) - nm_tc_qdisc_set_handle (qdisc, handle); + g_variant_iter_init (&qdisc_iter, qdisc_var); + while (g_variant_iter_next (&qdisc_iter, "{&sv}", &key, &attr_value)) { + if ( strcmp (key, "kind") == 0 + || strcmp (key, "parent") == 0) { + /* Already processed above */ + } else if (strcmp (key, "handle") == 0) { + nm_tc_qdisc_set_handle (qdisc, g_variant_get_uint32 (attr_value)); + } else { + nm_tc_qdisc_set_attribute (qdisc, key, attr_value); + } + g_variant_unref (attr_value); + } g_ptr_array_add (qdiscs, qdisc); next: diff --git a/libnm-core/nm-setting-tc-config.h b/libnm-core/nm-setting-tc-config.h index 6296b1ab..ab99e3cc 100644 --- a/libnm-core/nm-setting-tc-config.h +++ b/libnm-core/nm-setting-tc-config.h @@ -59,6 +59,16 @@ void nm_tc_qdisc_set_handle (NMTCQdisc *qdisc, NM_AVAILABLE_IN_1_12 guint32 nm_tc_qdisc_get_parent (NMTCQdisc *qdisc); +NM_AVAILABLE_IN_1_18 +const char **nm_tc_qdisc_get_attribute_names (NMTCQdisc *qdisc); +NM_AVAILABLE_IN_1_18 +GVariant *nm_tc_qdisc_get_attribute (NMTCQdisc *qdisc, + const char *name); +NM_AVAILABLE_IN_1_18 +void nm_tc_qdisc_set_attribute (NMTCQdisc *qdisc, + const char *name, + GVariant *value); + typedef struct NMTCAction NMTCAction; NM_AVAILABLE_IN_1_12 diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c index 1db8f22a..b2c57ffa 100644 --- a/libnm-core/nm-setting-team-port.c +++ b/libnm-core/nm-setting-team-port.c @@ -396,31 +396,18 @@ compare_property (const NMSettInfoSetting *sett_info, { NMSettingTeamPortPrivate *a_priv; NMSettingTeamPortPrivate *b_priv; - guint i, j; if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_PORT_LINK_WATCHERS)) { if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) return NM_TERNARY_DEFAULT; - - if (other) { - a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting); - b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (other); - - if (a_priv->link_watchers->len != b_priv->link_watchers->len) - return FALSE; - for (i = 0; i < a_priv->link_watchers->len; i++) { - for (j = 0; j < b_priv->link_watchers->len; j++) { - if (nm_team_link_watcher_equal (a_priv->link_watchers->pdata[i], - b_priv->link_watchers->pdata[j])) { - break; - } - } - if (j == b_priv->link_watchers->len) - return FALSE; - } - } - return TRUE; + if (!other) + return TRUE; + a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting); + b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (other); + return _nm_team_link_watchers_equal (a_priv->link_watchers, + b_priv->link_watchers, + TRUE); } if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_PORT_CONFIG)) { diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c index 42ee3eec..9fd070c1 100644 --- a/libnm-core/nm-setting-team.c +++ b/libnm-core/nm-setting-team.c @@ -396,6 +396,34 @@ nm_team_link_watcher_equal (NMTeamLinkWatcher *watcher, NMTeamLinkWatcher *other return TRUE; } +gboolean +_nm_team_link_watchers_equal (GPtrArray *a, GPtrArray *b, gboolean ignore_order) +{ + guint i, j; + + if (a->len != b->len) + return FALSE; + if (ignore_order) { + /* FIXME: comparing this way is O(n^2). Don't do that, instead + * add nm_team_link_watcher_cmp(), sort both lists, and + * compare step by step. */ + for (i = 0; i < a->len; i++) { + for (j = 0; j < b->len; j++) { + if (nm_team_link_watcher_equal (a->pdata[i], b->pdata[j])) + break; + } + if (j == b->len) + return FALSE; + } + } else { + for (i = 0; i < a->len; i++) { + if (!nm_team_link_watcher_equal (a->pdata[i], b->pdata[i])) + return FALSE; + } + } + return TRUE; +} + /** * nm_team_link_watcher_dup: * @watcher: the #NMTeamLinkWatcher @@ -929,13 +957,14 @@ nm_setting_team_remove_runner_tx_hash_by_value (NMSettingTeam *setting, g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE); g_return_val_if_fail (txhash != NULL, FALSE); - g_return_val_if_fail (txhash[0] != '\0', FALSE); - for (i = 0; i < priv->runner_tx_hash->len; i++) { - if (nm_streq (txhash, priv->runner_tx_hash->pdata[i])) { - g_ptr_array_remove_index (priv->runner_tx_hash, i); - _notify (setting, PROP_RUNNER_TX_HASH); - return TRUE; + if (priv->runner_tx_hash) { + for (i = 0; i < priv->runner_tx_hash->len; i++) { + if (nm_streq (txhash, priv->runner_tx_hash->pdata[i])) { + g_ptr_array_remove_index (priv->runner_tx_hash, i); + _notify (setting, PROP_RUNNER_TX_HASH); + return TRUE; + } } } return FALSE; @@ -974,6 +1003,7 @@ nm_setting_team_get_runner_tx_hash (NMSettingTeam *setting, guint idx) NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting); g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL); + g_return_val_if_fail (priv->runner_tx_hash, NULL); g_return_val_if_fail (idx < priv->runner_tx_hash->len, NULL); return priv->runner_tx_hash->pdata[idx]; @@ -994,6 +1024,7 @@ nm_setting_team_remove_runner_tx_hash (NMSettingTeam *setting, guint idx) NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting); g_return_if_fail (NM_IS_SETTING_TEAM (setting)); + g_return_if_fail (priv->runner_tx_hash); g_return_if_fail (idx < priv->runner_tx_hash->len); g_ptr_array_remove_index (priv->runner_tx_hash, idx); @@ -1239,9 +1270,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) if (!name) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, - _("missing link watcher name")); + _("missing link watcher name")); g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), - NM_SETTING_TEAM_LINK_WATCHERS); + NM_SETTING_TEAM_LINK_WATCHERS); return FALSE; } if (!NM_IN_STRSET (name, @@ -1249,9 +1280,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) NM_TEAM_LINK_WATCHER_ARP_PING, NM_TEAM_LINK_WATCHER_NSNA_PING)) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, - _("unknown link watcher \"%s\""), name); + _("unknown link watcher \"%s\""), name); g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), - NM_SETTING_TEAM_LINK_WATCHERS); + NM_SETTING_TEAM_LINK_WATCHERS); return FALSE; } @@ -1260,17 +1291,17 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) NM_TEAM_LINK_WATCHER_NSNA_PING) && !nm_team_link_watcher_get_target_host (link_watcher)) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, - _("missing target host")); + _("missing target host")); g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), - NM_SETTING_TEAM_LINK_WATCHERS); + NM_SETTING_TEAM_LINK_WATCHERS); return FALSE; } if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING) && !nm_team_link_watcher_get_source_host (link_watcher)) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, - _("missing source address")); + _("missing source address")); g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), - NM_SETTING_TEAM_LINK_WATCHERS); + NM_SETTING_TEAM_LINK_WATCHERS); return FALSE; } } @@ -1288,31 +1319,17 @@ compare_property (const NMSettInfoSetting *sett_info, NMSettingCompareFlags flags) { NMSettingTeamPrivate *a_priv, *b_priv; - guint i, j; if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_LINK_WATCHERS)) { - if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) return NM_TERNARY_DEFAULT; - - if (other) { - a_priv = NM_SETTING_TEAM_GET_PRIVATE (setting); - b_priv = NM_SETTING_TEAM_GET_PRIVATE (other); - - if (a_priv->link_watchers->len != b_priv->link_watchers->len) - return FALSE; - for (i = 0; i < a_priv->link_watchers->len; i++) { - for (j = 0; j < b_priv->link_watchers->len; j++) { - if (nm_team_link_watcher_equal (a_priv->link_watchers->pdata[i], - b_priv->link_watchers->pdata[j])) { - break; - } - } - if (j == b_priv->link_watchers->len) - return FALSE; - } - } - return TRUE; + if (!other) + return TRUE; + a_priv = NM_SETTING_TEAM_GET_PRIVATE (setting); + b_priv = NM_SETTING_TEAM_GET_PRIVATE (other); + return _nm_team_link_watchers_equal (a_priv->link_watchers, + b_priv->link_watchers, + TRUE); } if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_CONFIG)) { @@ -1350,7 +1367,7 @@ _align_team_properties (NMSettingTeam *setting) { NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting); char **strv; - int i; + gsize i; priv->notify_peers_count = JSON_TO_VAL (int, PROP_NOTIFY_PEERS_COUNT); priv->notify_peers_interval = JSON_TO_VAL (int, PROP_NOTIFY_PEERS_INTERVAL); @@ -1372,16 +1389,24 @@ _align_team_properties (NMSettingTeam *setting) priv->runner_tx_balancer = JSON_TO_VAL (string, PROP_RUNNER_TX_BALANCER); priv->runner_agg_select_policy = JSON_TO_VAL (string, PROP_RUNNER_AGG_SELECT_POLICY); - if (priv->runner_tx_hash) { - g_ptr_array_unref (priv->runner_tx_hash); - priv->runner_tx_hash = NULL; - } strv = JSON_TO_VAL (strv, PROP_RUNNER_TX_HASH); - if (strv) { - for (i = 0; strv[i]; i++) - nm_setting_team_add_runner_tx_hash (setting, strv[i]); - g_strfreev (strv); + if (_nm_utils_strv_cmp_n (( priv->runner_tx_hash + ? (const char *const*) priv->runner_tx_hash->pdata + : NULL), + ( priv->runner_tx_hash + ? (gssize) priv->runner_tx_hash->len + : (gssize) -1), + NM_CAST_STRV_CC (strv), + -1) != 0) { + nm_clear_pointer (&priv->runner_tx_hash, g_ptr_array_unref); + if (strv) { + priv->runner_tx_hash = g_ptr_array_new_full (NM_PTRARRAY_LEN (strv), g_free); + for (i = 0; strv[i]; i++) + g_ptr_array_add (priv->runner_tx_hash, strv[i]); + nm_clear_g_free (&strv); + } } + nm_clear_pointer (&strv, g_strfreev); g_ptr_array_unref (priv->link_watchers); priv->link_watchers = JSON_TO_VAL (ptr_array, PROP_LINK_WATCHERS); @@ -1419,8 +1444,10 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, nm_setting_team_get_runner_hwaddr_policy (setting)); break; case PROP_RUNNER_TX_HASH: - g_value_take_boxed (value, priv->runner_tx_hash ? - _nm_utils_ptrarray_to_strv (priv->runner_tx_hash): NULL); + g_value_take_boxed (value, + priv->runner_tx_hash + ? _nm_utils_ptrarray_to_strv (priv->runner_tx_hash) + : NULL); break; case PROP_RUNNER_TX_BALANCER: g_value_set_string (value, nm_setting_team_get_runner_tx_balancer (setting)); @@ -1772,9 +1799,9 @@ nm_setting_team_class_init (NMSettingTeamClass *klass) obj_properties[PROP_RUNNER_TX_HASH] = g_param_spec_boxed (NM_SETTING_TEAM_RUNNER_TX_HASH, "", "", G_TYPE_STRV, - G_PARAM_READWRITE | + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | - G_PARAM_STATIC_STRINGS); + G_PARAM_STATIC_STRINGS); /** * NMSettingTeam:runner-tx-balancer: diff --git a/libnm-core/nm-setting-vlan.c b/libnm-core/nm-setting-vlan.c index 5b8a49d4..8a220ad8 100644 --- a/libnm-core/nm-setting-vlan.c +++ b/libnm-core/nm-setting-vlan.c @@ -25,6 +25,7 @@ #include +#include "nm-libnm-core-intern/nm-libnm-core-utils.h" #include "nm-utils.h" #include "nm-core-types-internal.h" #include "nm-setting-connection.h" @@ -106,48 +107,33 @@ nm_setting_vlan_get_flags (NMSettingVlan *setting) return NM_SETTING_VLAN_GET_PRIVATE (setting)->flags; } -static guint32 -get_max_prio (NMVlanPriorityMap map, gboolean from) +static NMVlanQosMapping * +priority_map_new (guint32 from, guint32 to) { - if (map == NM_VLAN_INGRESS_MAP) - return from ? MAX_8021P_PRIO : MAX_SKB_PRIO; - else if (map == NM_VLAN_EGRESS_MAP) - return from ? MAX_SKB_PRIO : MAX_8021P_PRIO; - g_assert_not_reached (); + NMVlanQosMapping *mapping; + + mapping = g_new (NMVlanQosMapping, 1); + *mapping = (NMVlanQosMapping) { + .from = from, + .to = to, + }; + return mapping; } static NMVlanQosMapping * priority_map_new_from_str (NMVlanPriorityMap map, const char *str) { - NMVlanQosMapping *p = NULL; - char **t = NULL; - guint32 len; - guint64 from, to; + guint32 from, to; - g_return_val_if_fail (str && str[0], NULL); - - t = g_strsplit (str, ":", 0); - len = g_strv_length (t); - if (len == 2) { - from = g_ascii_strtoull (t[0], NULL, 10); - to = g_ascii_strtoull (t[1], NULL, 10); - - if ((from <= get_max_prio (map, TRUE)) && (to <= get_max_prio (map, FALSE))) { - G_STATIC_ASSERT (sizeof (*p) == sizeof (p->from) + sizeof (p->to)); - p = g_malloc (sizeof (NMVlanQosMapping)); - p->from = from; - p->to = to; - } - } - - g_strfreev (t); - return p; + if (!nm_utils_vlan_priority_map_parse_str (map, str, FALSE, &from, &to, NULL)) + return NULL; + return priority_map_new (from, to); } static void priority_map_free (NMVlanQosMapping *map) { - g_return_if_fail (map != NULL); + nm_assert (map); g_free (map); } @@ -158,7 +144,7 @@ get_map (NMSettingVlan *self, NMVlanPriorityMap map) return NM_SETTING_VLAN_GET_PRIVATE (self)->ingress_priority_map; else if (map == NM_VLAN_EGRESS_MAP) return NM_SETTING_VLAN_GET_PRIVATE (self)->egress_priority_map; - g_assert_not_reached (); + nm_assert_not_reached (); return NULL; } @@ -207,7 +193,7 @@ set_map (NMSettingVlan *self, NMVlanPriorityMap map, GSList *list) NM_SETTING_VLAN_GET_PRIVATE (self)->egress_priority_map = list; _notify (self, PROP_EGRESS_PRIORITY_MAP); } else - g_assert_not_reached (); + nm_assert_not_reached (); } static gboolean @@ -489,6 +475,36 @@ nm_setting_vlan_remove_priority (NMSettingVlan *setting, set_map (setting, map, g_slist_delete_link (list, item)); } +static gboolean +priority_map_remove_by_value (NMSettingVlan *setting, + NMVlanPriorityMap map, + guint32 from, + guint32 to, + gboolean wildcard_to) +{ + GSList *list = NULL, *iter = NULL; + NMVlanQosMapping *item; + + nm_assert (NM_IS_SETTING_VLAN (setting)); + nm_assert (NM_IN_SET (map, NM_VLAN_INGRESS_MAP, NM_VLAN_EGRESS_MAP)); + + list = get_map (setting, map); + for (iter = list; iter; iter = g_slist_next (iter)) { + item = iter->data; + + if (item->from != from) + continue; + if ( !wildcard_to + && item->to != to) + continue; + + priority_map_free ((NMVlanQosMapping *) (iter->data)); + set_map (setting, map, g_slist_delete_link (list, iter)); + return TRUE; + } + return FALSE; +} + /** * nm_setting_vlan_remove_priority_by_value: * @setting: the #NMSettingVlan @@ -508,22 +524,10 @@ nm_setting_vlan_remove_priority_by_value (NMSettingVlan *setting, guint32 from, guint32 to) { - GSList *list = NULL, *iter = NULL; - NMVlanQosMapping *item; - g_return_val_if_fail (NM_IS_SETTING_VLAN (setting), FALSE); g_return_val_if_fail (map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); - list = get_map (setting, map); - for (iter = list; iter; iter = g_slist_next (iter)) { - item = iter->data; - if (item->from == from && item->to == to) { - priority_map_free ((NMVlanQosMapping *) (iter->data)); - set_map (setting, map, g_slist_delete_link (list, iter)); - return TRUE; - } - } - return FALSE; + return priority_map_remove_by_value (setting, map, from, to, FALSE); } /** @@ -543,19 +547,15 @@ nm_setting_vlan_remove_priority_str_by_value (NMSettingVlan *setting, NMVlanPriorityMap map, const char *str) { - NMVlanQosMapping *item; - gboolean found; + gboolean is_wildcard_to; + guint32 from, to; g_return_val_if_fail (NM_IS_SETTING_VLAN (setting), FALSE); g_return_val_if_fail (map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); - item = priority_map_new_from_str (map, str); - if (!item) + if (!nm_utils_vlan_priority_map_parse_str (map, str, TRUE, &from, &to, &is_wildcard_to)) return FALSE; - - found = nm_setting_vlan_remove_priority_by_value (setting, map, item->from, item->to); - g_free (item); - return found; + return priority_map_remove_by_value (setting, map, from, to, is_wildcard_to); } /** @@ -703,18 +703,16 @@ static GSList * priority_strv_to_maplist (NMVlanPriorityMap map, char **strv) { GSList *list = NULL; - int i; + gsize i; for (i = 0; strv && strv[i]; i++) { - NMVlanQosMapping *item; + guint32 from, to; - item = priority_map_new_from_str (map, strv[i]); - if (item) { - if (!check_replace_duplicate_priority (list, item->from, item->to)) - list = g_slist_prepend (list, item); - else - g_free (item); - } + if (!nm_utils_vlan_priority_map_parse_str (map, strv[i], FALSE, &from, &to, NULL)) + continue; + if (check_replace_duplicate_priority (list, from, to)) + continue; + list = g_slist_prepend (list, priority_map_new (from, to)); } return g_slist_sort (list, prio_map_compare); } diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c index 20a0ae14..606b9d7f 100644 --- a/libnm-core/nm-setting-vpn.c +++ b/libnm-core/nm-setting-vpn.c @@ -25,7 +25,7 @@ #include -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-utils-private.h" #include "nm-setting-private.h" @@ -173,10 +173,8 @@ nm_setting_vpn_add_data_item (NMSettingVpn *setting, const char *item) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); - g_return_if_fail (key != NULL); - g_return_if_fail (strlen (key) > 0); - g_return_if_fail (item != NULL); - g_return_if_fail (strlen (item) > 0); + g_return_if_fail (key && key[0]); + g_return_if_fail (item && item[0]); g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data, g_strdup (key), g_strdup (item)); @@ -242,6 +240,7 @@ nm_setting_vpn_remove_data_item (NMSettingVpn *setting, const char *key) gboolean found; g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE); + g_return_val_if_fail (key, FALSE); found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key); if (found) @@ -350,10 +349,8 @@ nm_setting_vpn_add_secret (NMSettingVpn *setting, const char *secret) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); - g_return_if_fail (key != NULL); - g_return_if_fail (strlen (key) > 0); - g_return_if_fail (secret != NULL); - g_return_if_fail (strlen (secret) > 0); + g_return_if_fail (key && key[0]); + g_return_if_fail (secret && secret[0]); g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, g_strdup (key), g_strdup (secret)); @@ -419,6 +416,7 @@ nm_setting_vpn_remove_secret (NMSettingVpn *setting, const char *key) gboolean found; g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE); + g_return_val_if_fail (key, FALSE); found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key); if (found) diff --git a/libnm-core/nm-setting-wifi-p2p.c b/libnm-core/nm-setting-wifi-p2p.c index da978720..490c2ef9 100644 --- a/libnm-core/nm-setting-wifi-p2p.c +++ b/libnm-core/nm-setting-wifi-p2p.c @@ -24,7 +24,7 @@ #include #include "nm-utils.h" -#include "nm-common-macros.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-utils-private.h" #include "nm-setting-private.h" diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c index ef23294f..182e7193 100644 --- a/libnm-core/nm-setting-wired.c +++ b/libnm-core/nm-setting-wired.c @@ -27,7 +27,7 @@ #include #include "nm-utils.h" -#include "nm-common-macros.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-utils-private.h" #include "nm-setting-private.h" @@ -483,8 +483,7 @@ nm_setting_wired_get_s390_option_by_key (NMSettingWired *setting, const char *key) { g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), NULL); - g_return_val_if_fail (key != NULL, NULL); - g_return_val_if_fail (strlen (key), NULL); + g_return_val_if_fail (key && key[0], NULL); return g_hash_table_lookup (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options, key); } @@ -508,17 +507,11 @@ nm_setting_wired_add_s390_option (NMSettingWired *setting, const char *key, const char *value) { - size_t value_len; - g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE); - g_return_val_if_fail (key != NULL, FALSE); - g_return_val_if_fail (strlen (key), FALSE); + g_return_val_if_fail (key && key[0], FALSE); g_return_val_if_fail (g_strv_contains (valid_s390_opts, key), FALSE); g_return_val_if_fail (value != NULL, FALSE); - value_len = strlen (value); - g_return_val_if_fail (value_len > 0 && value_len < 200, FALSE); - g_hash_table_insert (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options, g_strdup (key), g_strdup (value)); @@ -544,8 +537,7 @@ nm_setting_wired_remove_s390_option (NMSettingWired *setting, gboolean found; g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE); - g_return_val_if_fail (key != NULL, FALSE); - g_return_val_if_fail (strlen (key), FALSE); + g_return_val_if_fail (key && key[0], FALSE); found = g_hash_table_remove (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options, key); if (found) @@ -683,7 +675,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) g_hash_table_iter_init (&iter, priv->s390_options); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) { if ( !g_strv_contains (valid_s390_opts, key) - || !strlen (value) + || value[0] == '\0' || (strlen (value) > 200)) { g_set_error (error, NM_CONNECTION_ERROR, diff --git a/libnm-core/nm-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c index 8c5b25a5..861b2abd 100644 --- a/libnm-core/nm-setting-wireguard.c +++ b/libnm-core/nm-setting-wireguard.c @@ -24,7 +24,7 @@ #include "nm-setting-private.h" #include "nm-utils-private.h" #include "nm-connection-private.h" -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" /*****************************************************************************/ diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c index b4cb1056..d8056c6c 100644 --- a/libnm-core/nm-setting-wireless.c +++ b/libnm-core/nm-setting-wireless.c @@ -27,7 +27,7 @@ #include #include "nm-utils.h" -#include "nm-common-macros.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-utils-private.h" #include "nm-setting-private.h" diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h index 46af26ed..a1a1369a 100644 --- a/libnm-core/nm-utils-private.h +++ b/libnm-core/nm-utils-private.h @@ -103,8 +103,31 @@ void _nm_utils_format_variant_attributes_full (GString *str, char key_value_separator); gboolean _nm_sriov_vf_parse_vlans (NMSriovVF *vf, const char *str, GError **error); +GVariant * _nm_utils_bridge_vlans_to_dbus (NMSetting *setting, const char *property); +gboolean _nm_utils_bridge_vlans_from_dbus (NMSetting *setting, + GVariant *connection_dict, + const char *property, + GVariant *value, + NMSettingParseFlags parse_flags, + GError **error); +gboolean _nm_utils_bridge_vlan_verify_list (GPtrArray *vlans, + gboolean check_normalizable, + GError **error, + const char *setting, + const char *property); + /* JSON to GValue conversion macros */ +static inline void +_nm_auto_unset_and_free_gvalue (GValue **ptr) +{ + if (*ptr) { + g_value_unset (*ptr); + g_free (*ptr); + } +} +#define nm_auto_unset_and_free_gvalue nm_auto(_nm_auto_unset_and_free_gvalue) + typedef struct { const char *key1; const char *key2; @@ -121,16 +144,13 @@ _nm_utils_json_extract_int (char *conf, _NMUtilsTeamPropertyKeys key, gboolean is_port) { - gs_free GValue *t_value = NULL; - int ret; + nm_auto_unset_and_free_gvalue GValue *t_value = NULL; t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port); - if (!t_value) + if ( !t_value + || !G_VALUE_HOLDS_INT (t_value)) return key.default_int; - - ret = g_value_get_int (t_value); - g_value_unset (t_value); - return ret; + return g_value_get_int (t_value); } static inline gboolean @@ -138,16 +158,13 @@ _nm_utils_json_extract_boolean (char *conf, _NMUtilsTeamPropertyKeys key, gboolean is_port) { - gs_free GValue *t_value = NULL; - gboolean ret; + nm_auto_unset_and_free_gvalue GValue *t_value = NULL; t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port); - if (!t_value) + if ( !t_value + || !G_VALUE_HOLDS_BOOLEAN (t_value)) return key.default_bool; - - ret = g_value_get_boolean (t_value); - g_value_unset (t_value); - return ret; + return g_value_get_boolean (t_value); } static inline char * @@ -155,16 +172,13 @@ _nm_utils_json_extract_string (char *conf, _NMUtilsTeamPropertyKeys key, gboolean is_port) { - gs_free GValue *t_value = NULL; - char *ret; + nm_auto_unset_and_free_gvalue GValue *t_value = NULL; t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port); - if (!t_value) + if ( !t_value + || !G_VALUE_HOLDS_STRING (t_value)) return g_strdup (key.default_str); - - ret = g_value_dup_string (t_value); - g_value_unset (t_value); - return ret; + return g_value_dup_string (t_value); } static inline char ** @@ -172,39 +186,37 @@ _nm_utils_json_extract_strv (char *conf, _NMUtilsTeamPropertyKeys key, gboolean is_port) { - gs_free GValue *t_value = NULL; - char **ret; + nm_auto_unset_and_free_gvalue GValue *t_value = NULL; t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port); - if (!t_value) + if ( !t_value + || !G_TYPE_CHECK_VALUE_TYPE (t_value, G_TYPE_STRV)) return NULL; - - ret = g_strdupv (g_value_get_boxed (t_value)); - g_value_unset (t_value); - return ret; + return g_strdupv (g_value_get_boxed (t_value)) + ?: g_new0 (char *, 1); } static inline GPtrArray * _nm_utils_json_extract_ptr_array (char *conf, - _NMUtilsTeamPropertyKeys key, - gboolean is_port) + _NMUtilsTeamPropertyKeys key, + gboolean is_port) { - gs_free GValue *t_value = NULL; + nm_auto_unset_and_free_gvalue GValue *t_value = NULL; GPtrArray *data, *ret; guint i; ret = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref); + t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port); - if (!t_value) + if ( !t_value + || !G_TYPE_CHECK_VALUE_TYPE (t_value, G_TYPE_PTR_ARRAY)) return ret; data = g_value_get_boxed (t_value); if (!data) return ret; - for (i = 0; i < data->len; i++) g_ptr_array_add (ret, nm_team_link_watcher_dup (data->pdata[i])); - g_value_unset (t_value); return ret; } diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index d276cfe6..04d5b1b5 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -37,15 +37,16 @@ #include "nm-json.h" #endif -#include "nm-utils/nm-enum-utils.h" -#include "nm-utils/nm-secret-utils.h" +#include "nm-glib-aux/nm-enum-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" #include "systemd/nm-sd-utils-shared.h" -#include "nm-common-macros.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-utils-private.h" #include "nm-setting-private.h" #include "nm-crypto.h" #include "nm-setting-bond.h" #include "nm-setting-bridge.h" +#include "nm-setting-bridge-port.h" #include "nm-setting-infiniband.h" #include "nm-setting-ip6-config.h" #include "nm-setting-team.h" @@ -1034,30 +1035,6 @@ _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray) return strv; } -/** - * _nm_utils_strv_equal: - * @strv1: a string array - * @strv2: a string array - * - * Compare NULL-terminated string arrays for equality. - * - * Returns: %TRUE if the arrays are equal, %FALSE otherwise. - **/ -gboolean -_nm_utils_strv_equal (char **strv1, char **strv2) -{ - if (strv1 == strv2) - return TRUE; - - if (!strv1 || !strv2) - return FALSE; - - for ( ; *strv1 && *strv2 && !strcmp (*strv1, *strv2); strv1++, strv2++) - ; - - return !*strv1 && !*strv2; -} - static gboolean device_supports_ap_ciphers (guint32 dev_caps, guint32 ap_flags, @@ -2332,6 +2309,15 @@ static const NMVariantAttributeSpec * const tc_object_attribute_spec[] = { NULL, }; +typedef struct { + const char *kind; + const NMVariantAttributeSpec * const *attrs; +} NMQdiscAttributeSpec; + +static const NMQdiscAttributeSpec *const tc_qdisc_attribute_spec[] = { + NULL, +}; + /*****************************************************************************/ /** @@ -2348,6 +2334,7 @@ _nm_utils_string_append_tc_qdisc_rest (GString *string, NMTCQdisc *qdisc) { guint32 handle = nm_tc_qdisc_get_handle (qdisc); const char *kind = nm_tc_qdisc_get_kind (qdisc); + gs_free char *str = NULL; if (handle != TC_H_UNSPEC && strcmp (kind, "ingress") != 0) { g_string_append (string, "handle "); @@ -2356,6 +2343,13 @@ _nm_utils_string_append_tc_qdisc_rest (GString *string, NMTCQdisc *qdisc) } g_string_append (string, kind); + + str = nm_utils_format_variant_attributes (_nm_tc_qdisc_get_attributes (qdisc), + ' ', ' '); + if (str) { + g_string_append_c (string, ' '); + g_string_append (string, str); + } } /** @@ -2463,7 +2457,7 @@ _tc_read_common_opts (const char *str, * @str: the string representation of a qdisc * @error: location of the error * - * Parces the tc style string qdisc representation of the queueing + * Parses the tc style string qdisc representation of the queueing * discipline to a %NMTCQdisc instance. Supports a subset of the tc language. * * Returns: the %NMTCQdisc or %NULL @@ -2478,21 +2472,36 @@ nm_utils_tc_qdisc_from_str (const char *str, GError **error) gs_free char *kind = NULL; gs_free char *rest = NULL; NMTCQdisc *qdisc = NULL; - gs_unref_hashtable GHashTable *ht = NULL; + gs_unref_hashtable GHashTable *options = NULL; + GHashTableIter iter; + gpointer key, value; + guint i; nm_assert (str); nm_assert (!error || !*error); - ht = nm_utils_parse_variant_attributes (str, - ' ', ' ', FALSE, - tc_object_attribute_spec, - error); - if (!ht) - return NULL; - if (!_tc_read_common_opts (str, &handle, &parent, &kind, &rest, error)) return NULL; + for (i = 0; rest && tc_qdisc_attribute_spec[i]; i++) { + if (strcmp (tc_qdisc_attribute_spec[i]->kind, kind) == 0) { + options = nm_utils_parse_variant_attributes (rest, + ' ', ' ', FALSE, + tc_qdisc_attribute_spec[i]->attrs, + error); + if (!options) + return NULL; + break; + } + } + nm_clear_pointer (&rest, g_free); + + if (options) { + value = g_hash_table_lookup (options, ""); + if (value) + rest = g_variant_dup_string (value, NULL); + } + if (rest) { g_set_error (error, 1, 0, _("unsupported qdisc option: '%s'."), rest); return NULL; @@ -2504,8 +2513,15 @@ nm_utils_tc_qdisc_from_str (const char *str, GError **error) nm_tc_qdisc_set_handle (qdisc, handle); + if (options) { + g_hash_table_iter_init (&iter, options); + while (g_hash_table_iter_next (&iter, &key, &value)) + nm_tc_qdisc_set_attribute (qdisc, key, g_variant_ref_sink (value)); + } + return qdisc; } + /*****************************************************************************/ static const NMVariantAttributeSpec * const tc_action_simple_attribute_spec[] = { @@ -2522,24 +2538,14 @@ static const NMVariantAttributeSpec * const tc_action_attribute_spec[] = { static gboolean _string_append_tc_action (GString *string, NMTCAction *action, GError **error) { - gs_unref_hashtable GHashTable *ht = NULL; const char *kind = nm_tc_action_get_kind (action); - gs_strfreev char **attr_names = NULL; gs_free char *str = NULL; - int i; - - ht = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL); g_string_append (string, kind); - attr_names = nm_tc_action_get_attribute_names (action); - for (i = 0; attr_names[i]; i++) { - g_hash_table_insert (ht, attr_names[i], - nm_tc_action_get_attribute (action, attr_names[i])); - } - - if (i) { - str = nm_utils_format_variant_attributes (ht, ' ', ' '); + str = nm_utils_format_variant_attributes (_nm_tc_action_get_attributes (action), + ' ', ' '); + if (str) { g_string_append_c (string, ' '); g_string_append (string, str); } @@ -2578,7 +2584,7 @@ nm_utils_tc_action_to_str (NMTCAction *action, GError **error) * @str: the string representation of a action * @error: location of the error * - * Parces the tc style string action representation of the queueing + * Parses the tc style string action representation of the queueing * discipline to a %NMTCAction instance. Supports a subset of the tc language. * * Returns: the %NMTCAction or %NULL @@ -2731,7 +2737,7 @@ static const NMVariantAttributeSpec * const tc_tfilter_attribute_spec[] = { * @str: the string representation of a tfilter * @error: location of the error * - * Parces the tc style string tfilter representation of the queueing + * Parses the tc style string tfilter representation of the queueing * discipline to a %NMTCTfilter instance. Supports a subset of the tc language. * * Returns: the %NMTCTfilter or %NULL @@ -2872,7 +2878,7 @@ _nm_sriov_vf_parse_vlans (NMSriovVF *vf, const char *str, GError **error) gs_free const char **vlans = NULL; guint i; - vlans = nm_utils_strsplit_set (str, ";", FALSE); + vlans = nm_utils_strsplit_set (str, ";"); if (!vlans) { g_set_error_literal (error, NM_CONNECTION_ERROR, @@ -2986,8 +2992,7 @@ nm_utils_sriov_vf_from_str (const char *str, GError **error) detail = strchr (str, ' '); if (detail) { - index_free = g_strndup (str, detail - str); - str = index_free; + str = nm_strndup_a (200, str, detail - str, &index_free); detail++; } @@ -4565,6 +4570,29 @@ _nm_utils_generate_mac_address_mask_parse (const char *value, /*****************************************************************************/ +gboolean +nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name) +{ + gs_free gpointer bin_to_free = NULL; + gconstpointer bin; + gsize len; + + g_return_val_if_fail (utf8safe_name, FALSE); + + bin = nm_utils_buf_utf8safe_unescape (utf8safe_name, &len, &bin_to_free); + + if (bin_to_free) { + /* some unescaping happened... */ + + if (len != strlen (bin)) { + /* there are embedded NUL chars. Invalid. */ + return FALSE; + } + } + + return nm_utils_is_valid_iface_name (bin, NULL); +} + /** * nm_utils_is_valid_iface_name: * @name: Name of interface @@ -5430,11 +5458,11 @@ _json_team_add_defaults (json_t *json, if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) { _json_add_object (json, "notify_peers", "count", NULL, - json_integer (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT)); + json_integer (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT)); _json_add_object (json, "mcast_rejoin", "count", NULL, - json_integer (NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT)); + json_integer (NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT)); } else if ( nm_streq (runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE) - || nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) { + || nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) { json_element = json_array (); json_array_append_new (json_element, json_string ("eth")); json_array_append_new (json_element, json_string ("ipv4")); @@ -5538,6 +5566,7 @@ _json_team_normalize_defaults (json_t *json, gboolean reset) { json_t *json_element; const char *runner = NM_SETTING_TEAM_RUNNER_DEFAULT; + gs_free char *runner_free = NULL; int notify_peers_count = 0, notify_peers_interval = 0; int mcast_rejoin_count = 0, mcast_rejoin_interval = 0; int runner_tx_balancer_interval = -1; @@ -5546,7 +5575,8 @@ _json_team_normalize_defaults (json_t *json, gboolean reset) json_element = _json_find_object (json, "runner", "name", NULL); if (json_element) { - runner = json_string_value (json_element); + runner_free = g_strdup (json_string_value (json_element)); + runner = runner_free; _json_delete_object_on_string_match (json, "runner", "name", NULL, NM_SETTING_TEAM_RUNNER_DEFAULT); } @@ -5922,11 +5952,9 @@ _nm_utils_team_config_get (const char *conf, if (json_is_string (str_element)) g_ptr_array_add (data, g_strdup (json_string_value (str_element))); } - if (data->len) { - g_value_init (value, G_TYPE_STRV); - g_value_take_boxed (value, _nm_utils_ptrarray_to_strv (data)); - } - g_ptr_array_free (data, TRUE); + g_ptr_array_add (data, NULL); + g_value_init (value, G_TYPE_STRV); + g_value_take_boxed (value, g_ptr_array_free (data, FALSE)); } else { g_assert_not_reached (); g_free (value); @@ -5948,14 +5976,13 @@ _nm_utils_team_config_set (char **conf, const char *key3, const GValue *value) { - json_t *json, *json_element, *json_link, *json_value = NULL; + nm_auto_decref_json json_t *json = NULL; + nm_auto_decref_json json_t *json_value = NULL; + json_t *json_element; + json_t *json_link; json_error_t jerror; - gboolean updated = FALSE; - char **strv; - GPtrArray *array; const char *iter_key = key; - int i; - NMTeamLinkWatcher *watcher; + gs_free char *conf_new = NULL; g_return_val_if_fail (key, FALSE); @@ -5966,14 +5993,12 @@ _nm_utils_team_config_set (char **conf, if (!json) return FALSE; - /* no new value? delete element */ if (!value) { - updated = _json_del_object (json, key, key2, key3); + if (!_json_del_object (json, key, key2, key3)) + return FALSE; goto done; } - /* insert new value */ - updated = TRUE; if (G_VALUE_HOLDS_STRING (value)) json_value = json_string (g_value_get_string (value)); else if (G_VALUE_HOLDS_INT (value)) @@ -5982,53 +6007,56 @@ _nm_utils_team_config_set (char **conf, json_value = json_boolean (g_value_get_boolean (value)); else if (G_VALUE_HOLDS_BOXED (value)) { if (nm_streq (key, "link_watch")) { + gboolean has_array = FALSE; + GPtrArray *array; + guint i; + array = g_value_get_boxed (value); - if (!array || !array->len) { - updated = FALSE; - goto done; - } + if (!array || !array->len) + return FALSE; - /* - * json_value: will hold the final link_watcher json (array) object - * json_element: is the next link_watcher to append to json_value - * json_link: used to transit the json_value from a single link_watcher - * object to an array of link watcher objects - */ - json_value = NULL; for (i = 0; i < array->len; i++) { - watcher = array->pdata[i]; - json_element = _nm_utils_team_link_watcher_to_json (watcher); - if (!json_element) + json_t *el; + + el = _nm_utils_team_link_watcher_to_json (array->pdata[i]); + if (!el) continue; + /* if there is only one watcher, it is added as-is. If there + * are multiple watchers, they are added in an array. */ if (!json_value) { - json_value = json_element; + json_value = el; continue; } - if (!json_is_array (json_value)) { - json_link = json_value; - json_value = json_array (); - json_array_append_new (json_value, json_link); + if (!has_array) { + json_t *el_arr; + + has_array = TRUE; + el_arr = json_array(); + json_array_append_new (el_arr, json_value); + json_value = el_arr; } - json_array_append_new (json_value, json_element); + json_array_append_new (json_value, el); } } else if ( nm_streq (key, "runner") && nm_streq0 (key2, "tx_hash")) { + const char *const*strv; + gsize i; + strv = g_value_get_boxed (value); - if (!strv) { - updated = FALSE; - goto done; - } + if (!strv) + return FALSE; + json_value = json_array (); for (i = 0; strv[i]; i++) json_array_append_new (json_value, json_string (strv[i])); } else { - updated = FALSE; - goto done; + nm_assert_not_reached (); + return FALSE; } + } else { /* G_VALUE_HOLDS_? */ - g_assert_not_reached (); - updated = FALSE; - goto done; + nm_assert_not_reached (); + return FALSE; } /* Simplest case: first level key only */ @@ -6054,22 +6082,19 @@ _nm_utils_team_config_set (char **conf, iter_key = key3; } - json_object_set_new (json_element, iter_key, json_value); + json_object_set_new (json_element, iter_key, g_steal_pointer (&json_value)); done: - if (updated) { - _json_team_normalize_defaults (json, ( nm_streq0 (key, "runner") - && nm_streq0 (key2, "name"))); - g_free (*conf); - *conf = json_dumps (json, JSON_PRESERVE_ORDER); - /* Don't save an empty config */ - if (nm_streq0 (*conf, "{}")) { - g_free (*conf); - *conf = NULL; - } - } - json_decref (json); - return updated; + _json_team_normalize_defaults (json, ( nm_streq0 (key, "runner") + && nm_streq0 (key2, "name"))); + conf_new = json_dumps (json, JSON_PRESERVE_ORDER); + if (nm_streq0 (conf_new, "{}")) + nm_clear_g_free (&conf_new); + if (nm_streq0 (conf_new, *conf)) + return FALSE; + g_free (*conf); + *conf = g_steal_pointer (&conf_new); + return TRUE; } #else /* !WITH_JSON_VALIDATION */ @@ -6701,7 +6726,7 @@ nm_utils_base64secret_decode (const char *base64_key, base64_key_len = strlen (base64_key); - r = nm_sd_utils_unbase64mem (base64_key, base64_key_len, &bin_arr, &bin_len); + r = nm_sd_utils_unbase64mem (base64_key, base64_key_len, TRUE, &bin_arr, &bin_len); if (r < 0) return FALSE; if (bin_len != required_key_len) { @@ -6740,3 +6765,166 @@ nm_utils_base64secret_normalize (const char *base64_key, nm_explicit_bzero (buf, required_key_len); return TRUE; } + +GVariant * +_nm_utils_bridge_vlans_to_dbus (NMSetting *setting, const char *property) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + GVariantBuilder builder; + guint i; + + g_object_get (setting, property, &vlans, NULL); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); + + if (vlans) { + for (i = 0; i < vlans->len; i++) { + NMBridgeVlan *vlan = vlans->pdata[i]; + GVariantBuilder vlan_builder; + guint16 vid_start, vid_end; + + nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end); + + g_variant_builder_init (&vlan_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&vlan_builder, "{sv}", "vid-start", + g_variant_new_uint16 (vid_start)); + g_variant_builder_add (&vlan_builder, "{sv}", "vid-end", + g_variant_new_uint16 (vid_end)); + g_variant_builder_add (&vlan_builder, "{sv}", "pvid", + g_variant_new_boolean (nm_bridge_vlan_is_pvid (vlan))); + g_variant_builder_add (&vlan_builder, "{sv}", "untagged", + g_variant_new_boolean (nm_bridge_vlan_is_untagged (vlan))); + g_variant_builder_add (&builder, "a{sv}", &vlan_builder); + } + } + + return g_variant_builder_end (&builder); +} + +gboolean +_nm_utils_bridge_vlans_from_dbus (NMSetting *setting, + GVariant *connection_dict, + const char *property, + GVariant *value, + NMSettingParseFlags parse_flags, + GError **error) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + GVariantIter vlan_iter; + GVariant *vlan_var; + + g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), FALSE); + + vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); + g_variant_iter_init (&vlan_iter, value); + while (g_variant_iter_next (&vlan_iter, "@a{sv}", &vlan_var)) { + _nm_unused gs_unref_variant GVariant *var_unref = vlan_var; + NMBridgeVlan *vlan; + guint16 vid_start, vid_end; + gboolean pvid = FALSE, untagged = FALSE; + + if (!g_variant_lookup (vlan_var, "vid-start", "q", &vid_start)) + continue; + if ( vid_start < NM_BRIDGE_VLAN_VID_MIN + || vid_start > NM_BRIDGE_VLAN_VID_MAX) + continue; + + if (!g_variant_lookup (vlan_var, "vid-end", "q", &vid_end)) + continue; + if ( vid_end < NM_BRIDGE_VLAN_VID_MIN + || vid_end > NM_BRIDGE_VLAN_VID_MAX) + continue; + if (vid_start > vid_end) + continue; + + g_variant_lookup (vlan_var, "pvid", "b", &pvid); + if (pvid && vid_start != vid_end) + continue; + g_variant_lookup (vlan_var, "untagged", "b", &untagged); + + vlan = nm_bridge_vlan_new (vid_start, vid_end); + nm_bridge_vlan_set_untagged (vlan, untagged); + nm_bridge_vlan_set_pvid (vlan, pvid); + g_ptr_array_add (vlans, vlan); + } + + g_object_set (setting, property, vlans, NULL); + + return TRUE; +} + +gboolean +_nm_utils_bridge_vlan_verify_list (GPtrArray *vlans, + gboolean check_normalizable, + GError **error, + const char *setting, + const char *property) +{ + guint i; + gs_unref_hashtable GHashTable *h = NULL; + gboolean pvid_found = FALSE; + + if ( !vlans + || vlans->len <= 1) + return TRUE; + + if (check_normalizable) { + guint16 vid_prev_end, vid_start, vid_end; + + nm_assert (_nm_utils_bridge_vlan_verify_list (vlans, FALSE, NULL, setting, property)); + + nm_bridge_vlan_get_vid_range (vlans->pdata[0], NULL, &vid_prev_end); + for (i = 1; i < vlans->len; i++) { + const NMBridgeVlan *vlan = vlans->pdata[i]; + + nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end); + + if (vid_prev_end > vid_start) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Bridge VLANs %d and %d are not sorted by ascending vid"), + vid_prev_end, + vid_start); + g_prefix_error (error, "%s.%s: ", setting, property); + return FALSE; + } + + vid_prev_end = vid_end; + } + return TRUE; + } + + h = g_hash_table_new (nm_direct_hash, NULL); + for (i = 0; i < vlans->len; i++) { + NMBridgeVlan *vlan = vlans->pdata[i]; + guint16 v, vid_start, vid_end; + + nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end); + + for (v = vid_start; v <= vid_end; v++) { + if (!nm_g_hash_table_add (h, GUINT_TO_POINTER (v))) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("duplicate bridge VLAN vid %u"), v); + g_prefix_error (error, "%s.%s: ", setting, property); + return FALSE; + } + } + + if (nm_bridge_vlan_is_pvid (vlan)) { + if ( vid_start != vid_end + || pvid_found) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("only one VLAN can be the PVID")); + g_prefix_error (error, "%s.%s: ", setting, property); + return FALSE; + } + pvid_found = TRUE; + } + } + + return TRUE; +} diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h index 7fe2e45b..0ac2955b 100644 --- a/libnm-core/nm-version.h +++ b/libnm-core/nm-version.h @@ -188,4 +188,18 @@ # define NM_AVAILABLE_IN_1_16 #endif +#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_18 +# define NM_DEPRECATED_IN_1_18 G_DEPRECATED +# define NM_DEPRECATED_IN_1_18_FOR(f) G_DEPRECATED_FOR(f) +#else +# define NM_DEPRECATED_IN_1_18 +# define NM_DEPRECATED_IN_1_18_FOR(f) +#endif + +#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_18 +# define NM_AVAILABLE_IN_1_18 G_UNAVAILABLE(1,18) +#else +# define NM_AVAILABLE_IN_1_18 +#endif + #endif /* NM_VERSION_H */ diff --git a/libnm-core/tests/meson.build b/libnm-core/tests/meson.build index 7d772c80..1afb9c4b 100644 --- a/libnm-core/tests/meson.build +++ b/libnm-core/tests/meson.build @@ -34,7 +34,7 @@ foreach test_unit: test_units 'libnm-core-' + test_unit, [test_unit + '.c'] + enum, dependencies: [ - nm_core_dep, + libnm_core_dep, libnm_systemd_shared_no_logging_dep, ], c_args: [ diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 7d97296a..a6abf391 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -23,8 +23,8 @@ #include "nm-default.h" -#include "nm-utils/c-list-util.h" -#include "nm-utils/nm-enum-utils.h" +#include "nm-std-aux/c-list-util.h" +#include "nm-glib-aux/nm-enum-utils.h" #include "nm-utils.h" #include "nm-setting-private.h" @@ -63,8 +63,8 @@ #include "nm-setting-wpan.h" #include "nm-simple-connection.h" #include "nm-keyfile-internal.h" -#include "nm-utils/nm-dedup-multi.h" -#include "nm-ethtool-utils.h" +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-libnm-core-intern/nm-ethtool-utils.h" #include "test-general-enums.h" @@ -80,6 +80,29 @@ G_STATIC_ASSERT (sizeof (bool) <= sizeof (int)); /*****************************************************************************/ +static void +test_nm_ascii_spaces (void) +{ + int i; + const char *const S = NM_ASCII_SPACES; + + for (i = 0; S[i]; i++) + g_assert (!strchr (&S[i + 1], S[i])); + + for (i = 0; S[i] != '\0'; i++) + g_assert (g_ascii_isspace (S[i])); + + g_assert (!g_ascii_isspace ((char) 0)); + for (i = 1; i < 0x100; i++) { + if (g_ascii_isspace ((char) i)) + g_assert (strchr (S, (char) i)); + else + g_assert (!strchr (S, (char) i)); + } +} + +/*****************************************************************************/ + typedef struct _nm_packed { int v0; char v1; @@ -236,51 +259,324 @@ test_nm_g_slice_free_fcn (void) /*****************************************************************************/ static void -_do_test_nm_utils_strsplit_set (gboolean escape, const char *str, ...) +_do_test_nm_utils_strsplit_set_f_one (NMUtilsStrsplitSetFlags flags, + const char *str, + gsize words_len, + const char *const*exp_words) { - gs_unref_ptrarray GPtrArray *args_array = g_ptr_array_new (); - const char *const*args; +#define DELIMITERS " \n" +#define DELIMITERS_C ' ', '\n' + gs_free const char **words = NULL; - const char *arg; - gsize i; - va_list ap; + gsize i, j, k; + const gboolean f_allow_escaping = NM_FLAGS_HAS (flags, NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); + const gboolean f_preserve_empty = NM_FLAGS_HAS (flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); + const char *s1; + gsize initial_offset; + gs_strfreev char **words_g = NULL; + + g_assert (!NM_FLAGS_ANY (flags, ~( NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY))); + + /* assert that the epected words are valid (and don't contain unescaped delimiters). */ + for (i = 0; i < words_len; i++) { + const char *w = exp_words[i]; + + g_assert (w); + if (!f_preserve_empty) + g_assert (w[0]); + for (k = 0; w[k]; ) { + if ( f_allow_escaping + && w[k] == '\\') { + k++; + if (w[k] == '\0') + break; + k++; + continue; + } + g_assert (!NM_IN_SET (w[k], DELIMITERS_C)); + k++; + } + if (!f_allow_escaping) + g_assert (!NM_STRCHAR_ANY (w, ch, NM_IN_SET (ch, DELIMITERS_C))); + } - va_start (ap, str); - while ((arg = va_arg (ap, const char *))) - g_ptr_array_add (args_array, (gpointer) arg); - va_end (ap); - g_ptr_array_add (args_array, NULL); + initial_offset = (f_preserve_empty || !str) + ? 0u + : strspn (str, DELIMITERS); + + /* first compare our expected values with what g_strsplit_set() would + * do. */ + words_g = str ? g_strsplit_set (str, DELIMITERS, -1) : NULL; + if (str == NULL) { + g_assert_cmpint (words_len, ==, 0); + g_assert (!words_g); + } else if (nm_streq0 (str, "")) { + g_assert_cmpint (words_len, ==, 0); + g_assert (words_g); + g_assert (!words_g[0]); + } else { + g_assert (words_g); + g_assert (words_g[0]); + if (!f_allow_escaping) { + if (!f_preserve_empty) { + for (i = 0, j = 0; words_g[i]; i++) { + if (words_g[i][0] == '\0') + g_free (words_g[i]); + else + words_g[j++] = words_g[i]; + } + words_g[j] = NULL; + } + if (f_preserve_empty) + g_assert_cmpint (words_len, >, 0); + for (i = 0; i < words_len; i++) { + g_assert (exp_words[i]); + g_assert_cmpstr (exp_words[i], ==, words_g[i]); + } + g_assert (words_g[words_len] == NULL); + g_assert_cmpint (NM_PTRARRAY_LEN (words_g), ==, words_len); + g_assert (_nm_utils_strv_cmp_n (exp_words, words_len, NM_CAST_STRV_CC (words_g), -1) == 0); + } + } - args = (const char *const*) args_array->pdata; + if ( flags == NM_UTILS_STRSPLIT_SET_FLAGS_NONE + && nmtst_get_rand_bool ()) + words = nm_utils_strsplit_set (str, DELIMITERS); + else if ( flags == NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY + && nmtst_get_rand_bool ()) + words = nm_utils_strsplit_set_with_empty (str, DELIMITERS); + else + words = nm_utils_strsplit_set_full (str, DELIMITERS, flags); - words = nm_utils_strsplit_set (str, " \t\n", escape); + g_assert_cmpint (NM_PTRARRAY_LEN (words), ==, words_len); - if (!args[0]) { + if (words_len == 0) { g_assert (!words); g_assert ( !str - || NM_STRCHAR_ALL (str, ch, NM_IN_SET (ch, ' ', '\t', '\n'))); + || NM_STRCHAR_ALL (str, ch, NM_IN_SET (ch, DELIMITERS_C))); return; } + g_assert (words); - for (i = 0; args[i] || words[i]; i++) { - g_assert (args[i]); - g_assert (words[i]); - g_assert (args[i][0]); - g_assert (escape || NM_STRCHAR_ALL (args[i], ch, !NM_IN_SET (ch, ' ', '\t', '\n'))); - g_assert_cmpstr (args[i], ==, words[i]); + for (i = 0; i < words_len; i++) + g_assert_cmpstr (exp_words[i], ==, words[i]); + g_assert (words[words_len] == NULL); + + g_assert (_nm_utils_strv_cmp_n (exp_words, words_len, words, -1) == 0); + + s1 = words[0]; + g_assert (s1 >= (char *) &words[words_len + 1]); + s1 = &s1[strlen (str)]; + for (i = 1; i < words_len; i++) { + g_assert (&(words[i - 1])[strlen (words[i - 1])] < words[i]); + g_assert (words[i] <= s1); + } + + /* while strsplit removes all delimiters, we can relatively easily find them + * in the original string. Assert that the original string and the pointer offsets + * of words correspond. In particular, find idx_delim_after and idx_delim_before + * to determine which delimiter was after/before a word. */ + { + gsize idx_word_start; + gsize idx_delim_after_old = G_MAXSIZE; + + idx_word_start = initial_offset; + for (i = 0; i < words_len; i++) { + const gsize l_i = strlen (words[i]); + gsize idx_delim_after; + gsize idx_delim_before; + + /* find the delimiter *after* words[i]. We can do that by looking at the next + * word and calculating the pointer difference. + * + * The delimiter after the very last word is '\0' and requires strlen() to find. */ + idx_delim_after = initial_offset + ((words[i] - words[0]) + l_i); + if (idx_delim_after != idx_word_start + l_i) { + g_assert (!f_preserve_empty); + g_assert_cmpint (idx_word_start + l_i, <, idx_delim_after); + idx_word_start = idx_delim_after - l_i; + } + if (i + 1 < words_len) { + gsize x = initial_offset + ((words[i + 1] - words[0]) - 1); + + if (idx_delim_after != x) { + g_assert (!f_preserve_empty); + g_assert_cmpint (idx_delim_after, <, x); + for (k = idx_delim_after; k <= x; k++) + g_assert (NM_IN_SET (str[k], DELIMITERS_C)); + } + g_assert (NM_IN_SET (str[idx_delim_after], DELIMITERS_C)); + } else { + if (f_preserve_empty) + g_assert (NM_IN_SET (str[idx_delim_after], '\0')); + else + g_assert (NM_IN_SET (str[idx_delim_after], '\0', DELIMITERS_C)); + } + + /* find the delimiter *before* words[i]. */ + if (i == 0) { + /* there is only a delimiter *before*, with !f_preserve_empty and leading + * delimiters. */ + idx_delim_before = G_MAXSIZE; + if (initial_offset > 0) { + g_assert (!f_preserve_empty); + idx_delim_before = initial_offset - 1; + } + } else + idx_delim_before = initial_offset + (words[i] - words[0]) - 1; + if (idx_delim_before != G_MAXSIZE) + g_assert (NM_IN_SET (str[idx_delim_before], DELIMITERS_C)); + if (idx_delim_after_old != idx_delim_before) { + g_assert (!f_preserve_empty); + if (i == 0) { + g_assert_cmpint (initial_offset, >, 0); + g_assert_cmpint (idx_delim_before, !=, G_MAXSIZE); + g_assert_cmpint (idx_delim_before, ==, initial_offset - 1); + } else { + g_assert_cmpint (idx_delim_after_old, !=, G_MAXSIZE); + g_assert_cmpint (idx_delim_before, !=, G_MAXSIZE); + g_assert_cmpint (idx_delim_after_old, <, idx_delim_before); + for (k = idx_delim_after_old; k <= idx_delim_before; k++) + g_assert (NM_IN_SET (str[k], DELIMITERS_C)); + } + } + + for (k = 0; k < l_i; ) { + if ( f_allow_escaping + && str[idx_word_start + k] == '\\') { + k++; + if (k >= l_i) + break; + k++; + continue; + } + g_assert (!NM_IN_SET (str[idx_word_start + k], DELIMITERS_C)); + k++; + } + g_assert (strncmp (words[i], &str[idx_word_start], l_i) == 0); + + if (i > 0) { + const char *s = &(words[i - 1])[strlen (words[i - 1]) + 1]; + + if (s != words[i]) { + g_assert (!f_preserve_empty); + g_assert (s < words[i]); + } + } + + idx_word_start += l_i + 1; + idx_delim_after_old = idx_delim_after; + } } } -#define do_test_nm_utils_strsplit_set(str, ...) \ - _do_test_nm_utils_strsplit_set (str, ##__VA_ARGS__, NULL) +static void +_do_test_nm_utils_strsplit_set_f (NMUtilsStrsplitSetFlags flags, + const char *str, + gsize words_len, + const char *const*exp_words) +{ + _do_test_nm_utils_strsplit_set_f_one (flags, str, words_len, exp_words); + + if (NM_FLAGS_HAS (flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY)) { + gs_unref_ptrarray GPtrArray *exp_words2 = NULL; + gsize k; + + exp_words2 = g_ptr_array_new (); + for (k = 0; k < words_len; k++) { + if (exp_words[k][0] != '\0') + g_ptr_array_add (exp_words2, (gpointer) exp_words[k]); + } + + _do_test_nm_utils_strsplit_set_f_one (flags & (~NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY), + str, + exp_words2->len, + (const char *const*) exp_words2->pdata); + } +} + +#define do_test_nm_utils_strsplit_set_f(flags, str, ...) \ + _do_test_nm_utils_strsplit_set_f (flags, \ + str, \ + NM_NARG (__VA_ARGS__), \ + NM_MAKE_STRV (__VA_ARGS__)) + +#define do_test_nm_utils_strsplit_set(allow_escaping, str, ...) \ + do_test_nm_utils_strsplit_set_f ( (allow_escaping) \ + ? NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING \ + : NM_UTILS_STRSPLIT_SET_FLAGS_NONE, \ + str, \ + ##__VA_ARGS__) + +static void +_do_test_nm_utils_strsplit_set_simple (NMUtilsStrsplitSetFlags flags, + const char *str, + gsize words_len, + const char *const*exp_words) +{ + gs_free const char **tokens = NULL; + gsize n_tokens; + + tokens = nm_utils_strsplit_set_full (str, DELIMITERS, flags); + + if (!tokens) { + g_assert_cmpint (words_len, ==, 0); + return; + } + + g_assert (str && str[0]); + g_assert_cmpint (words_len, >, 0); + n_tokens = NM_PTRARRAY_LEN (tokens); + + if (_nm_utils_strv_cmp_n (exp_words, words_len, tokens, -1) != 0) { + gsize i; + + g_print (">>> split \"%s\" (flags %x) got %zu tokens (%zu expected)\n", str, (guint) flags, n_tokens, words_len); + for (i = 0; i < NM_MAX (n_tokens, words_len); i++) { + const char *s1 = i < n_tokens ? tokens[i] : NULL; + const char *s2 = i < words_len ? exp_words[i] : NULL; + + g_print (">>> [%zu]: %s - %s%s%s vs. %s%s%s\n", + i, + nm_streq0 (s1, s2) ? "same" : "diff", + NM_PRINT_FMT_QUOTE_STRING (s1), + NM_PRINT_FMT_QUOTE_STRING (s2)); + } + g_assert_not_reached (); + } + g_assert_cmpint (words_len, ==, NM_PTRARRAY_LEN (tokens)); +} +#define do_test_nm_utils_strsplit_set_simple(flags, str, ...) \ + _do_test_nm_utils_strsplit_set_simple ((flags), (str), NM_NARG (__VA_ARGS__), NM_MAKE_STRV (__VA_ARGS__)) static void test_nm_utils_strsplit_set (void) { + gs_unref_ptrarray GPtrArray *words_exp = NULL; + guint test_run; + + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_NONE, NULL); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_NONE, ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_NONE, " "); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_NONE, "a b", "a", "b"); + + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, NULL); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, " ", "", ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, " ", "", "", ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "a ", "a", "", ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "a b", "a", "", "b"); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, " ab b", "", "ab", "", "b"); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "ab b", "ab", "", "b"); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "abb", "abb"); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "abb bb ", "abb", "", "bb", ""); + do_test_nm_utils_strsplit_set_f (NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "abb bcb ", "abb", "bcb", ""); + do_test_nm_utils_strsplit_set (FALSE, NULL); do_test_nm_utils_strsplit_set (FALSE, ""); - do_test_nm_utils_strsplit_set (FALSE, "\t"); - do_test_nm_utils_strsplit_set (FALSE, " \t\n"); + do_test_nm_utils_strsplit_set (FALSE, "\n"); + do_test_nm_utils_strsplit_set (TRUE, " \t\n", "\t"); do_test_nm_utils_strsplit_set (FALSE, "a", "a"); do_test_nm_utils_strsplit_set (FALSE, "a b", "a", "b"); do_test_nm_utils_strsplit_set (FALSE, "a\rb", "a\rb"); @@ -300,6 +596,76 @@ test_nm_utils_strsplit_set (void) do_test_nm_utils_strsplit_set (TRUE, "foo\\", "foo\\"); do_test_nm_utils_strsplit_set (TRUE, "bar foo\\", "bar", "foo\\"); do_test_nm_utils_strsplit_set (TRUE, "\\ a b\\ \\ c", "\\ a", "b\\ \\ ", "c"); + + words_exp = g_ptr_array_new_with_free_func (g_free); + for (test_run = 0; test_run < 100; test_run++) { + gboolean f_allow_escaping = nmtst_get_rand_bool (); + guint words_len = nmtst_get_rand_int () % 100; + gs_free char *str = NULL; + guint i; + + g_ptr_array_set_size (words_exp, 0); + for (i = 0; i < words_len; i++) { + guint word_len; + char *word; + guint j; + + word_len = nmtst_get_rand_int (); + if ((word_len % 100) < 30) + word_len = 0; + else + word_len = (word_len >> 10) % 100; + word = g_new (char, word_len + 3); + for (j = 0; j < word_len; ) { + guint32 p = nmtst_get_rand_int (); + static const char delimiters_arr[] = { DELIMITERS_C }; + static const char regular_chars[] = "abcdefghijklmnopqrstuvwxyz"; + + if ( !f_allow_escaping + || (p % 1000) < 700) { + if (((p >> 20) % 100) < 20) + word[j++] = '\\'; + word[j++] = regular_chars[(p >> 11) % (G_N_ELEMENTS (regular_chars) - 1)]; + continue; + } + word[j++] = '\\'; + word[j++] = delimiters_arr[(p >> 11) % G_N_ELEMENTS (delimiters_arr)]; + } + word[j] = '\0'; + g_ptr_array_add (words_exp, word); + } + g_ptr_array_add (words_exp, NULL); + + str = g_strjoinv (" ", (char **) words_exp->pdata); + + if ( str[0] == '\0' + && words_len > 0) { + g_assert (words_len == 1); + g_assert_cmpstr (words_exp->pdata[0], ==, ""); + words_len = 0; + } + + _do_test_nm_utils_strsplit_set_f ( (f_allow_escaping ? NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING : NM_UTILS_STRSPLIT_SET_FLAGS_NONE) + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + str, + words_len, + (const char *const*) words_exp->pdata); + } + + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\t", "\t"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, "\t"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "\t", ""); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "\t\\\t\t\t\\\t", "\t\t\t\t"); + + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\ta", "\ta"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, "\ta", "a"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\ta\\ b\t\\ ", "\ta b\t "); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, "\ta\\ b\t\\ \t", "a b\t "); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "a\\ b", "a ", "b"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\ta\\ b", "\ta ", "b"); + do_test_nm_utils_strsplit_set_simple (NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, "\ta\\ b", "a ", "b"); } /*****************************************************************************/ @@ -809,54 +1175,54 @@ test_setting_vpn_items (void) nm_setting_vpn_remove_data_item (s_vpn, "foobar4-flags"); /* Try to add some blank values and make sure they are rejected */ - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_data_item (s_vpn, NULL, NULL); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (key) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_data_item (s_vpn, "", ""); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0])); nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (item) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0])); nm_setting_vpn_add_data_item (s_vpn, "foobar1", ""); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1"); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (key) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_data_item (s_vpn, "", "blahblah1"); g_test_assert_expected_messages (); nm_setting_vpn_foreach_data_item (s_vpn, vpn_check_empty_func, NULL); /* Try to add some blank secrets and make sure they are rejected */ - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_secret (s_vpn, NULL, NULL); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (key) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_secret (s_vpn, "", ""); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0])); nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (secret) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0])); nm_setting_vpn_add_secret (s_vpn, "foobar1", ""); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key != NULL)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1"); g_test_assert_expected_messages (); - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (strlen (key) > 0)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0])); nm_setting_vpn_add_secret (s_vpn, "", "blahblah1"); g_test_assert_expected_messages (); @@ -2347,7 +2713,7 @@ test_setting_connection_permissions_helpers (void) g_assert (!success); /* Ensure a bad [type] is rejected */ - NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (ptype)); + NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (ptype && ptype[0])); success = nm_setting_connection_add_permission (s_con, NULL, "blah", NULL); g_test_assert_expected_messages (); g_assert (!success); @@ -2687,6 +3053,7 @@ test_connection_diff_a_only (void) { NM_SETTING_IP_CONFIG_ROUTES, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_IP_CONFIG_ROUTE_METRIC, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_IP_CONFIG_ROUTE_TABLE, NM_SETTING_DIFF_RESULT_IN_A }, + { NM_SETTING_IP_CONFIG_ROUTING_RULES, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, NM_SETTING_DIFF_RESULT_IN_A }, @@ -4713,6 +5080,7 @@ test_connection_normalize_infiniband_mtu (void) { gs_unref_object NMConnection *con = NULL; NMSettingInfiniband *s_infini; + guint mtu_regular = nmtst_rand_select (2044, 2045, 65520); con = nmtst_create_minimal_connection ("test_connection_normalize_infiniband_mtu", NULL, NM_SETTING_INFINIBAND_SETTING_NAME, NULL); @@ -4725,26 +5093,26 @@ test_connection_normalize_infiniband_mtu (void) g_object_set (s_infini, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", - NM_SETTING_INFINIBAND_MTU, (guint) 2044, + NM_SETTING_INFINIBAND_MTU, (guint) mtu_regular, NULL); nmtst_assert_connection_verifies_and_normalizable (con); nmtst_connection_normalize (con); - g_assert_cmpint (2044, ==, nm_setting_infiniband_get_mtu (s_infini)); + g_assert_cmpint (mtu_regular, ==, nm_setting_infiniband_get_mtu (s_infini)); g_object_set (s_infini, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", - NM_SETTING_INFINIBAND_MTU, (guint) 2045, + NM_SETTING_INFINIBAND_MTU, (guint) 65521, NULL); nmtst_assert_connection_verifies_after_normalization (con, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); nmtst_connection_normalize (con); - g_assert_cmpint (2044, ==, nm_setting_infiniband_get_mtu (s_infini)); + g_assert_cmpint (65520, ==, nm_setting_infiniband_get_mtu (s_infini)); g_object_set (s_infini, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", - NM_SETTING_INFINIBAND_MTU, (guint) 65520, + NM_SETTING_INFINIBAND_MTU, (guint) mtu_regular, NULL); nmtst_assert_connection_verifies_without_normalization (con); - g_assert_cmpint (65520, ==, nm_setting_infiniband_get_mtu (s_infini)); + g_assert_cmpint (mtu_regular, ==, nm_setting_infiniband_get_mtu (s_infini)); g_object_set (s_infini, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", @@ -5540,7 +5908,7 @@ _sock_addr_endpoint (const char *endpoint, SockAddrUnion sockaddr = { }; g_assert (endpoint); - g_assert (!host == (port == -1)); + g_assert ((!host) == (port == -1)); g_assert (port >= -1 && port <= G_MAXUINT16); ep = nm_sock_addr_endpoint_new (endpoint); @@ -7706,53 +8074,6 @@ test_ethtool_offload (void) g_assert_cmpstr (d->optname, ==, NM_ETHTOOL_OPTNAME_FEATURE_RXHASH); } -static void -test_nm_utils_escape_spaces (void) -{ - char *to_free; - - g_assert_cmpstr (_nm_utils_escape_spaces (NULL, &to_free), ==, NULL); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces ("", &to_free), ==, ""); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces (" ", &to_free), ==, "\\ "); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces ("\t ", &to_free), ==, "\\\t\\ "); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces ("abc", &to_free), ==, "abc"); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces ("abc def", &to_free), ==, "abc\\ def"); - g_free (to_free); - - g_assert_cmpstr (_nm_utils_escape_spaces ("abc\tdef", &to_free), ==, "abc\\\tdef"); - g_free (to_free); -} - -static void -test_nm_utils_unescape_spaces (void) -{ -#define CHECK_STR(in, out) \ - G_STMT_START { \ - gs_free char *str = g_strdup (in); \ - \ - g_assert_cmpstr (_nm_utils_unescape_spaces (str), ==, out); \ - } G_STMT_END - - CHECK_STR ("\\a", "\\a"); - CHECK_STR ("foobar", "foobar"); - CHECK_STR ("foo bar", "foo bar"); - CHECK_STR ("foo\\ bar", "foo bar"); - CHECK_STR ("foo\\", "foo\\"); - CHECK_STR ("\\\\\t", "\\\t"); - -#undef CHECK_STR -} - /*****************************************************************************/ NMTST_DEFINE (); @@ -7761,6 +8082,7 @@ int main (int argc, char **argv) { nmtst_init (&argc, &argv, TRUE); + g_test_add_func ("/core/general/test_nm_ascii_spaces", test_nm_ascii_spaces); g_test_add_func ("/core/general/test_nm_hash", test_nm_hash); g_test_add_func ("/core/general/test_nm_g_slice_free_fcn", test_nm_g_slice_free_fcn); g_test_add_func ("/core/general/test_c_list_sort", test_c_list_sort); @@ -7905,8 +8227,6 @@ int main (int argc, char **argv) g_test_add_func ("/core/general/_nm_utils_dns_option_find_idx", test_nm_utils_dns_option_find_idx); g_test_add_func ("/core/general/_nm_utils_validate_json", test_nm_utils_check_valid_json); g_test_add_func ("/core/general/_nm_utils_team_config_equal", test_nm_utils_team_config_equal); - g_test_add_func ("/core/general/_nm_utils_escape_spaces", test_nm_utils_escape_spaces); - g_test_add_func ("/core/general/_nm_utils_unescape_spaces", test_nm_utils_unescape_spaces); g_test_add_func ("/core/general/test_nm_utils_enum", test_nm_utils_enum); g_test_add_func ("/core/general/nm-set-out", test_nm_set_out); g_test_add_func ("/core/general/route_attributes/parse", test_route_attributes_parse); diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c index 9b9d1c04..157e8f18 100644 --- a/libnm-core/tests/test-keyfile.c +++ b/libnm-core/tests/test-keyfile.c @@ -744,6 +744,102 @@ test_vpn_1 (void) /*****************************************************************************/ +static void +test_bridge_vlans (void) +{ + gs_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingBridge *s_bridge; + NMBridgeVlan *vlan; + guint16 vid, vid_end; + + con = nmtst_create_connection_from_keyfile ( + "[connection]\n" + "id=t\n" + "type=bridge\n" + "interface-name=br4\n" + "\n" + "[bridge]\n" + "vlans=900 , 1 pvid untagged, 100-123 untagged\n" + "", + "/test_bridge_port/vlans"); + s_bridge = NM_SETTING_BRIDGE (nm_connection_get_setting (con, NM_TYPE_SETTING_BRIDGE)); + g_assert (s_bridge); + g_assert_cmpuint (nm_setting_bridge_get_num_vlans (s_bridge), ==, 3); + + vlan = nm_setting_bridge_get_vlan (s_bridge, 0); + g_assert (vlan); + nm_bridge_vlan_get_vid_range (vlan, &vid, &vid_end); + g_assert_cmpuint (vid, ==, 1); + g_assert_cmpuint (vid_end, ==, 1); + g_assert_cmpint (nm_bridge_vlan_is_pvid (vlan), ==, TRUE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (vlan), ==, TRUE); + + vlan = nm_setting_bridge_get_vlan (s_bridge, 1); + g_assert (vlan); + nm_bridge_vlan_get_vid_range (vlan, &vid, &vid_end); + g_assert_cmpuint (vid, ==, 100); + g_assert_cmpuint (vid_end, ==, 123); + g_assert_cmpint (nm_bridge_vlan_is_pvid (vlan), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (vlan), ==, TRUE); + + vlan = nm_setting_bridge_get_vlan (s_bridge, 2); + g_assert (vlan); + nm_bridge_vlan_get_vid_range (vlan, &vid, &vid_end); + g_assert_cmpuint (vid, ==, 900); + g_assert_cmpuint (vid_end, ==, 900); + g_assert_cmpint (nm_bridge_vlan_is_pvid (vlan), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (vlan), ==, FALSE); + + CLEAR (&con, &keyfile); +} + +static void +test_bridge_port_vlans (void) +{ + gs_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingBridgePort *s_port; + NMBridgeVlan *vlan; + guint16 vid_start, vid_end; + + con = nmtst_create_connection_from_keyfile ( + "[connection]\n" + "id=t\n" + "type=dummy\n" + "interface-name=dummy1\n" + "master=br0\n" + "slave-type=bridge\n" + "\n" + "[bridge-port]\n" + "vlans=4094 pvid , 10-20 untagged\n" + "", + "/test_bridge_port/vlans"); + s_port = NM_SETTING_BRIDGE_PORT (nm_connection_get_setting (con, NM_TYPE_SETTING_BRIDGE_PORT)); + g_assert (s_port); + g_assert_cmpuint (nm_setting_bridge_port_get_num_vlans (s_port), ==, 2); + + vlan = nm_setting_bridge_port_get_vlan (s_port, 0); + g_assert (vlan); + nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end); + g_assert_cmpuint (vid_start, ==, 10); + g_assert_cmpuint (vid_end, ==, 20); + g_assert_cmpint (nm_bridge_vlan_is_pvid (vlan), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (vlan), ==, TRUE); + + vlan = nm_setting_bridge_port_get_vlan (s_port, 1); + g_assert (vlan); + nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end); + g_assert_cmpuint (vid_start, ==, 4094); + g_assert_cmpuint (vid_end, ==, 4094); + g_assert_cmpint (nm_bridge_vlan_is_pvid (vlan), ==, TRUE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (vlan), ==, FALSE); + + CLEAR (&con, &keyfile); +} + +/*****************************************************************************/ + NMTST_DEFINE (); int main (int argc, char **argv) @@ -757,6 +853,8 @@ int main (int argc, char **argv) g_test_add_func ("/core/keyfile/test_team_conf_read/invalid", test_team_conf_read_invalid); g_test_add_func ("/core/keyfile/test_user/1", test_user_1); g_test_add_func ("/core/keyfile/test_vpn/1", test_vpn_1); + g_test_add_func ("/core/keyfile/bridge/vlans", test_bridge_vlans); + g_test_add_func ("/core/keyfile/bridge-port/vlans", test_bridge_port_vlans); return g_test_run (); } diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c index 2011273a..03100a03 100644 --- a/libnm-core/tests/test-setting.c +++ b/libnm-core/tests/test-setting.c @@ -20,6 +20,7 @@ #include "nm-default.h" #include +#include #include "nm-utils.h" #include "nm-utils-private.h" @@ -1658,6 +1659,76 @@ test_sriov_parse_vlans (void) test_sriov_parse_vlan_one ("1.2.ad;2.0.q;5;3", TRUE, {1, 2, 1}, {2, 0, 0}, {3, 0, 0}, {5, 0, 0}); } +static void +test_bridge_vlans (void) +{ + NMBridgeVlan *v1, *v2; + GError *error = NULL; + guint16 vid_start, vid_end; + char *str; + + v1 = nm_bridge_vlan_from_str ("1 foobar", &error); + nmtst_assert_no_success (v1, error); + g_clear_error (&error); + + v1 = nm_bridge_vlan_from_str ("4095", &error); + nmtst_assert_no_success (v1, error); + g_clear_error (&error); + + /* test ranges */ + v1 = nm_bridge_vlan_from_str ("2-1000 untagged", &error); + nmtst_assert_success (v1, error); + g_assert_cmpint (nm_bridge_vlan_get_vid_range (v1, &vid_start, &vid_end), ==, TRUE); + g_assert_cmpuint (vid_start, ==, 2); + g_assert_cmpuint (vid_end, ==, 1000); + g_assert_cmpint (nm_bridge_vlan_is_pvid (v1), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (v1), ==, TRUE); + nm_bridge_vlan_unref (v1); + + /* test comparison (1) */ + v1 = nm_bridge_vlan_from_str ("10 untagged", &error); + nmtst_assert_success (v1, error); + + g_assert_cmpint (nm_bridge_vlan_get_vid_range (v1, &vid_start, &vid_end), ==, FALSE); + g_assert_cmpuint (vid_start, ==, 10); + g_assert_cmpuint (vid_end, ==, 10); + g_assert_cmpint (nm_bridge_vlan_is_sealed (v1), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_pvid (v1), ==, FALSE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (v1), ==, TRUE); + + nm_bridge_vlan_set_pvid (v1, TRUE); + nm_bridge_vlan_set_untagged (v1, FALSE); + nm_bridge_vlan_seal (v1); + + g_assert_cmpint (nm_bridge_vlan_is_sealed (v1), ==, TRUE); + g_assert_cmpint (nm_bridge_vlan_is_pvid (v1), ==, TRUE); + g_assert_cmpint (nm_bridge_vlan_is_untagged (v1), ==, FALSE); + + str = nm_bridge_vlan_to_str (v1, &error); + nmtst_assert_success (str, error); + g_assert_cmpstr (str, ==, "10 pvid"); + g_clear_pointer (&str, g_free); + + v2 = nm_bridge_vlan_from_str (" 10 pvid ", &error); + nmtst_assert_success (v2, error); + + g_assert_cmpint (nm_bridge_vlan_cmp (v1, v2), ==, 0); + + nm_bridge_vlan_unref (v1); + nm_bridge_vlan_unref (v2); + + /* test comparison (2) */ + v1 = nm_bridge_vlan_from_str ("10", &error); + nmtst_assert_success (v1, error); + v2 = nm_bridge_vlan_from_str ("20", &error); + nmtst_assert_success (v2, error); + + g_assert_cmpint (nm_bridge_vlan_cmp (v1, v2), <, 0); + + nm_bridge_vlan_unref (v1); + nm_bridge_vlan_unref (v2); +} + /*****************************************************************************/ static void @@ -2304,7 +2375,16 @@ test_roundtrip_conversion (gconstpointer test_data) NMSettingConnection *s_con = NULL; NMSettingWired *s_eth = NULL; NMSettingWireGuard *s_wg = NULL; + union { + struct { + NMSettingIPConfig *s_6; + NMSettingIPConfig *s_4; + }; + NMSettingIPConfig *s_x[2]; + } s_ip; + int is_ipv4; guint i; + gboolean success; switch (MODE) { case 0: @@ -2491,6 +2571,89 @@ test_roundtrip_conversion (gconstpointer test_data) _rndt_wg_peers_assert_equal (s_wg, wg_peers, TRUE, TRUE, FALSE); break; + case 3: + con = nmtst_create_minimal_connection (ID, UUID, NM_SETTING_WIRED_SETTING_NAME, &s_con); + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, + INTERFACE_NAME, + NULL); + nmtst_connection_normalize (con); + + s_eth = NM_SETTING_WIRED (nm_connection_get_setting (con, NM_TYPE_SETTING_WIRED)); + g_assert (NM_IS_SETTING_WIRED (s_eth)); + + g_object_set (s_eth, + NM_SETTING_WIRED_MTU, + ETH_MTU, + NULL); + + s_ip.s_4 = NM_SETTING_IP_CONFIG (nm_connection_get_setting (con, NM_TYPE_SETTING_IP4_CONFIG)); + g_assert (NM_IS_SETTING_IP4_CONFIG (s_ip.s_4)); + + s_ip.s_6 = NM_SETTING_IP_CONFIG (nm_connection_get_setting (con, NM_TYPE_SETTING_IP6_CONFIG)); + g_assert (NM_IS_SETTING_IP6_CONFIG (s_ip.s_6)); + + for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) { + g_assert (NM_IS_SETTING_IP_CONFIG (s_ip.s_x[is_ipv4])); + for (i = 0; i < 3; i++) { + char addrstr[NM_UTILS_INET_ADDRSTRLEN]; + + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr = NULL; + + rr = nm_ip_routing_rule_new (is_ipv4 ? AF_INET : AF_INET6); + nm_ip_routing_rule_set_priority (rr, i + 1); + if (i > 0) { + if (is_ipv4) + nm_sprintf_buf (addrstr, "192.168.%u.0", i); + else + nm_sprintf_buf (addrstr, "1:2:3:%x::", 10 + i); + nm_ip_routing_rule_set_from (rr, addrstr, is_ipv4 ? 24 + i : 64 + i); + } + nm_ip_routing_rule_set_table (rr, 1000 + i); + + success = nm_ip_routing_rule_validate (rr, &error); + nmtst_assert_success (success, error); + + nm_setting_ip_config_add_routing_rule (s_ip.s_x[is_ipv4], rr); + } + } + + g_ptr_array_add (kf_data_arr, + g_strdup_printf ("[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=ethernet\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[ethernet]\n" + "mac-address-blacklist=\n" + "%s" /* mtu */ + "\n" + "[ipv4]\n" + "dns-search=\n" + "method=auto\n" + "routing-rule1=priority 1 from 0.0.0.0/0 table 1000\n" + "routing-rule2=priority 2 from 192.168.1.0/25 table 1001\n" + "routing-rule3=priority 3 from 192.168.2.0/26 table 1002\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=auto\n" + "routing-rule1=priority 1 from ::/0 table 1000\n" + "routing-rule2=priority 2 from 1:2:3:b::/65 table 1001\n" + "routing-rule3=priority 3 from 1:2:3:c::/66 table 1002\n" + "", + ID, + UUID, + INTERFACE_NAME, + (ETH_MTU != 0) + ? nm_sprintf_bufa (100, "mtu=%u\n", ETH_MTU) + : "")); + + break; + default: g_assert_not_reached (); } @@ -2618,6 +2781,204 @@ 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 aab 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); + nm_clear_pointer (&rr1, nm_ip_routing_rule_unref); + + 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 @@ -2663,6 +3024,8 @@ main (int argc, char **argv) g_test_add_func ("/libnm/settings/tc_config/setting/duplicates", test_tc_config_setting_duplicates); g_test_add_func ("/libnm/settings/tc_config/dbus", test_tc_config_dbus); + g_test_add_func ("/libnm/settings/bridge/vlans", test_bridge_vlans); + #if WITH_JSON_VALIDATION g_test_add_func ("/libnm/settings/team/sync_runner_from_config_roundrobin", test_runner_roundrobin_sync_from_config); @@ -2694,9 +3057,12 @@ main (int argc, char **argv) g_test_add_func ("/libnm/settings/team-port/sycn_from_config_full", test_team_port_full_config); #endif - g_test_add_data_func ("/libnm/settings/roundtrip-conversion/general/0", GINT_TO_POINTER (0), test_roundtrip_conversion); + g_test_add_data_func ("/libnm/settings/roundtrip-conversion/general/0", GINT_TO_POINTER (0), test_roundtrip_conversion); 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/roundtrip-conversion/general/3", GINT_TO_POINTER (3), test_roundtrip_conversion); + + g_test_add_data_func ("/libnm/settings/routing-rule/1", GINT_TO_POINTER (0), test_routing_rule); return g_test_run (); } -- cgit 1.3.0-6-gf8a5