diff options
Diffstat (limited to 'src/core')
101 files changed, 5332 insertions, 2189 deletions
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c index 6f4c60f8..8606082c 100644 --- a/src/core/NetworkManagerUtils.c +++ b/src/core/NetworkManagerUtils.c @@ -23,6 +23,7 @@ #include "nm-setting-connection.h" #include "nm-setting-ip4-config.h" #include "nm-setting-ip6-config.h" +#include "settings/nm-settings.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-platform/nmp-object.h" @@ -30,6 +31,7 @@ #include "libnm-platform/nm-linux-platform.h" #include "libnm-platform/nm-platform-utils.h" #include "nm-auth-utils.h" +#include "devices/nm-device.h" /*****************************************************************************/ @@ -684,6 +686,53 @@ check_connection_cloned_mac_address(NMConnection *orig, } static gboolean +check_connection_controller(NMConnection *orig, NMConnection *candidate, GHashTable *settings) +{ + GHashTable *props; + const char *orig_controller = NULL, *cand_controller = NULL; + NMSettingConnection *s_con_orig, *s_con_cand, *s_con_controller; + NMSettingsConnection *con_controller; + + props = check_property_in_hash(settings, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + if (!props) + return TRUE; + + s_con_orig = nm_connection_get_setting_connection(orig); + s_con_cand = nm_connection_get_setting_connection(candidate); + orig_controller = nm_setting_connection_get_master(s_con_orig); + cand_controller = nm_setting_connection_get_master(s_con_cand); + + /* A generated connection uses the UUID to specify the controller. Accept + * candidates that specify as controller an interface name matching that + * UUID */ + if (orig_controller && cand_controller) { + if (nm_utils_is_uuid(orig_controller)) { + con_controller = nm_settings_get_connection_by_uuid(NM_SETTINGS_GET, orig_controller); + /* no connection found for that uuid */ + if (!con_controller) + return FALSE; + + s_con_controller = + nm_settings_connection_get_setting(con_controller, NM_META_SETTING_TYPE_CONNECTION); + if (nm_streq0(nm_setting_connection_get_interface_name(s_con_controller), + cand_controller)) { + remove_from_hash(settings, + props, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + return TRUE; + } else { + return FALSE; + } + } + } + + return FALSE; +} + +static gboolean check_connection_s390_props(NMConnection *orig, NMConnection *candidate, GHashTable *settings) { GHashTable *props1, *props2, *props3; @@ -764,9 +813,16 @@ check_possible_match(NMConnection *orig, if (!check_connection_cloned_mac_address(orig, candidate, settings)) return NULL; + if (!check_connection_controller(orig, candidate, settings)) + return NULL; + if (!check_connection_s390_props(orig, candidate, settings)) return NULL; + /* match properties are for matching from static to generated connections, + * so they are not really part of the difference. */ + g_hash_table_remove(settings, NM_SETTING_MATCH_SETTING_NAME); + if (g_hash_table_size(settings) == 0) return candidate; else @@ -896,6 +952,73 @@ nm_utils_match_connection(NMConnection *const *connections, /*****************************************************************************/ +const struct _NMMatchSpecDeviceData * +nm_match_spec_device_data_init_from_device(struct _NMMatchSpecDeviceData *out_data, + NMDevice *device) +{ + const char *hw_address; + gboolean is_fake; + + nm_assert(out_data); + + if (!device) { + *out_data = (NMMatchSpecDeviceData){}; + return out_data; + } + + nm_assert(NM_IS_DEVICE(device)); + + hw_address = nm_device_get_permanent_hw_address_full( + device, + !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_PLATFORM_INIT), + &is_fake); + + /* Note that here we access various getters on @device, without cloning + * or taking ownership and return it to the caller. + * + * The returned data is only valid, until NMDevice gets modified again. */ + + *out_data = (NMMatchSpecDeviceData){ + .interface_name = nm_device_get_iface(device), + .device_type = nm_device_get_type_description(device), + .driver = nm_device_get_driver(device), + .driver_version = nm_device_get_driver_version(device), + .hwaddr = is_fake ? NULL : hw_address, + .s390_subchannels = nm_device_get_s390_subchannels(device), + .dhcp_plugin = nm_dhcp_manager_get_config(nm_dhcp_manager_get()), + }; + + return out_data; +} + +const NMMatchSpecDeviceData * +nm_match_spec_device_data_init_from_platform(NMMatchSpecDeviceData *out_data, + const NMPlatformLink *pllink, + const char *match_device_type, + const char *match_dhcp_plugin) +{ + nm_assert(out_data); + + /* we can only match by certain properties that are available on the + * platform link (and even @pllink might be missing. + * + * It's still useful because of specs like "*" and "except:interface-name:eth0", + * which match even in that case. */ + + *out_data = (NMMatchSpecDeviceData){ + .interface_name = pllink ? pllink->name : NULL, + .device_type = match_device_type, + .driver = pllink ? pllink->driver : NULL, + .driver_version = NULL, + .hwaddr = NULL, + .s390_subchannels = NULL, + .dhcp_plugin = match_dhcp_plugin, + }; + return out_data; +} + +/*****************************************************************************/ + int nm_match_spec_device_by_pllink(const NMPlatformLink *pllink, const char *match_device_type, @@ -903,32 +1026,15 @@ nm_match_spec_device_by_pllink(const NMPlatformLink *pllink, const GSList *specs, int no_match_value) { - NMMatchSpecMatchType m; + NMMatchSpecMatchType m; + NMMatchSpecDeviceData data; - /* we can only match by certain properties that are available on the - * platform link (and even @pllink might be missing. - * - * It's still useful because of specs like "*" and "except:interface-name:eth0", - * which match even in that case. */ m = nm_match_spec_device(specs, - pllink ? pllink->name : NULL, - match_device_type, - pllink ? pllink->driver : NULL, - NULL, - NULL, - NULL, - match_dhcp_plugin); - - switch (m) { - case NM_MATCH_SPEC_MATCH: - return TRUE; - case NM_MATCH_SPEC_NEG_MATCH: - return FALSE; - case NM_MATCH_SPEC_NO_MATCH: - return no_match_value; - } - nm_assert_not_reached(); - return no_match_value; + nm_match_spec_device_data_init_from_platform(&data, + pllink, + match_device_type, + match_dhcp_plugin)); + return nm_match_spec_match_type_to_bool(m, no_match_value); } /*****************************************************************************/ @@ -1748,6 +1854,13 @@ nm_utils_platform_capture_ip_setting(NMPlatform *platform, method = maybe_ipv6_disabled ? NM_SETTING_IP6_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_IGNORE; } + + /* The IPv6 method "ignore" and "disabled" are not supported for loopback */ + if (ifindex == 1 + && NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; g_object_set(s_ip, NM_SETTING_IP_CONFIG_METHOD, method, NULL); nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4), ifindex); @@ -1860,3 +1973,13 @@ nm_linux_platform_setup_with_tc_cache(void) { nm_platform_setup(nm_linux_platform_new(NULL, FALSE, FALSE, TRUE)); } + +/*****************************************************************************/ + +NM_UTILS_FLAGS2STR_DEFINE( + nm_settings_autoconnect_blocked_reason_to_string, + NMSettingsAutoconnectBlockedReason, + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, "failed"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), ); diff --git a/src/core/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h index 67c9cba4..7d8afe5a 100644 --- a/src/core/NetworkManagerUtils.h +++ b/src/core/NetworkManagerUtils.h @@ -89,6 +89,20 @@ NMConnection *nm_utils_match_connection(NMConnection *const *connections, NMUtilsMatchFilterFunc match_filter_func, gpointer match_filter_data); +/*****************************************************************************/ + +struct _NMMatchSpecDeviceData; + +const struct _NMMatchSpecDeviceData * +nm_match_spec_device_data_init_from_device(struct _NMMatchSpecDeviceData *out_data, + NMDevice *device); + +const struct _NMMatchSpecDeviceData * +nm_match_spec_device_data_init_from_platform(struct _NMMatchSpecDeviceData *out_data, + const NMPlatformLink *pllink, + const char *match_device_type, + const char *match_dhcp_plugin); + int nm_match_spec_device_by_pllink(const NMPlatformLink *pllink, const char *match_device_type, const char *match_dhcp_plugin, @@ -228,6 +242,26 @@ void nm_utils_ip_routes_to_dbus(int addr_family, /*****************************************************************************/ +typedef enum _nm_packed { + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE = 0, + + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0), + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED = (1LL << 1), + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS = (1LL << 2), + + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_ALL = + (NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST + | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED + | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS), +} NMSettingsAutoconnectBlockedReason; + +const char * +nm_settings_autoconnect_blocked_reason_to_string(NMSettingsAutoconnectBlockedReason reason, + char *buf, + gsize len); + +/*****************************************************************************/ + /* For now, all we track about a DHCP lease is the GHashTable with * the options. * diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c index fcd16b1c..89bc84d8 100644 --- a/src/core/devices/adsl/nm-device-adsl.c +++ b/src/core/devices/adsl/nm-device-adsl.c @@ -67,13 +67,16 @@ get_generic_capabilities(NMDevice *dev) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingAdsl *s_adsl; const char *protocol; if (!NM_DEVICE_CLASS(nm_device_adsl_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_adsl = nm_connection_get_setting_adsl(connection); diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c index a8258123..8b13e97d 100644 --- a/src/core/devices/bluetooth/nm-device-bt.c +++ b/src/core/devices/bluetooth/nm-device-bt.c @@ -192,7 +192,10 @@ can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **speci } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceBt *self = NM_DEVICE_BT(device); NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); @@ -200,7 +203,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * const char *bdaddr; if (!NM_DEVICE_CLASS(nm_device_bt_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; if (!get_connection_bt_type_check(self, connection, NULL, error)) diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c index 870a1c14..f3386ddb 100644 --- a/src/core/devices/nm-device-6lowpan.c +++ b/src/core/devices/nm-device-6lowpan.c @@ -72,14 +72,22 @@ create_and_realize(NMDevice *device, s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); g_return_val_if_fail(s_6lowpan, FALSE); - parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0; + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "6LoWPAN device can not be created without a parent interface"); + return FALSE; + } + parent_ifindex = nm_device_get_ifindex(parent); if (parent_ifindex <= 0) { g_set_error(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES, - "6LoWPAN devices can not be created without a parent interface"); - g_return_val_if_fail(!parent, FALSE); + "cannot retrieve ifindex of interface %s (%s)", + nm_device_get_iface(parent), + nm_device_get_type_desc(parent)); return FALSE; } diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c index 56c5ec50..10fe8092 100644 --- a/src/core/devices/nm-device-bond.c +++ b/src/core/devices/nm-device-bond.c @@ -39,7 +39,8 @@ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, \ NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, \ - NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, \ + NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, NM_SETTING_BOND_OPTION_LACP_ACTIVE #define OPTIONS_REAPPLY_SUBSET \ NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, \ @@ -51,11 +52,12 @@ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER, \ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, \ - NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY + NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, \ + NM_SETTING_BOND_OPTION_LACP_ACTIVE #define OPTIONS_REAPPLY_FULL \ OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \ - NM_SETTING_BOND_OPTION_ARP_IP_TARGET + NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET /*****************************************************************************/ @@ -267,7 +269,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new cur_strv = nm_strsplit_set_full(cur_arp_ip_target, NM_ASCII_SPACES, NM_STRSPLIT_SET_FLAGS_STRSTRIP); - new_strv = nm_utils_bond_option_arp_ip_targets_split(new_arp_ip_target); + new_strv = nm_utils_bond_option_ip_split(new_arp_ip_target); cur_len = NM_PTRARRAY_LEN(cur_strv); new_len = NM_PTRARRAY_LEN(new_strv); @@ -364,7 +366,7 @@ _bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_ int i; int added = 0; - ip = nm_utils_bond_option_arp_ip_targets_split(value); + ip = nm_utils_bond_option_ip_split(value); if (!ip) return added; @@ -380,6 +382,31 @@ _bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_ return added; } +static guint8 +_bond_ns_ip6_target_to_platform(const char *value, + struct in6_addr out[static NM_BOND_MAX_ARP_TARGETS]) +{ + gs_free const char **ip = NULL; + struct in6_addr in6_a; + int i; + int added = 0; + + ip = nm_utils_bond_option_ip_split(value); + + if (!ip) + return added; + + for (i = 0; ip[i]; i++) { + if (added > NM_BOND_MAX_ARP_TARGETS - 1) + break; + if (!nm_inet_parse_bin(AF_INET6, ip[i], NULL, &in6_a)) + nm_assert_not_reached(); /* verify() already validated the IP addresses */ + + out[added++] = in6_a; + } + return added; +} + static int _setting_bond_primary_opt_as_ifindex(NMSettingBond *s_bond) { @@ -436,6 +463,10 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY), .num_grat_arp = _v_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP), .all_ports_active = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE), + .arp_missed_max = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX), + .lacp_active = _v_fcn(_nm_setting_bond_lacp_active_from_string, + s_bond, + NM_SETTING_BOND_OPTION_LACP_ACTIVE), .lacp_rate = _v_fcn(_nm_setting_bond_lacp_rate_from_string, s_bond, NM_SETTING_BOND_OPTION_LACP_RATE), @@ -455,6 +486,11 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p props->arp_ip_targets_num = _bond_arp_ip_target_to_platform(opt_value, props->arp_ip_target); + opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_NS_IP6_TARGET); + if (opt_value != NULL) + props->ns_ip6_targets_num = + _bond_ns_ip6_target_to_platform(opt_value, props->ns_ip6_target); + props->miimon_has = !props->arp_interval && !props->arp_validate; props->updelay_has = props->miimon_has && props->miimon; props->downdelay_has = props->miimon_has && props->miimon; @@ -462,6 +498,7 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p props->resend_igmp_has = props->resend_igmp != 1; props->lp_interval_has = props->lp_interval != 1; props->tlb_dynamic_lb_has = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB); + props->lacp_active_has = NM_IN_SET(props->mode, NM_BOND_MODE_8023AD); } static void @@ -639,12 +676,14 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_ nm_platform_link_change(nm_device_get_platform(port), nm_device_get_ifindex(port), + NULL, &((NMPlatformLinkBondPort){ .queue_id = s_port ? nm_setting_bond_port_get_queue_id(s_port) : NM_BOND_PORT_QUEUE_ID_DEF, .prio = prio_has ? prio : 0, .prio_has = prio_has, - })); + }), + 0); } static NMTernary @@ -686,8 +725,13 @@ attach_port(NMDevice *device, return TRUE; } -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) { NMDeviceBond *self = NM_DEVICE_BOND(device); gboolean success; @@ -749,6 +793,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) _LOGI(LOGD_BOND, "bond port %s was detached", nm_device_get_ip_iface(port)); } } + + return TRUE; } static gboolean diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c index c5ce34c2..9a45dbf3 100644 --- a/src/core/devices/nm-device-bridge.c +++ b/src/core/devices/nm-device-bridge.c @@ -101,13 +101,16 @@ check_connection_available(NMDevice *device, } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingBridge *s_bridge; const char *mac_address; if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; if (nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME) @@ -434,96 +437,6 @@ static const Option slave_options[] = { OPTION(NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", OPTION_TYPE_BOOL(FALSE), ), {0}}; -static void -commit_option(NMDevice *device, NMSetting *setting, const Option *option, gboolean slave) -{ - int ifindex = nm_device_get_ifindex(device); - nm_auto_unset_gvalue GValue val = G_VALUE_INIT; - GParamSpec *pspec; - const char *value; - char value_buf[100]; - - if (slave) - nm_assert(NM_IS_SETTING_BRIDGE_PORT(setting)); - else - nm_assert(NM_IS_SETTING_BRIDGE(setting)); - - pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), option->name); - nm_assert(pspec); - - g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec)); - g_object_get_property((GObject *) setting, option->name, &val); - - if (option->to_sysfs) { - value = option->to_sysfs(&val); - goto out; - } - - switch (pspec->value_type) { - case G_TYPE_BOOLEAN: - value = g_value_get_boolean(&val) ? "1" : "0"; - break; - case G_TYPE_UINT64: - case G_TYPE_UINT: - { - guint64 uval; - - if (pspec->value_type == G_TYPE_UINT64) - uval = g_value_get_uint64(&val); - else - uval = (guint) g_value_get_uint(&val); - - /* zero means "unspecified" for some NM properties but isn't in the - * allowed kernel range, so reset the property to the default value. - */ - if (option->default_if_zero && uval == 0) { - if (pspec->value_type == G_TYPE_UINT64) - uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec); - else - uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec); - } - - /* Linux kernel bridge interfaces use 'centiseconds' for time-based values. - * In reality it's not centiseconds, but depends on HZ and USER_HZ, which - * is almost always works out to be a multiplier of 100, so we can assume - * centiseconds. See clock_t_to_jiffies(). - */ - if (option->user_hz_compensate) - uval *= 100; - - if (pspec->value_type == G_TYPE_UINT64) - nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval); - else - nm_sprintf_buf(value_buf, "%u", (guint) uval); - - value = value_buf; - } break; - case G_TYPE_STRING: - value = g_value_get_string(&val); - break; - default: - nm_assert_not_reached(); - value = NULL; - break; - } - -out: - if (!value) - return; - - if (slave) { - nm_platform_sysctl_slave_set_option(nm_device_get_platform(device), - ifindex, - option->sysname, - value); - } else { - nm_platform_sysctl_master_set_option(nm_device_get_platform(device), - ifindex, - option->sysname, - value); - } -} - static const NMPlatformBridgeVlan ** setting_vlans_to_platform(GPtrArray *array) { @@ -558,19 +471,92 @@ setting_vlans_to_platform(GPtrArray *array) } static void -commit_slave_options(NMDevice *device, NMSettingBridgePort *setting) +commit_port_options(NMDevice *device, NMSettingBridgePort *setting) { const Option *option; NMSetting *s; gs_unref_object NMSetting *s_clear = NULL; + int ifindex = nm_device_get_ifindex(device); if (setting) s = NM_SETTING(setting); else s = s_clear = nm_setting_bridge_port_new(); - for (option = slave_options; option->name; option++) - commit_option(device, s, option, TRUE); + for (option = slave_options; option->name; option++) { + nm_auto_unset_gvalue GValue val = G_VALUE_INIT; + GParamSpec *pspec; + const char *value; + char value_buf[100]; + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(s), option->name); + nm_assert(pspec); + + g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec)); + g_object_get_property((GObject *) s, option->name, &val); + + if (option->to_sysfs) { + value = option->to_sysfs(&val); + goto out; + } + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + value = g_value_get_boolean(&val) ? "1" : "0"; + break; + case G_TYPE_UINT64: + case G_TYPE_UINT: + { + guint64 uval; + + if (pspec->value_type == G_TYPE_UINT64) + uval = g_value_get_uint64(&val); + else + uval = (guint) g_value_get_uint(&val); + + /* zero means "unspecified" for some NM properties but isn't in the + * allowed kernel range, so reset the property to the default value. + */ + if (option->default_if_zero && uval == 0) { + if (pspec->value_type == G_TYPE_UINT64) + uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec); + else + uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec); + } + + /* Linux kernel bridge interfaces use 'centiseconds' for time-based values. + * In reality it's not centiseconds, but depends on HZ and USER_HZ, which + * is almost always works out to be a multiplier of 100, so we can assume + * centiseconds. See clock_t_to_jiffies(). + */ + if (option->user_hz_compensate) + uval *= 100; + + if (pspec->value_type == G_TYPE_UINT64) + nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval); + else + nm_sprintf_buf(value_buf, "%u", (guint) uval); + + value = value_buf; + } break; + case G_TYPE_STRING: + value = g_value_get_string(&val); + break; + default: + nm_assert_not_reached(); + value = NULL; + break; + } + +out: + if (!value) + return; + + nm_platform_sysctl_slave_set_option(nm_device_get_platform(device), + ifindex, + option->sysname, + value); + } } static void @@ -746,8 +732,13 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge) enabled = nm_setting_bridge_get_vlan_filtering(s_bridge); if (!enabled) { - nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0"); - nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "1"); + nm_platform_link_set_bridge_info( + plat, + ifindex, + &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has = TRUE, + .vlan_filtering_val = FALSE, + .vlan_default_pvid_has = TRUE, + .vlan_default_pvid_val = 1})); nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL); return TRUE; } @@ -762,14 +753,17 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge) self->vlan_configured = TRUE; - /* Filtering must be disabled to change the default PVID */ - if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0")) - return FALSE; - - /* Clear the default PVID so that we later can force the re-creation of + /* Filtering must be disabled to change the default PVID. + * Clear the default PVID so that we later can force the re-creation of * default PVID VLANs by writing the option again. */ - if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "0")) - return FALSE; + + nm_platform_link_set_bridge_info( + plat, + ifindex, + &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has = TRUE, + .vlan_filtering_val = FALSE, + .vlan_default_pvid_has = TRUE, + .vlan_default_pvid_val = 0})); /* Clear all existing VLANs */ if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL)) @@ -779,11 +773,11 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge) * a PVID VLAN on each port, including the bridge itself. */ pvid = nm_setting_bridge_get_vlan_default_pvid(s_bridge); if (pvid) { - char value[32]; - - nm_sprintf_buf(value, "%u", pvid); - if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", value)) - return FALSE; + nm_platform_link_set_bridge_info( + plat, + ifindex, + &((NMPlatformLinkSetBridgeInfoData){.vlan_default_pvid_has = TRUE, + .vlan_default_pvid_val = pvid})); } /* Create VLANs only after setting the default PVID, so that @@ -793,8 +787,12 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge) if (plat_vlans && !nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, plat_vlans)) return FALSE; - if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "1")) - return FALSE; + nm_platform_link_set_bridge_info(plat, + ifindex, + &((NMPlatformLinkSetBridgeInfoData){ + .vlan_filtering_has = TRUE, + .vlan_filtering_val = TRUE, + })); return TRUE; } @@ -1027,7 +1025,7 @@ attach_port(NMDevice *device, return FALSE; } - commit_slave_options(port, s_port); + commit_port_options(port, s_port); _LOGI(LOGD_BRIDGE, "attached bridge port %s", nm_device_get_ip_iface(port)); } else { @@ -1037,8 +1035,13 @@ attach_port(NMDevice *device, return TRUE; } -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) { NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); gboolean success; @@ -1055,7 +1058,7 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) if (ifindex_slave <= 0) { _LOGD(LOGD_TEAM, "bridge port %s is already detached", nm_device_get_ip_iface(port)); - return; + return TRUE; } if (configure) { @@ -1071,6 +1074,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) } else { _LOGI(LOGD_BRIDGE, "bridge port %s was detached", nm_device_get_ip_iface(port)); } + + return TRUE; } static gboolean diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c index 97cf84a1..aedacc24 100644 --- a/src/core/devices/nm-device-ethernet.c +++ b/src/core/devices/nm-device-ethernet.c @@ -344,13 +344,16 @@ match_subchans(NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_ma } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device); NMSettingWired *s_wired; if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; if (nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME) diff --git a/src/core/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h index ac5ae05f..fc3d9dd4 100644 --- a/src/core/devices/nm-device-factory.h +++ b/src/core/devices/nm-device-factory.h @@ -208,8 +208,7 @@ NMDevice *nm_device_factory_create_device(NMDeviceFactory *factory, \ NM_DEVICE_FACTORY_DECLARE_TYPES(st_code) \ \ - static void nm_##lower##_device_factory_init(NM##mixed##DeviceFactory *self) \ - {} \ + static void nm_##lower##_device_factory_init(NM##mixed##DeviceFactory *self) {} \ \ static void nm_##lower##_device_factory_class_init(NM##mixed##DeviceFactoryClass *klass) \ { \ diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c index 9f85925b..c0dcf0de 100644 --- a/src/core/devices/nm-device-generic.c +++ b/src/core/devices/nm-device-generic.c @@ -16,7 +16,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_TYPE_DESCRIPTION, ); typedef struct { - char *type_description; + const char *type_description; } NMDeviceGenericPrivate; struct _NMDeviceGeneric { @@ -64,20 +64,23 @@ realize_start_notify(NMDevice *device, const NMPlatformLink *plink) NM_DEVICE_CLASS(nm_device_generic_parent_class)->realize_start_notify(device, plink); - nm_clear_g_free(&priv->type_description); ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self)); - if (ifindex > 0) + if (ifindex > 0) { priv->type_description = - g_strdup(nm_platform_link_get_type_name(nm_device_get_platform(device), ifindex)); + nm_platform_link_get_type_name(nm_device_get_platform(device), ifindex); + } } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingConnection *s_con; if (!NM_DEVICE_CLASS(nm_device_generic_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_con = nm_connection_get_setting_connection(connection); @@ -125,22 +128,6 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) } } -static void -set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) -{ - NMDeviceGeneric *self = NM_DEVICE_GENERIC(object); - NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); - - switch (prop_id) { - case PROP_TYPE_DESCRIPTION: - priv->type_description = g_value_dup_string(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - /*****************************************************************************/ static void @@ -177,17 +164,6 @@ nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing) NULL); } -static void -dispose(GObject *object) -{ - NMDeviceGeneric *self = NM_DEVICE_GENERIC(object); - NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); - - nm_clear_g_free(&priv->type_description); - - G_OBJECT_CLASS(nm_device_generic_parent_class)->dispose(object); -} - static const NMDBusInterfaceInfoExtended interface_info_device_generic = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( NM_DBUS_INTERFACE_DEVICE_GENERIC, @@ -207,9 +183,7 @@ nm_device_generic_class_init(NMDeviceGenericClass *klass) NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); object_class->constructor = constructor; - object_class->dispose = dispose; object_class->get_property = get_property; - object_class->set_property = set_property; dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_generic); @@ -228,7 +202,7 @@ nm_device_generic_class_init(NMDeviceGenericClass *klass) "", "", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); } diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c index d025931e..6ce61d0b 100644 --- a/src/core/devices/nm-device-infiniband.c +++ b/src/core/devices/nm-device-infiniband.c @@ -87,7 +87,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) /* With some drivers the interface must be down to set transport mode */ nm_device_take_down(device, TRUE); ok = nm_platform_sysctl_set(nm_device_get_platform(device), - NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"), + NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "mode"), transport_mode); nm_device_bring_up(device); @@ -108,15 +108,18 @@ get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *ou } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingInfiniband *s_infiniband; if (!NM_DEVICE_CLASS(nm_device_infiniband_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { const char *mac; const char *hw_addr; diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c index a5760bf4..cc62180e 100644 --- a/src/core/devices/nm-device-ip-tunnel.c +++ b/src/core/devices/nm-device-ip-tunnel.c @@ -498,7 +498,10 @@ update_connection(NMDevice *device, NMConnection *connection) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL(device); NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); @@ -507,7 +510,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * const char *parent; if (!NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); @@ -520,7 +523,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * return FALSE; } - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { /* Check parent interface; could be an interface name or a UUID */ parent = nm_setting_ip_tunnel_get_parent(s_ip_tunnel); if (parent && !nm_device_match_parent(device, parent)) { diff --git a/src/core/devices/nm-device-logging.h b/src/core/devices/nm-device-logging.h index ffc90c2d..53330b5e 100644 --- a/src/core/devices/nm-device-logging.h +++ b/src/core/devices/nm-device-logging.h @@ -11,37 +11,37 @@ #if !_NM_CC_SUPPORT_GENERIC #define _NM_DEVICE_CAST(self) ((NMDevice *) (self)) #elif !defined(_NMLOG_DEVICE_TYPE) -#define _NM_DEVICE_CAST(self) _Generic((self), NMDevice * : ((NMDevice *) (self))) +#define _NM_DEVICE_CAST(self) _Generic((self), NMDevice *: ((NMDevice *) (self))) #else -#define _NM_DEVICE_CAST(self) \ - _Generic((self), _NMLOG_DEVICE_TYPE * \ - : ((NMDevice *) (self)), NMDevice * \ - : ((NMDevice *) (self))) +#define _NM_DEVICE_CAST(self) \ + _Generic((self), _NMLOG_DEVICE_TYPE *: ((NMDevice *) (self)), NMDevice *: ((NMDevice *) (self))) #endif #undef _NMLOG_ENABLED #define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain))) -#define _NMLOG(level, domain, ...) \ - G_STMT_START \ - { \ - const NMLogLevel _level = (level); \ - const NMLogDomain _domain = (domain); \ - \ - if (nm_logging_enabled(_level, _domain)) { \ - typeof(*self) *const _self = (self); \ - const char *const _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self)); \ - \ - nm_log_obj(_level, \ - _domain, \ - _ifname, \ - NULL, \ - _self, \ - "device", \ - "%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]") \ - _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } \ +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + typeof(*self) *const _self = (self); \ + const char *const _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self)); \ + const char *_type = nm_device_get_type_desc_for_log(_NM_DEVICE_CAST(_self)); \ + \ + nm_log_obj(_level, \ + _domain, \ + _ifname, \ + NULL, \ + _self, \ + "device", \ + "%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]"), \ + NM_PRINT_FMT_QUOTED(_type, "[", _type, "]", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ G_STMT_END #endif /* __NETWORKMANAGER_DEVICE_LOGGING_H__ */ diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c index 3f57bfb1..8cdef0cf 100644 --- a/src/core/devices/nm-device-macvlan.c +++ b/src/core/devices/nm-device-macvlan.c @@ -208,14 +208,22 @@ create_and_realize(NMDevice *device, s_macvlan = nm_connection_get_setting_macvlan(connection); g_return_val_if_fail(s_macvlan, FALSE); - parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0; + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "MACVLAN device can not be created without a parent interface"); + return FALSE; + } + parent_ifindex = nm_device_get_ifindex(parent); if (parent_ifindex <= 0) { g_set_error(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES, - "MACVLAN devices can not be created without a parent interface"); - g_return_val_if_fail(!parent, FALSE); + "cannot retrieve ifindex of interface %s (%s)", + nm_device_get_iface(parent), + nm_device_get_type_desc(parent)); return FALSE; } @@ -274,14 +282,17 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) /*****************************************************************************/ static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device); NMSettingMacvlan *s_macvlan; const char *parent = NULL; if (!NM_DEVICE_CLASS(nm_device_macvlan_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_macvlan = nm_connection_get_setting_macvlan(connection); @@ -300,7 +311,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * } /* Before the device is realized some properties will not be set */ - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { if (setting_mode_to_platform(nm_setting_macvlan_get_mode(s_macvlan)) != priv->props.mode) { nm_utils_error_set_literal(error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c index 1860df3c..27566607 100644 --- a/src/core/devices/nm-device-ppp.c +++ b/src/core/devices/nm-device-ppp.c @@ -149,12 +149,15 @@ _ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data, /*****************************************************************************/ static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingPppoe *s_pppoe; if (!NM_DEVICE_CLASS(nm_device_ppp_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_pppoe = nm_connection_get_setting_pppoe(connection); diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h index c597e052..013bc7fd 100644 --- a/src/core/devices/nm-device-private.h +++ b/src/core/devices/nm-device-private.h @@ -29,10 +29,6 @@ enum NMActStageReturn { #define NM_DEVICE_CAP_INTERNAL_MASK 0xc0000000 -NMSettings *nm_device_get_settings(NMDevice *self); - -NMManager *nm_device_get_manager(NMDevice *self); - gboolean nm_device_set_ip_ifindex(NMDevice *self, int ifindex); gboolean nm_device_set_ip_iface(NMDevice *self, const char *iface); @@ -180,4 +176,6 @@ void nm_device_auth_request(NMDevice *self, NMManagerDeviceAuthRequestFunc callback, gpointer user_data); +void nm_device_link_properties_set(NMDevice *self, gboolean reapply); + #endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c index cbea7d7d..430e62aa 100644 --- a/src/core/devices/nm-device-tun.c +++ b/src/core/devices/nm-device-tun.c @@ -284,7 +284,10 @@ _same_og(const char *str, gboolean og_valid, guint32 og_num) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceTun *self = NM_DEVICE_TUN(device); NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self); @@ -292,10 +295,10 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * NMSettingTun *s_tun; if (!NM_DEVICE_CLASS(nm_device_tun_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { switch (priv->props.type) { case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c index feb011db..7849e724 100644 --- a/src/core/devices/nm-device-vlan.c +++ b/src/core/devices/nm-device-vlan.c @@ -303,17 +303,20 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) /*****************************************************************************/ static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(device); NMSettingVlan *s_vlan; const char *parent; if (!NM_DEVICE_CLASS(nm_device_vlan_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { s_vlan = nm_connection_get_setting_vlan(connection); if (nm_setting_vlan_get_id(s_vlan) != priv->vlan_id) { diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c index ad31f3a2..a13de1cb 100644 --- a/src/core/devices/nm-device-vrf.c +++ b/src/core/devices/nm-device-vrf.c @@ -142,16 +142,19 @@ create_and_realize(NMDevice *device, } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(device); NMSettingVrf *s_vrf; if (!NM_DEVICE_CLASS(nm_device_vrf_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF); if (priv->props.table != nm_setting_vrf_get_table(s_vrf)) { @@ -238,8 +241,13 @@ attach_port(NMDevice *device, return TRUE; } -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) { NMDeviceVrf *self = NM_DEVICE_VRF(device); gboolean success; @@ -274,6 +282,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) _LOGI(LOGD_DEVICE, "VRF port %s was detached", nm_device_get_ip_iface(port)); } } + + return TRUE; } /*****************************************************************************/ diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c index 44a7be33..061ee3f2 100644 --- a/src/core/devices/nm-device-vxlan.c +++ b/src/core/devices/nm-device-vxlan.c @@ -238,17 +238,20 @@ address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device); NMSettingVxlan *s_vxlan; const char *parent; if (!NM_DEVICE_CLASS(nm_device_vxlan_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; - if (nm_device_is_real(device)) { + if (check_properties && nm_device_is_real(device)) { s_vxlan = nm_connection_get_setting_vxlan(connection); parent = nm_setting_vxlan_get_parent(s_vxlan); diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c index 98356ccf..282eea87 100644 --- a/src/core/devices/nm-device-wpan.c +++ b/src/core/devices/nm-device-wpan.c @@ -75,13 +75,16 @@ update_connection(NMDevice *device, NMConnection *connection) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingWpan *s_wpan; const char *mac, *hw_addr; if (!NM_DEVICE_CLASS(nm_device_wpan_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_wpan = NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN)); diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 62a9ff1e..2038e2f2 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -91,8 +91,8 @@ #define GRACE_PERIOD_MULTIPLIER 2U -#define CARRIER_WAIT_TIME_MS 6000 -#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000 +#define CARRIER_WAIT_TIME_MS 6000 +#define CARRIER_WAIT_TIME_AFTER_MTU_MSEC 10000 #define NM_DEVICE_AUTH_RETRIES_UNSET -1 #define NM_DEVICE_AUTH_RETRIES_INFINITY -2 @@ -132,11 +132,6 @@ typedef struct { } SlaveInfo; typedef struct { - NMDevice *device; - guint idle_add_id; -} DeleteOnDeactivateData; - -typedef struct { NMDevice *device; GCancellable *cancellable; NMPlatformAsyncCallback callback; @@ -335,7 +330,6 @@ enum { IP6_PREFIX_DELEGATED, IP6_SUBNET_NEEDED, REMOVED, - RECHECK_AUTO_ACTIVATE, RECHECK_ASSUME, DNS_LOOKUP_DONE, PLATFORM_ADDRESS_CHANGED, @@ -513,8 +507,8 @@ typedef struct _NMDevicePrivate { NMUnmanagedFlags unmanaged_mask; NMUnmanagedFlags unmanaged_flags; - DeleteOnDeactivateData - *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */ + + GSource *delete_on_deactivate_idle_source; GCancellable *deactivating_cancellable; @@ -542,10 +536,10 @@ typedef struct _NMDevicePrivate { /* Link stuff */ guint link_connected_id; guint link_disconnected_id; - guint carrier_defer_id; - guint carrier_wait_id; gulong config_changed_id; gulong ifindex_changed_id; + GSource *carrier_wait_source; + GSource *carrier_defer_source; guint32 mtu; guint32 ip6_mtu; /* FIXME(l3cfg) */ guint32 mtu_initial; @@ -559,9 +553,9 @@ typedef struct _NMDevicePrivate { * until taking action. * * When changing MTU, the device might take longer then that. So, whenever - * NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS + * NM changes the MTU it sets @carrier_wait_until_msec to CARRIER_WAIT_TIME_AFTER_MTU_MSEC * in the future. This is used to extend the grace period in this particular case. */ - gint64 carrier_wait_until_ms; + gint64 carrier_wait_until_msec; union { struct { @@ -576,6 +570,8 @@ typedef struct _NMDevicePrivate { NMDeviceSysIfaceState sys_iface_state_; }; + NMDeviceSysIfaceState sys_iface_state_before_sleep; + bool carrier : 1; bool ignore_carrier : 1; @@ -599,6 +595,8 @@ typedef struct _NMDevicePrivate { bool tc_committed : 1; + bool link_props_set : 1; + NMDeviceStageState stage1_sriov_state : 3; char *current_stable_id; @@ -702,6 +700,10 @@ typedef struct _NMDevicePrivate { GHashTable *ip6_saved_properties; EthtoolState *ethtool_state; + struct { + NMPlatformLinkProps props; + NMPlatformLinkChangeFlags flags; + } link_props_state; /* master interface for bridge/bond/team slave */ NMDevice *master; @@ -743,6 +745,9 @@ typedef struct _NMDevicePrivate { guint check_delete_unrealized_id; guint32 interface_flags; + guint32 port_detach_count; + NMDeviceStateReason port_detach_reason; + struct { SriovOp *pending; /* SR-IOV operation currently running */ SriovOp *next; /* next SR-IOV operation scheduled */ @@ -863,6 +868,7 @@ static void sriov_op_cb(GError *error, gpointer user_data); static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self); static gboolean device_link_changed(gpointer user_data); static gboolean _get_maybe_ipv6_disabled(NMDevice *self); +static void deactivate_ready(NMDevice *self, NMDeviceStateReason reason); /*****************************************************************************/ @@ -2750,6 +2756,152 @@ _ethtool_state_set(NMDevice *self) priv->ethtool_state = g_steal_pointer(ðtool_state); } +static NMPlatformLinkChangeFlags +link_properties_fill_from_setting(NMDevice *self, NMPlatformLinkProps *props) +{ + NMPlatformLinkChangeFlags flags = NM_PLATFORM_LINK_CHANGE_NONE; + NMSettingLink *s_link; + gint64 v; + + *props = (NMPlatformLinkProps){}; + + s_link = nm_device_get_applied_setting(self, NM_TYPE_SETTING_LINK); + if (!s_link) + return 0; + + v = nm_setting_link_get_tx_queue_length(s_link); + if (v != -1) { + props->tx_queue_length = (guint32) v; + flags |= NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH; + } + + v = nm_setting_link_get_gso_max_size(s_link); + if (v != -1) { + props->gso_max_size = (guint32) v; + flags |= NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE; + } + + v = nm_setting_link_get_gso_max_segments(s_link); + if (v != -1) { + props->gso_max_segments = (guint32) v; + flags |= NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS; + } + + v = nm_setting_link_get_gro_max_size(s_link); + if (v != -1) { + props->gro_max_size = (guint32) v; + flags |= NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE; + } + + return flags; +} + +void +nm_device_link_properties_set(NMDevice *self, gboolean reapply) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatformLinkProps props; + NMPlatformLinkChangeFlags flags; + NMPlatform *platform; + const NMPlatformLink *plink; + int ifindex; + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + return; + + if (priv->link_props_set && !reapply) + return; + + priv->link_props_set = TRUE; + + flags = link_properties_fill_from_setting(self, &props); + + if (flags == NM_PLATFORM_LINK_CHANGE_NONE + && priv->link_props_state.flags == NM_PLATFORM_LINK_CHANGE_NONE) { + /* Nothing to set now, and nothing was set previously. */ + return; + } + + platform = nm_device_get_platform(self); + + if (priv->link_props_state.flags == NM_PLATFORM_LINK_CHANGE_NONE) { + /* It's the first time we reach here. Try to fetch the current + * link settings (reset them later). */ + plink = nm_platform_link_get(platform, ifindex); + if (plink) { + priv->link_props_state.props = plink->link_props; + priv->link_props_state.flags = flags; + } else { + /* Unknown properties. The "priv->link_props_state.flags" stays unset. + * It indicates that "priv->link_props_state.props" is unknown. */ + } + + } else { + /* From a previous call we have some "priv->link_props_state.flags" + * flags, which indicates that all link props are cached. Also add + * "flags" which are are going to set, to indicate that those flags + * will need to be reset later. */ + priv->link_props_state.flags |= flags; + } + +#define _RESET(_f, _field) \ + if (!NM_FLAGS_HAS(flags, (_f)) && NM_FLAGS_HAS(priv->link_props_state.flags, (_f))) { \ + props._field = priv->link_props_state.props._field; \ + priv->link_props_state.flags &= ~(_f); \ + flags |= (_f); \ + } + + /* During reapply, if we previously set some "priv->link_props_state.flags" + * but now not anymore (according to "flags"), then we reset the value now. + * + * We do this by copying the props field from "priv->link_props_state" to + * "props", reset the flag in "priv->link_props_state.flags" and set the + * flag in "flags" (for changing it). */ + _RESET(NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH, tx_queue_length); + _RESET(NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE, gso_max_size); + _RESET(NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS, gso_max_segments); + _RESET(NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE, gro_max_size); + + if (nm_platform_link_change(platform, ifindex, &props, NULL, flags)) { + _LOGD(LOGD_DEVICE, "link properties successfully set"); + } else { + _LOGW(LOGD_DEVICE, "failure setting link properties"); + } +} + +static void +link_properties_reset(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform *platform; + int ifindex; + + if (priv->link_props_state.flags == 0) + goto out; + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + goto out; + + platform = nm_device_get_platform(self); + nm_assert(platform); + + if (nm_platform_link_change(platform, + ifindex, + &priv->link_props_state.props, + NULL, + priv->link_props_state.flags)) { + _LOGD(LOGD_DEVICE, "link properties successfully reset"); + } else { + _LOGW(LOGD_DEVICE, "failure resetting link properties"); + } + +out: + priv->link_props_set = FALSE; + priv->link_props_state.flags = 0; +} + /*****************************************************************************/ gboolean @@ -2892,6 +3044,7 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st nm_device_sys_iface_state_to_string(sys_iface_state)); priv->sys_iface_state_ = sys_iface_state; _dev_l3_cfg_commit_type_reset(self); + nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, FALSE); } /* this function only sets a flag, no immediate actions are initiated. @@ -2901,6 +3054,22 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st nm_assert(priv->sys_iface_state == sys_iface_state); } +void +nm_device_notify_sleeping(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->sys_iface_state_before_sleep = priv->sys_iface_state; +} + +NMDeviceSysIfaceState +nm_device_get_sys_iface_state_before_sleep(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + return priv->sys_iface_state_before_sleep; +} + static void _active_connection_set_state_flags_full(NMDevice *self, NMActivationStateFlags flags, @@ -3371,7 +3540,7 @@ _dev_ip_state_check(NMDevice *self, int addr_family) &s_is_pending, &s_is_failed); - has_tna = priv->l3cfg && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family); + has_tna = priv->l3cfg && nm_l3cfg_has_failedobj_pending(priv->l3cfg, addr_family); if (has_tna) s_is_pending = TRUE; @@ -3816,9 +3985,7 @@ after_merge_flags: } static gboolean -_dev_l3_register_l3cds_add_config(NMDevice *self, - L3ConfigDataType l3cd_type, - NML3CfgConfigFlags flags) +_dev_l3_register_l3cds_add_config(NMDevice *self, L3ConfigDataType l3cd_type) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); NML3ConfigMergeFlags merge_flags; @@ -3841,7 +4008,7 @@ _dev_l3_register_l3cds_add_config(NMDevice *self, _prop_get_ipvx_dns_priority(self, AF_INET6), acd_defend_type, acd_timeout_msec, - flags, + NM_L3CFG_CONFIG_FLAGS_NONE, merge_flags); } @@ -3849,7 +4016,6 @@ static gboolean _dev_l3_register_l3cds_set_one_full(NMDevice *self, L3ConfigDataType l3cd_type, const NML3ConfigData *l3cd, - NML3CfgConfigFlags flags, NMTernary commit_sync) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); @@ -3873,7 +4039,7 @@ _dev_l3_register_l3cds_set_one_full(NMDevice *self, if (priv->l3cfg) { if (priv->l3cds[l3cd_type].d) { - if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags)) + if (_dev_l3_register_l3cds_add_config(self, l3cd_type)) changed = TRUE; } @@ -3897,11 +4063,7 @@ _dev_l3_register_l3cds_set_one(NMDevice *self, const NML3ConfigData *l3cd, NMTernary commit_sync) { - return _dev_l3_register_l3cds_set_one_full(self, - l3cd_type, - l3cd, - NM_L3CFG_CONFIG_FLAGS_NONE, - commit_sync); + return _dev_l3_register_l3cds_set_one_full(self, l3cd_type, l3cd, commit_sync); } static void @@ -3956,7 +4118,7 @@ _dev_l3_register_l3cds(NMDevice *self, } if (is_external) continue; - if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE)) + if (_dev_l3_register_l3cds_add_config(self, i)) changed = TRUE; } @@ -4110,6 +4272,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N _dev_ipshared4_spawn_dnsmasq(self); nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd); } + _dev_ip_state_check_async(self, AF_UNSPEC); _dev_ipmanual_check_ready(self); return; case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT: @@ -4119,10 +4282,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N return; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: return; - case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED: - /* we commit again. This way we try to configure the routes.*/ - _dev_l3_cfg_commit(self, FALSE); - return; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK) @@ -4154,9 +4313,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N * synchronously to update the current state and schedule a commit. */ nm_ndisc_dad_failed(priv->ipac6_data.ndisc, conflicts, TRUE); } else if (ready) { - if (nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, AF_INET6)) - _dev_l3_cfg_commit(self, FALSE); - nm_clear_l3cd(&priv->ipac6_data.l3cd); _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY); _dev_ip_state_check_async(self, AF_INET6); @@ -4736,6 +4892,7 @@ nm_device_parent_find_for_connection(NMDevice *self, const char *current_setting && nm_device_check_connection_compatible( parent_device, nm_settings_connection_get_connection(parent_connection), + TRUE, NULL)) return current_setting_parent; } @@ -4959,6 +5116,18 @@ nm_device_get_ip_iface_identifier(NMDevice *self, } const char * +nm_device_get_s390_subchannels(NMDevice *self) +{ + NMDeviceClass *klass; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + klass = NM_DEVICE_GET_CLASS(self); + + return klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL; +} + +const char * nm_device_get_driver(NMDevice *self) { g_return_val_if_fail(self != NULL, NULL); @@ -5243,13 +5412,31 @@ nm_device_get_type_desc(NMDevice *self) } const char * +nm_device_get_type_desc_for_log(NMDevice *self) +{ + const char *type; + + type = nm_device_get_type_desc(self); + + /* Some OVS device types (ports and bridges) are not backed by a kernel link, and + * they can have the same name of another device of a different type. In fact, it's + * quite common to assign the same name to the OVS bridge, the OVS port and the OVS + * interface. For this reason, also log the type in case of OVS devices to make the + * log message unambiguous. */ + if (NM_STR_HAS_PREFIX(type, "Open vSwitch")) + return type; + + return NULL; +} + +const char * nm_device_get_type_description(NMDevice *self) { g_return_val_if_fail(self != NULL, NULL); /* Beware: this function should return the same - * value as nm_device_get_type_description() in libnm. */ - + * value as nm_device_get_type_description() in libnm. + * The returned string is static or interned */ return NM_DEVICE_GET_CLASS(self)->get_type_description(self); } @@ -6162,7 +6349,7 @@ attach_port_cb(NMDevice *self, GError *error, gpointer user_data) * nm_device_master_enslave_slave: * @self: the master device * @slave: the slave device to enslave - * @connection: (allow-none): the slave device's connection + * @connection: (nullable): the slave device's connection * * If @self is capable of enslaving other devices (ie it's a bridge, bond, team, * etc) then this function enslaves @slave. @@ -6206,6 +6393,21 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co attach_port_done(self, slave, success); } +static void +detach_port_cb(NMDevice *self, GError *error, gpointer user_data) +{ + nm_auto_unref_object NMDevice *slave = user_data; + NMDevicePrivate *slave_priv = NM_DEVICE_GET_PRIVATE(slave); + + nm_assert(slave_priv->port_detach_count > 0); + + if (--slave_priv->port_detach_count == 0) { + if (slave_priv->state == NM_DEVICE_STATE_DEACTIVATING) { + deactivate_ready(slave, slave_priv->port_detach_reason); + } + } +} + /** * nm_device_master_release_slave: * @self: the master device @@ -6262,10 +6464,20 @@ nm_device_master_release_slave(NMDevice *self, /* first, let subclasses handle the release ... */ if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave) - || release_type >= RELEASE_SLAVE_TYPE_CONFIG_FORCE) - NM_DEVICE_GET_CLASS(self)->detach_port(self, - slave, - release_type >= RELEASE_SLAVE_TYPE_CONFIG); + || release_type >= RELEASE_SLAVE_TYPE_CONFIG_FORCE) { + NMTernary ret; + + ret = NM_DEVICE_GET_CLASS(self)->detach_port(self, + slave, + release_type >= RELEASE_SLAVE_TYPE_CONFIG, + NULL, + detach_port_cb, + g_object_ref(slave)); + if (ret == NM_TERNARY_DEFAULT) { + slave_priv->port_detach_count++; + slave_priv->port_detach_reason = reason; + } + } /* raise notifications about the release, including clearing is_enslaved. */ nm_device_slave_notify_release(slave, reason, release_type); @@ -6344,13 +6556,6 @@ _dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, g } ext_flags = _dev_unmanaged_is_external_down(self, FALSE); - if (ext_flags != NM_UNMAN_FLAG_OP_SET_UNMANAGED) { - /* Ensure the assume check is queued before any queued state changes - * from the transition to UNAVAILABLE. - */ - nm_device_queue_recheck_assume(self); - } - if (now) { nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_EXTERNAL_DOWN, @@ -6434,6 +6639,8 @@ carrier_changed(NMDevice *self, gboolean carrier) } if (carrier) { + gboolean recheck_auto_activate = FALSE; + if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, @@ -6444,8 +6651,18 @@ carrier_changed(NMDevice *self, gboolean carrier) * when the carrier appears, auto connections are rechecked for * the device. */ - nm_device_emit_recheck_auto_activate(self); + recheck_auto_activate = TRUE; } + if (nm_manager_devcon_autoconnect_blocked_reason_set( + nm_device_get_manager(self), + self, + NULL, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, + FALSE)) + recheck_auto_activate = TRUE; + + if (recheck_auto_activate) + nm_device_recheck_auto_activate_schedule(self); } else { if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { if (priv->queued_state.id && priv->queued_state.state >= NM_DEVICE_STATE_DISCONNECTED) @@ -6464,24 +6681,20 @@ carrier_disconnected_action_cb(gpointer user_data) NMDevice *self = NM_DEVICE(user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - _LOGD(LOGD_DEVICE, - "carrier: link disconnected (calling deferred action) (id=%u)", - priv->carrier_defer_id); + _LOGD(LOGD_DEVICE, "carrier: link disconnected (calling deferred action)"); - priv->carrier_defer_id = 0; + nm_clear_g_source_inst(&priv->carrier_defer_source); carrier_changed(self, FALSE); - return FALSE; + return G_SOURCE_CONTINUE; } static void carrier_disconnected_action_cancel(NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - guint id = priv->carrier_defer_id; - if (nm_clear_g_source(&priv->carrier_defer_id)) { - _LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)", id); - } + if (nm_clear_g_source_inst(&priv->carrier_defer_source)) + _LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action)"); } void @@ -6509,28 +6722,29 @@ nm_device_set_carrier(NMDevice *self, gboolean carrier) NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier); carrier_changed(self, TRUE); - if (priv->carrier_wait_id) { + if (priv->carrier_wait_source) { nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); _carrier_wait_check_queued_act_request(self); } } else { - if (priv->carrier_wait_id) + if (priv->carrier_wait_source) nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier); if (state <= NM_DEVICE_STATE_DISCONNECTED && !priv->queued_act_request) { _LOGD(LOGD_DEVICE, "carrier: link disconnected"); + carrier_disconnected_action_cancel(self); carrier_changed(self, FALSE); - } else { - gint64 now_ms, until_ms; + } else if (!priv->carrier_defer_source) { + gint64 until_ms; + gint64 now_ms; now_ms = nm_utils_get_monotonic_timestamp_msec(); - until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms); - priv->carrier_defer_id = - g_timeout_add(until_ms - now_ms, carrier_disconnected_action_cb, self); + until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_msec); + priv->carrier_defer_source = + nm_g_timeout_add_source(until_ms - now_ms, carrier_disconnected_action_cb, self); _LOGD(LOGD_DEVICE, - "carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)", - (long) (until_ms - now_ms), - priv->carrier_defer_id); + "carrier: link disconnected (deferring action for %ld milliseconds)", + (long) (until_ms - now_ms)); } } } @@ -6682,6 +6896,37 @@ device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink) TRUE); } +/* + * Returns the reason for managing a device. The suffix "external" indicates + * that the reason mainly depends on whether we want to make the device + * sys-iface-state=external or not. + */ +NMDeviceStateReason +nm_device_get_manage_reason_external(NMDevice *self) +{ + NMDeviceStateReason reason; + + /* By default we return reason NOW_MANAGED, which makes the device fully + * managed by NM (sys-iface-state=managed). */ + reason = NM_DEVICE_STATE_REASON_NOW_MANAGED; + + /* If the device is an external-down candidate but no longer has the flag + * set, then the device is an externally created interface that previously + * had no addresses or no controller and now has. + * We need to set CONNECTION_ASSUMED as the reason, so that the device + * is managed but is not touched by NM (sys-iface-state=external). */ + if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN) + && !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) { + /* user-udev overwrites external-down, so we only assume the device + * when it is a external-down candidate which is not managed via udev. */ + if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) { + reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED; + } + } + + return reason; +} + static gboolean device_link_changed(gpointer user_data) { @@ -6768,7 +7013,7 @@ device_link_changed(gpointer user_data) /* Let any connections that use the new interface name have a chance * to auto-activate on the device. */ - nm_device_emit_recheck_auto_activate(self); + nm_device_recheck_auto_activate_schedule(self); } if (priv->ipac6_data.ndisc && pllink->inet6_token.id) { @@ -6793,35 +7038,13 @@ device_link_changed(gpointer user_data) priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP); if (pllink->initialized && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { - NMDeviceStateReason reason; - nm_device_set_unmanaged_by_user_udev(self); nm_device_set_unmanaged_by_user_conf(self); - reason = NM_DEVICE_STATE_REASON_NOW_MANAGED; - - /* If the device is a external-down candidated but no longer has external - * down set, we must clear the platform-unmanaged flag with reason - * "assumed". */ - if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN) - && !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) { - /* actually, user-udev overwrites external-down. So we only assume the device, - * when it is a external-down candidate, which is not managed via udev. */ - if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) { - /* Ensure the assume check is queued before any queued state changes - * from the transition to UNAVAILABLE. - */ - reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED; - } - } - - /* The assume check should happen before the device transitions to - * UNAVAILABLE, because in UNAVAILABLE we already clean up the IP - * configuration. Therefore, this function should never trigger a - * sync state transition. - */ - nm_device_queue_recheck_assume(self); - nm_device_set_unmanaged_by_flags_queue(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason); + nm_device_set_unmanaged_by_flags_queue(self, + NM_UNMANAGED_PLATFORM_INIT, + NM_UNMAN_FLAG_OP_SET_MANAGED, + nm_device_get_manage_reason_external(self)); } _dev_unmanaged_check_external_down(self, FALSE, FALSE); @@ -7309,14 +7532,15 @@ device_init_static_sriov_num_vfs(NMDevice *self) if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) { int num_vfs; - num_vfs = nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA, - NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, - self, - 10, - 0, - G_MAXINT32, - -1, - -1); + num_vfs = nm_config_data_get_device_config_int64_by_device( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, + self, + 10, + 0, + G_MAXINT32, + -1, + -1); if (num_vfs >= 0) sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL); } @@ -7332,7 +7556,7 @@ config_changed(NMConfig *config, NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state >= NM_DEVICE_STATE_ACTIVATED) { - priv->ignore_carrier = nm_config_data_get_ignore_carrier(config_data, self); + priv->ignore_carrier = nm_config_data_get_ignore_carrier_by_device(config_data, self); if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES) && !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV)) device_init_static_sriov_num_vfs(self); @@ -7471,8 +7695,9 @@ realize_start_setup(NMDevice *self, nm_device_update_permanent_hw_address(self, FALSE); /* Note: initial hardware address must be read before calling get_ignore_carrier() */ - config = nm_config_get(); - priv->ignore_carrier = nm_config_data_get_ignore_carrier(nm_config_get_data(config), self); + config = nm_config_get(); + priv->ignore_carrier = + nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(config), self); if (!priv->config_changed_id) { priv->config_changed_id = g_signal_connect(config, NM_CONFIG_SIGNAL_CONFIG_CHANGED, @@ -7701,6 +7926,10 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error) /* Garbage-collect unneeded unrealized devices. */ nm_device_recheck_available_connections(self); + /* In case the unrealized device is not going away, it may need to + * autoactivate. Schedule also a check for that. */ + nm_device_recheck_auto_activate_schedule(self); + return TRUE; } @@ -7721,7 +7950,7 @@ nm_device_notify_availability_maybe_changed(NMDevice *self) * available. */ nm_device_recheck_available_connections(self); if (g_hash_table_size(priv->available_connections) > 0) - nm_device_emit_recheck_auto_activate(self); + nm_device_recheck_auto_activate_schedule(self); } /** @@ -7880,7 +8109,7 @@ nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure) g_warn_if_fail(!NM_FLAGS_HAS(slave_priv->unmanaged_mask, NM_UNMANAGED_IS_SLAVE)); nm_device_set_unmanaged_by_flags(slave, NM_UNMANAGED_IS_SLAVE, - FALSE, + NM_UNMAN_FLAG_OP_SET_MANAGED, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); changed = TRUE; } else @@ -8331,7 +8560,7 @@ nm_device_autoconnect_allowed(NMDevice *self) return FALSE; } - if (priv->delete_on_deactivate_data) + if (priv->delete_on_deactivate_idle_source) return FALSE; /* The 'autoconnect-allowed' signal is emitted on a device to allow @@ -8450,7 +8679,7 @@ device_has_config(NMDevice *self) * @self: the master #NMDevice * @slave: the slave #NMDevice * @connection: the #NMConnection to update with the slave settings - * @GError: (out): error description + * @error: error description * * Reads the slave configuration for @slave and updates @connection with those * properties. This invokes a virtual function on the master device @self. @@ -8719,7 +8948,7 @@ nm_device_complete_connection(NMDevice *self, if (!nm_connection_normalize(connection, NULL, NULL, error)) return FALSE; - return nm_device_check_connection_compatible(self, connection, error); + return nm_device_check_connection_compatible(self, connection, TRUE, error); } gboolean @@ -8779,7 +9008,10 @@ nm_device_match_parent_hwaddr(NMDevice *device, } static gboolean -check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *self, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); const char *device_iface = nm_device_get_iface(self); @@ -8906,12 +9138,18 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e * @self. */ gboolean -nm_device_check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error) +nm_device_check_connection_compatible(NMDevice *self, + NMConnection *connection, + gboolean check_properties, + GError **error) { g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); - return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self, connection, error); + return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self, + connection, + check_properties, + error); } gboolean @@ -9077,9 +9315,9 @@ nm_device_queue_recheck_available(NMDevice *self, } void -nm_device_emit_recheck_auto_activate(NMDevice *self) +nm_device_recheck_auto_activate_schedule(NMDevice *self) { - g_signal_emit(self, signals[RECHECK_AUTO_ACTIVATE], 0); + nm_manager_device_recheck_auto_activate_schedule(nm_device_get_manager(self), self); } void @@ -9352,11 +9590,7 @@ sriov_params_cb(GError *error, gpointer user_data) if (!nm_platform_link_set_sriov_vfs(nm_device_get_platform(self), priv->ifindex, (const NMPlatformVF *const *) plat_vfs)) { - _LOGE(LOGD_DEVICE, "failed to apply SR-IOV VFs"); - nm_device_state_changed(self, - NM_DEVICE_STATE_FAILED, - NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED); - return; + _LOGW(LOGD_DEVICE, "failed to apply SR-IOV VF configurations"); } priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED; @@ -9757,10 +9991,12 @@ activate_stage2_device_config(NMDevice *self) nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); - if (!nm_device_sys_iface_state_is_external_or_assume(self)) + if (!nm_device_sys_iface_state_is_external(self)) { _ethtool_state_set(self); + nm_device_link_properties_set(self, FALSE); + } - if (!nm_device_sys_iface_state_is_external_or_assume(self)) { + if (!nm_device_sys_iface_state_is_external(self)) { if (!priv->tc_committed && !tc_commit(self)) { _LOGW(LOGD_DEVICE, "failed applying traffic control rules"); nm_device_state_changed(self, @@ -10161,14 +10397,6 @@ _dev_ipmanual_check_ready(NMDevice *self) _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED); _dev_ip_state_check_async(self, AF_UNSPEC); } else if (ready) { - if (priv->ipmanual_data.state_x[IS_IPv4] != NM_DEVICE_IP_STATE_READY - && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family)) { - /* Addresses with pending ACD/DAD are a possible cause for the - * presence of temporarily-not-available objects. Once all addresses - * are ready, retry to commit those unavailable objects. */ - _dev_l3_cfg_commit(self, FALSE); - } - _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY); _dev_ip_state_check_async(self, AF_UNSPEC); } @@ -10334,7 +10562,6 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), notify_data->lease_update.l3cd, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, FALSE); if (notify_data->lease_update.accepted) { @@ -10471,6 +10698,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) .addr_family = AF_INET, .l3cfg = nm_device_get_l3cfg(self), .iface = nm_device_get_ip_iface(self), + .iface_type_log = nm_device_get_type_desc_for_log(self), .uuid = nm_connection_get_uuid(connection), .hwaddr = hwaddr, .bcast_hwaddr = bcast_hwaddr, @@ -10499,6 +10727,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) gboolean iaid_explicit; guint32 iaid; NMDhcpClientConfig config; + const char *pd_hint; iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_explicit); duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid); @@ -10507,6 +10736,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) .addr_family = AF_INET6, .l3cfg = nm_device_get_l3cfg(self), .iface = nm_device_get_ip_iface(self), + .iface_type_log = nm_device_get_type_desc_for_log(self), .uuid = nm_connection_get_uuid(connection), .send_hostname = nm_setting_ip_config_get_dhcp_send_hostname(s_ip), .hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip), @@ -10525,6 +10755,21 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) }, }; + pd_hint = nm_setting_ip6_config_get_dhcp_pd_hint(NM_SETTING_IP6_CONFIG(s_ip)); + if (pd_hint) { + int pd_hint_length; + gboolean res; + + res = nm_inet_parse_with_prefix_bin(AF_INET6, + pd_hint, + NULL, + &config.v6.pd_hint_addr, + &pd_hint_length); + nm_assert(res); + nm_assert(pd_hint_length > 0 && pd_hint_length <= 128); + config.v6.pd_hint_length = pd_hint_length; + } + priv->ipdhcp_data_6.client = nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error); } @@ -10555,7 +10800,6 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), previous_lease, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, FALSE); } @@ -10680,10 +10924,13 @@ connection_ip_method_requires_carrier(NMConnection *connection, static gboolean connection_requires_carrier(NMConnection *connection) { - NMSettingIPConfig *s_ip4, *s_ip6; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; NMSettingConnection *s_con; - gboolean ip4_carrier_wanted, ip6_carrier_wanted; - gboolean ip4_used = FALSE, ip6_used = FALSE; + gboolean ip4_carrier_wanted; + gboolean ip6_carrier_wanted; + gboolean ip4_used = FALSE; + gboolean ip6_used = FALSE; /* We can progress to IP_CONFIG now, so that we're enslaved. * That may actually cause carrier to go up and thus continue activation. */ @@ -11196,10 +11443,8 @@ _commit_mtu(NMDevice *self) if (ifindex <= 0) return; - if (!nm_device_get_applied_connection(self) - || nm_device_sys_iface_state_is_external_or_assume(self)) { - /* we don't tamper with the MTU of disconnected and - * external/assumed devices. */ + if (!nm_device_get_applied_connection(self) || nm_device_sys_iface_state_is_external(self)) { + /* we don't tamper with the MTU of disconnected and external devices. */ return; } @@ -11378,8 +11623,8 @@ _commit_mtu(NMDevice *self) ? "Are the MTU sizes of the slaves large enough?" : "Did you configure the MTU correctly?")); } - priv->carrier_wait_until_ms = - nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + priv->carrier_wait_until_msec = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC; } if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS()) { @@ -11408,8 +11653,8 @@ _commit_mtu(NMDevice *self) msg ? ": " : "", msg ?: ""); } - priv->carrier_wait_until_ms = - nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + priv->carrier_wait_until_msec = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC; } } @@ -11488,11 +11733,7 @@ _dev_ipac6_ndisc_config_changed(NMNDisc *ndisc, _dev_ipac6_grace_period_start(self, 0, TRUE); - _dev_l3_register_l3cds_set_one_full(self, - L3_CONFIG_DATA_TYPE_AC_6, - l3cd, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, - FALSE); + _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE); nm_clear_l3cd(&priv->ipac6_data.l3cd); ready = nm_l3cfg_check_ready(priv->l3cfg, @@ -11682,7 +11923,11 @@ _dev_ipac6_start(NMDevice *self) } if (nm_device_get_ip_iface_identifier(self, &iid, FALSE, &is_token)) { - _LOGD_ipac6("using the device EUI-64 identifier"); + char buf[INET6_ADDRSTRLEN]; + + _LOGD_ipac6("using the device EUI-64 identifier %s (from %s)", + nm_utils_inet6_interface_identifier_to_token(&iid, buf), + is_token ? "token" : "address"); nm_ndisc_set_iid(priv->ipac6_data.ndisc, iid, is_token); } else { /* Don't abort the addrconf at this point -- if ndisc needs the iid @@ -12610,24 +12855,24 @@ nm_device_is_nm_owned(NMDevice *self) static gboolean delete_on_deactivate_link_delete(gpointer user_data) { - DeleteOnDeactivateData *data = user_data; - nm_auto_unref_object NMDevice *self = data->device; + nm_auto_unref_object NMDevice *self = user_data; NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); gs_free_error GError *error = NULL; - _LOGD(LOGD_DEVICE, - "delete_on_deactivate: cleanup and delete virtual link (id=%u)", - data->idle_add_id); + _LOGD(LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link"); - priv->delete_on_deactivate_data = NULL; + nm_clear_g_source_inst(&priv->delete_on_deactivate_idle_source); if (!nm_device_unrealize(self, TRUE, &error)) _LOGD(LOGD_DEVICE, "delete_on_deactivate: unrealizing failed (%s)", error->message); - nm_device_emit_recheck_auto_activate(self); + if (nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))) { + /* The device is still alive. We may need to autoactivate virtual + * devices again. */ + nm_device_recheck_auto_activate_schedule(self); + } - g_free(data); - return FALSE; + return G_SOURCE_CONTINUE; } static void @@ -12635,25 +12880,16 @@ delete_on_deactivate_unschedule(NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - if (priv->delete_on_deactivate_data) { - DeleteOnDeactivateData *data = priv->delete_on_deactivate_data; - - priv->delete_on_deactivate_data = NULL; - - g_source_remove(data->idle_add_id); - _LOGD(LOGD_DEVICE, - "delete_on_deactivate: cancel cleanup and delete virtual link (id=%u)", - data->idle_add_id); - g_object_unref(data->device); - g_free(data); + if (nm_clear_g_source_inst(&priv->delete_on_deactivate_idle_source)) { + _LOGD(LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link"); + g_object_unref(self); } } static void delete_on_deactivate_check_and_schedule(NMDevice *self) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - DeleteOnDeactivateData *data; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); if (!priv->nm_owned) return; @@ -12663,18 +12899,13 @@ delete_on_deactivate_check_and_schedule(NMDevice *self) return; if (nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED) return; - if (nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE) - return; - delete_on_deactivate_unschedule(self); /* always cancel and reschedule */ - data = g_new(DeleteOnDeactivateData, 1); - data->device = g_object_ref(self); - data->idle_add_id = g_idle_add(delete_on_deactivate_link_delete, data); - priv->delete_on_deactivate_data = data; + g_object_ref(self); + delete_on_deactivate_unschedule(self); /* always cancel and reschedule */ + priv->delete_on_deactivate_idle_source = + nm_g_idle_add_source(delete_on_deactivate_link_delete, self); - _LOGD(LOGD_DEVICE, - "delete_on_deactivate: schedule cleanup and delete virtual link (id=%u)", - data->idle_add_id); + _LOGD(LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link"); } static void @@ -12835,7 +13066,8 @@ can_reapply_change(NMDevice *self, NM_SETTING_USER_SETTING_NAME, NM_SETTING_PROXY_SETTING_NAME, NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP6_CONFIG_SETTING_NAME)) + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_LINK_SETTING_NAME)) return TRUE; if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) { @@ -12884,7 +13116,7 @@ reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new) * Change configuration of an already configured device if possible. * Updates the device's applied connection upon success. * - * Return: %FALSE if the new configuration can not be reapplied. + * Returns: %FALSE if the new configuration can not be reapplied. */ static gboolean check_and_reapply_connection(NMDevice *self, @@ -13032,6 +13264,8 @@ check_and_reapply_connection(NMDevice *self, *************************************************************************/ klass->reapply_connection(self, con_old, con_new); + nm_device_link_properties_set(self, TRUE); + if (priv->state >= NM_DEVICE_STATE_CONFIG) lldp_setup(self, NM_TERNARY_DEFAULT); @@ -13079,7 +13313,7 @@ check_and_reapply_connection(NMDevice *self, if (sett_conn) { nm_settings_connection_autoconnect_blocked_reason_set( sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, FALSE); } @@ -13414,7 +13648,8 @@ delete_cb(NMDevice *self, GError *error, gpointer user_data) { - GError *local = NULL; + NMSettingsConnection *sett_conn; + GError *local = NULL; if (error) { g_dbus_method_invocation_return_gerror(context, error); @@ -13429,10 +13664,26 @@ delete_cb(NMDevice *self, /* Authorized */ nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE, self, TRUE, NULL, subject, NULL); - if (nm_device_unrealize(self, TRUE, &local)) - g_dbus_method_invocation_return_value(context, NULL); - else + + sett_conn = nm_device_get_settings_connection(self); + if (sett_conn) { + /* Block profile from autoconnecting. We block the profile, which may + * be ugly/wrong with multi-connect profiles. However, it's not + * obviously wrong, because profiles for software devices tend not to + * work with multi-connect anyway, because they describe a (unique) + * interface by name. */ + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, + TRUE); + } + + if (!nm_device_unrealize(self, TRUE, &local)) { g_dbus_method_invocation_take_error(context, local); + return; + } + + g_dbus_method_invocation_return_value(context, NULL); } static void @@ -13539,7 +13790,7 @@ _carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req) * request is not blocked waiting for carrier. */ if (priv->carrier) return FALSE; - if (priv->carrier_wait_id == 0) + if (!priv->carrier_wait_source) return FALSE; connection = nm_act_request_get_applied_connection(req); @@ -14053,11 +14304,11 @@ carrier_wait_timeout(gpointer user_data) NMDevice *self = NM_DEVICE(user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - priv->carrier_wait_id = 0; + nm_clear_g_source_inst(&priv->carrier_wait_source); nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); if (!priv->carrier) _carrier_wait_check_queued_act_request(self); - return G_SOURCE_REMOVE; + return G_SOURCE_CONTINUE; } static gboolean @@ -14074,14 +14325,15 @@ nm_device_is_up(NMDevice *self) static gint64 _get_carrier_wait_ms(NMDevice *self) { - return nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA, - NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, - self, - 10, - 0, - G_MAXINT32, - CARRIER_WAIT_TIME_MS, - CARRIER_WAIT_TIME_MS); + return nm_config_data_get_device_config_int64_by_device( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, + self, + 10, + 0, + G_MAXINT32, + CARRIER_WAIT_TIME_MS, + CARRIER_WAIT_TIME_MS); } /* @@ -14104,13 +14356,14 @@ carrier_detect_wait(NMDevice *self) * * If during that time carrier goes away, we declare the interface * as not ready. */ - nm_clear_g_source(&priv->carrier_wait_id); + nm_clear_g_source_inst(&priv->carrier_wait_source); if (!priv->carrier) nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); now_ms = nm_utils_get_monotonic_timestamp_msec(); - until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms); - priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self); + until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_msec); + priv->carrier_wait_source = + nm_g_timeout_add_source(until_ms - now_ms, carrier_wait_timeout, self); } gboolean @@ -14563,7 +14816,15 @@ _set_unmanaged_flags(NMDevice *self, new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE; if (new_state == NM_DEVICE_STATE_UNMANAGED) { _cancel_activation(self); + } else { + /* The assume check should happen before the device transitions to + * UNAVAILABLE, because in UNAVAILABLE we already clean up the IP + * configuration. Therefore, this function should never trigger a + * sync state transition. + */ + nm_device_queue_recheck_assume(self); } + if (now) nm_device_state_changed(self, new_state, reason); else @@ -14629,11 +14890,11 @@ nm_device_check_unrealized_device_managed(NMDevice *self) nm_assert(!nm_device_is_real(self)); - if (!nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA, - NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, - self, - TRUE, - TRUE)) + if (!nm_config_data_get_device_config_boolean_by_device(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + self, + TRUE, + TRUE)) return FALSE; if (nm_device_spec_match_list(self, nm_settings_get_unmanaged_specs(priv->settings))) @@ -14700,11 +14961,11 @@ nm_device_set_unmanaged_by_user_conf(NMDevice *self) gboolean value; NMUnmanFlagOp set_op; - value = nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA, - NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, - self, - -1, - TRUE); + value = nm_config_data_get_device_config_boolean_by_device(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + self, + -1, + TRUE); switch (value) { case TRUE: set_op = NM_UNMAN_FLAG_OP_SET_MANAGED; @@ -14739,7 +15000,7 @@ nm_device_set_unmanaged_by_quitting(NMDevice *self) nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_QUITTING, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, need_deactivate ? NM_DEVICE_STATE_REASON_REMOVED : NM_DEVICE_STATE_REASON_NOW_UNMANAGED); } @@ -14918,7 +15179,10 @@ _nm_device_check_connection_available(NMDevice *self, /* an unrealized software device is always available, hardware devices never. */ if (!nm_device_is_real(self)) { if (nm_device_is_software(self)) { - if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) { + if (!nm_device_check_connection_compatible(self, + connection, + TRUE, + error ? &local : NULL)) { if (error) { g_return_val_if_fail(local, FALSE); nm_utils_error_set(error, @@ -14982,7 +15246,7 @@ _nm_device_check_connection_available(NMDevice *self, } } - if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) { + if (!nm_device_check_connection_compatible(self, connection, TRUE, error ? &local : NULL)) { if (error) { nm_utils_error_set(error, local->domain == NM_UTILS_ERROR ? local->code @@ -15084,14 +15348,11 @@ check_connection_available(NMDevice *self, { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - /* Connections which require a network connection are not available when - * the device has no carrier, even with ignore-carrer=TRUE. - */ - if (priv->carrier || !connection_requires_carrier(connection)) + if (priv->carrier) return TRUE; if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER) - && priv->carrier_wait_id != 0) { + && priv->carrier_wait_source) { /* The device has no carrier though the connection requires it. * * If we are still waiting for carrier, the connection is available @@ -15099,12 +15360,6 @@ check_connection_available(NMDevice *self, return TRUE; } - /* master types are always available even without carrier. - * Making connection non-available would un-enslave slaves which - * is not desired. */ - if (nm_device_is_master(self)) - return TRUE; - if (!priv->up) { /* If the device is !IFF_UP it also has no carrier. But we assume that if we * would start activating the device (and thereby set the device IFF_UP), @@ -15114,6 +15369,18 @@ check_connection_available(NMDevice *self, return TRUE; } + if (!connection_requires_carrier(connection)) { + /* Connections that don't require carrier are available. */ + return TRUE; + } + + if (nm_device_is_master(self)) { + /* master types are always available even without carrier. + * Making connection non-available would un-enslave slaves which + * is not desired. */ + return TRUE; + } + nm_utils_error_set_literal(error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device has no carrier"); @@ -15479,7 +15746,7 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type) } static void -_cleanup_generic_post(NMDevice *self, CleanupType cleanup_type) +_cleanup_generic_post(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanup_type) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); @@ -15502,7 +15769,11 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type) act_request_set(self, NULL); } - if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) { + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE + && ((reason == NM_DEVICE_STATE_REASON_CARRIER && nm_device_is_master(self)) + || !NM_IN_SET(reason, + NM_DEVICE_STATE_REASON_NOW_MANAGED, + NM_DEVICE_STATE_REASON_CARRIER))) { /* Check if the device was deactivated, and if so, delete_link. * Don't call delete_link synchronously because we are currently * handling a state change -- which is not reentrant. */ @@ -15623,8 +15894,8 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu ifindex); if (priv->mtu_initial) { nm_platform_link_set_mtu(nm_device_get_platform(self), ifindex, priv->mtu_initial); - priv->carrier_wait_until_ms = - nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + priv->carrier_wait_until_msec = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC; } if (priv->ip6_mtu_initial) { char sbuf[64]; @@ -15641,6 +15912,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu } _ethtool_state_reset(self); + link_properties_reset(self); if (priv->promisc_reset != NM_OPTION_BOOL_DEFAULT && ifindex > 0) { nm_platform_link_change_flags(nm_device_get_platform(self), @@ -15650,7 +15922,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu priv->promisc_reset = NM_OPTION_BOOL_DEFAULT; } - _cleanup_generic_post(self, cleanup_type); + _cleanup_generic_post(self, reason, cleanup_type); } static void @@ -15675,6 +15947,9 @@ deactivate_ready(NMDevice *self, NMDeviceStateReason reason) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->port_detach_count > 0) + return; + if (priv->dispatcher.call_id) return; @@ -15966,8 +16241,11 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, * userspace IPv6LL enabled. */ _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE); + if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_REMOVED) { + nm_device_cleanup(self, reason, CLEANUP_TYPE_REMOVED); + } else + nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); - nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); } else if (old_state < NM_DEVICE_STATE_DISCONNECTED) { if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) { /* Ensure IPv6 is set up as it may not have been done when @@ -16036,7 +16314,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, /* We cache the ignore_carrier state to not react on config-reloads while the connection * is active. But on deactivating, reset the ignore-carrier flag to the current state. */ - priv->ignore_carrier = nm_config_data_get_ignore_carrier(NM_CONFIG_GET_DATA, self); + priv->ignore_carrier = + nm_config_data_get_ignore_carrier_by_device(NM_CONFIG_GET_DATA, self); if (quitting) { nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_PRE_DOWN, self, req); @@ -16743,7 +17022,7 @@ nm_device_hw_addr_set(NMDevice *self, const char *addr, const char *detail, gboo * @hwaddr: (out): the cloned MAC address to set on interface * @hwaddr_type: (out): the type of address to set * @hwaddr_detail: (out): the detail (origin) of address to set - * @error: (out): on return, an error or %NULL + * @error: on return, an error or %NULL * * Computes the MAC to be set on a interface. On success, one of the * following exclusive conditions are verified: @@ -16940,6 +17219,7 @@ nm_device_hw_addr_reset(NMDevice *self, const char *detail) { NMDevicePrivate *priv; const char *addr; + int ifindex; g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); @@ -16949,7 +17229,13 @@ nm_device_hw_addr_reset(NMDevice *self, const char *detail) return TRUE; priv->hw_addr_type = HW_ADDR_TYPE_UNSET; - addr = nm_device_get_initial_hw_address(self); + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) { + return TRUE; + } + + addr = nm_device_get_initial_hw_address(self); if (!addr) { /* as hw_addr_type is not UNSET, we expect that we can get an * initial address to which to reset. */ @@ -17028,38 +17314,11 @@ nm_device_spec_match_list(NMDevice *self, const GSList *specs) int nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value) { - NMDeviceClass *klass; - NMMatchSpecMatchType m; - const char *hw_address = NULL; - gboolean is_fake; - - g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + NMMatchSpecDeviceData data; + NMMatchSpecMatchType m; - klass = NM_DEVICE_GET_CLASS(self); - hw_address = nm_device_get_permanent_hw_address_full( - self, - !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT), - &is_fake); - - m = nm_match_spec_device(specs, - nm_device_get_iface(self), - nm_device_get_type_description(self), - nm_device_get_driver(self), - nm_device_get_driver_version(self), - is_fake ? NULL : hw_address, - klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL, - nm_dhcp_manager_get_config(nm_dhcp_manager_get())); - - switch (m) { - case NM_MATCH_SPEC_MATCH: - return TRUE; - case NM_MATCH_SPEC_NEG_MATCH: - return FALSE; - case NM_MATCH_SPEC_NO_MATCH: - return no_match_value; - } - nm_assert_not_reached(); - return no_match_value; + m = nm_match_spec_device(specs, nm_match_spec_device_data_init_from_device(&data, self)); + return nm_match_spec_match_type_to_bool(m, no_match_value); } guint @@ -17747,6 +18006,8 @@ nm_device_init(NMDevice *self) c_list_init(&priv->concheck_lst_head); c_list_init(&self->devices_lst); + c_list_init(&self->devcon_dev_lst_head); + c_list_init(&self->policy_auto_activate_lst); c_list_init(&priv->slaves); priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_NONE; @@ -17773,7 +18034,11 @@ nm_device_init(NMDevice *self) priv->unmanaged_mask = priv->unmanaged_flags; priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); priv->ip6_saved_properties = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); - priv->sys_iface_state_ = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL; + + priv->sys_iface_state_ = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL; + /* If networking is already disabled at boot, we want to manage all devices + * after re-enabling networking; hence, the initial state is MANAGED. */ + priv->sys_iface_state_before_sleep = NM_DEVICE_SYS_IFACE_STATE_MANAGED; priv->promisc_reset = NM_OPTION_BOOL_DEFAULT; } @@ -17867,6 +18132,9 @@ dispose(GObject *object) _LOGD(LOGD_DEVICE, "disposing"); nm_assert(c_list_is_empty(&self->devices_lst)); + nm_assert(c_list_is_empty(&self->devcon_dev_lst_head)); + nm_assert(c_list_is_empty(&self->policy_auto_activate_lst)); + nm_assert(!self->policy_auto_activate_idle_source); while ((con_handle = c_list_first_entry(&priv->concheck_lst_head, NMDeviceConnectivityHandle, @@ -17899,7 +18167,7 @@ dispose(GObject *object) /* Let the kernel manage IPv6LL again */ _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64); - _cleanup_generic_post(self, CLEANUP_TYPE_KEEP); + _cleanup_generic_post(self, NM_DEVICE_STATE_REASON_NONE, CLEANUP_TYPE_KEEP); nm_assert(priv->master_ready_id == 0); @@ -17925,7 +18193,7 @@ dispose(GObject *object) available_connections_del_all(self); - if (nm_clear_g_source(&priv->carrier_wait_id)) + if (nm_clear_g_source_inst(&priv->carrier_wait_source)) nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); @@ -18529,16 +18797,6 @@ nm_device_class_init(NMDeviceClass *klass) G_TYPE_NONE, 0); - signals[RECHECK_AUTO_ACTIVATE] = g_signal_new(NM_DEVICE_RECHECK_AUTO_ACTIVATE, - G_OBJECT_CLASS_TYPE(object_class), - G_SIGNAL_RUN_FIRST, - 0, - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0); - signals[RECHECK_ASSUME] = g_signal_new(NM_DEVICE_RECHECK_ASSUME, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h index bcf4d7b9..b096d23a 100644 --- a/src/core/devices/nm-device.h +++ b/src/core/devices/nm-device.h @@ -75,7 +75,6 @@ #define NM_DEVICE_IP6_PREFIX_DELEGATED "ip6-prefix-delegated" #define NM_DEVICE_IP6_SUBNET_NEEDED "ip6-subnet-needed" #define NM_DEVICE_REMOVED "removed" -#define NM_DEVICE_RECHECK_AUTO_ACTIVATE "recheck-auto-activate" #define NM_DEVICE_RECHECK_ASSUME "recheck-assume" #define NM_DEVICE_STATE_CHANGED "state-changed" #define NM_DEVICE_LINK_INITIALIZED "link-initialized" @@ -144,6 +143,10 @@ struct _NMDevice { NMDBusObject parent; struct _NMDevicePrivate *_priv; CList devices_lst; + CList devcon_dev_lst_head; + + CList policy_auto_activate_lst; + GSource *policy_auto_activate_idle_source; }; /* The flags have an relaxing meaning, that means, specifying more flags, can make @@ -294,7 +297,7 @@ typedef struct _NMDeviceClass { GPtrArray *(*get_extra_rules)(NMDevice *self); /* allow derived classes to override the result of nm_device_autoconnect_allowed(). - * If the value changes, the class should call nm_device_emit_recheck_auto_activate(), + * If the value changes, the class should call nm_device_recheck_auto_activate_schedule(), * which emits NM_DEVICE_RECHECK_AUTO_ACTIVATE signal. */ gboolean (*get_autoconnect_allowed)(NMDevice *self); @@ -321,6 +324,7 @@ typedef struct _NMDeviceClass { */ gboolean (*check_connection_compatible)(NMDevice *self, NMConnection *connection, + gboolean check_properties, GError **error); /* Checks whether the connection is likely available to be activated, @@ -387,7 +391,15 @@ typedef struct _NMDeviceClass { GCancellable *cancellable, NMDeviceAttachPortCallback callback, gpointer user_data); - void (*detach_port)(NMDevice *self, NMDevice *port, gboolean configure); + /* This works similarly to attach_port(). However, current + * implementations don't report errors and so the only possible + * return values are TRUE and DEFAULT. */ + NMTernary (*detach_port)(NMDevice *self, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data); void (*parent_changed_notify)(NMDevice *self, int old_ifindex, @@ -421,6 +433,10 @@ typedef struct _NMDeviceClass { const char *(*get_dhcp_anycast_address)(NMDevice *self); } NMDeviceClass; +NMSettings *nm_device_get_settings(NMDevice *self); + +NMManager *nm_device_get_manager(NMDevice *self); + GType nm_device_get_type(void); struct _NMDedupMultiIndex *nm_device_get_multi_index(NMDevice *self); @@ -444,9 +460,11 @@ gboolean nm_device_is_real(NMDevice *dev); const char *nm_device_get_ip_iface(NMDevice *dev); const char *nm_device_get_ip_iface_from_platform(NMDevice *dev); int nm_device_get_ip_ifindex(const NMDevice *dev); +const char *nm_device_get_s390_subchannels(NMDevice *self); const char *nm_device_get_driver(NMDevice *dev); const char *nm_device_get_driver_version(NMDevice *dev); const char *nm_device_get_type_desc(NMDevice *dev); +const char *nm_device_get_type_desc_for_log(NMDevice *dev); const char *nm_device_get_type_description(NMDevice *dev); NMDeviceType nm_device_get_device_type(NMDevice *dev); NMLinkType nm_device_get_link_type(NMDevice *dev); @@ -526,8 +544,10 @@ gboolean nm_device_complete_connection(NMDevice *device, NMConnection *const *existing_connections, GError **error); -gboolean -nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error); +gboolean nm_device_check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error); gboolean nm_device_check_slave_connection_compatible(NMDevice *device, NMConnection *connection); @@ -608,9 +628,9 @@ typedef enum { } NMUnmanagedFlags; typedef enum { - NM_UNMAN_FLAG_OP_SET_MANAGED = FALSE, - NM_UNMAN_FLAG_OP_SET_UNMANAGED = TRUE, - NM_UNMAN_FLAG_OP_FORGET = 2, + NM_UNMAN_FLAG_OP_SET_MANAGED = 0, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, + NM_UNMAN_FLAG_OP_FORGET, } NMUnmanFlagOp; const char *nm_unmanaged_flags2str(NMUnmanagedFlags flags, char *buf, gsize len); @@ -631,6 +651,7 @@ void nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now); void nm_device_set_unmanaged_by_user_udev(NMDevice *self); void nm_device_set_unmanaged_by_user_conf(NMDevice *self); void nm_device_set_unmanaged_by_quitting(NMDevice *device); +NMDeviceStateReason nm_device_get_manage_reason_external(NMDevice *self); gboolean nm_device_check_unrealized_device_managed(NMDevice *self); @@ -701,7 +722,7 @@ nm_device_autoconnect_blocked_unset(NMDevice *device, NMDeviceAutoconnectBlocked nm_device_autoconnect_blocked_set_full(device, mask, NM_DEVICE_AUTOCONNECT_BLOCKED_NONE); } -void nm_device_emit_recheck_auto_activate(NMDevice *device); +void nm_device_recheck_auto_activate_schedule(NMDevice *device); NMDeviceSysIfaceState nm_device_sys_iface_state_get(NMDevice *device); @@ -710,6 +731,10 @@ gboolean nm_device_sys_iface_state_is_external_or_assume(NMDevice *self); void nm_device_sys_iface_state_set(NMDevice *device, NMDeviceSysIfaceState sys_iface_state); +void nm_device_notify_sleeping(NMDevice *self); + +NMDeviceSysIfaceState nm_device_get_sys_iface_state_before_sleep(NMDevice *self); + void nm_device_state_changed(NMDevice *device, NMDeviceState state, NMDeviceStateReason reason); void nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason); diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c index 7b319af3..ff1917b1 100644 --- a/src/core/devices/ovs/nm-device-ovs-bridge.c +++ b/src/core/devices/ovs/nm-device-ovs-bridge.c @@ -97,9 +97,16 @@ attach_port(NMDevice *device, return TRUE; } -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) -{} +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) +{ + return TRUE; +} void nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new) diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c index 711f65cb..fd48c2fd 100644 --- a/src/core/devices/ovs/nm-device-ovs-interface.c +++ b/src/core/devices/ovs/nm-device-ovs-interface.c @@ -91,12 +91,15 @@ can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **speci } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingOvsInterface *s_ovs_iface; if (!NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_ovs_iface = nm_connection_get_setting_ovs_interface(connection); @@ -132,6 +135,8 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink) nm_device_devip_set_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_CONFIG_FAILED); return; } + + nm_device_link_properties_set(device, FALSE); nm_device_bring_up(device); nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL); @@ -214,6 +219,7 @@ _set_ip_ifindex_tun(gpointer user_data) priv->wait_link_is_waiting = FALSE; nm_device_set_ip_ifindex(device, priv->wait_link_ifindex); + nm_device_link_properties_set(device, FALSE); nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL); nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL); @@ -303,6 +309,7 @@ act_stage3_ip_config(NMDevice *device, int addr_family) return; } + nm_device_link_properties_set(device, FALSE); nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL); } @@ -479,7 +486,7 @@ ovsdb_ready(NMOvsdb *ovsdb, NMDeviceOvsInterface *self) NM_DEVICE_STATE_REASON_NONE, NM_DEVICE_STATE_REASON_NONE); nm_device_recheck_available_connections(device); - nm_device_emit_recheck_auto_activate(device); + nm_device_recheck_auto_activate_schedule(device); } static void diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c index 5510e39f..5ede46e9 100644 --- a/src/core/devices/ovs/nm-device-ovs-port.c +++ b/src/core/devices/ovs/nm-device-ovs-port.c @@ -78,10 +78,11 @@ typedef struct { GCancellable *cancellable; NMDeviceAttachPortCallback callback; gpointer callback_user_data; + gboolean add; } AttachPortData; static void -add_iface_cb(GError *error, gpointer user_data) +add_del_iface_cb(GError *error, gpointer user_data) { AttachPortData *data = user_data; NMDeviceOvsPort *self; @@ -93,15 +94,17 @@ add_iface_cb(GError *error, gpointer user_data) } else if (error && !nm_utils_error_is_cancelled_or_disposing(error)) { self = NM_DEVICE_OVS_PORT(data->device); _LOGW(LOGD_DEVICE, - "device %s could not be added to a ovs port: %s", + "device %s could not be %s a ovs port: %s", nm_device_get_iface(data->port), + data->add ? "added to" : "removed from", error->message); nm_device_state_changed(data->port, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); } - data->callback(data->device, error, data->callback_user_data); + if (data->callback) + data->callback(data->device, error, data->callback_user_data); g_object_unref(data->device); g_object_unref(data->port); @@ -178,6 +181,7 @@ attach_port(NMDevice *device, .cancellable = g_object_ref(cancellable), .callback = callback, .callback_user_data = user_data, + .add = TRUE, }; nm_ovsdb_add_interface(nm_ovsdb_get(), @@ -186,7 +190,7 @@ attach_port(NMDevice *device, nm_device_get_applied_connection(port), bridge_device, port, - add_iface_cb, + add_del_iface_cb, data); /* DPDK ports does not have a link after the devbind, so the MTU must be @@ -205,29 +209,19 @@ attach_port(NMDevice *device, return NM_TERNARY_DEFAULT; } -static void -del_iface_cb(GError *error, gpointer user_data) -{ - NMDevice *slave = user_data; - - if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) { - nm_log_warn(LOGD_DEVICE, - "device %s could not be removed from a ovs port: %s", - nm_device_get_iface(slave), - error->message); - nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); - } - - g_object_unref(slave); -} - -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) { NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device); bool port_not_managed = !NM_IN_SET(nm_device_sys_iface_state_get(port), NM_DEVICE_SYS_IFACE_STATE_MANAGED, NM_DEVICE_SYS_IFACE_STATE_ASSUME); + NMTernary ret = TRUE; _LOGI(LOGD_DEVICE, "detaching ovs interface %s", nm_device_get_ip_iface(port)); @@ -236,10 +230,20 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) * to make sure its OVSDB entry is gone. */ if (configure || port_not_managed) { - nm_ovsdb_del_interface(nm_ovsdb_get(), - nm_device_get_iface(port), - del_iface_cb, - g_object_ref(port)); + AttachPortData *data; + + data = g_slice_new(AttachPortData); + *data = (AttachPortData){ + .device = g_object_ref(device), + .port = g_object_ref(port), + .cancellable = nm_g_object_ref(cancellable), + .callback = callback, + .callback_user_data = user_data, + .add = FALSE, + }; + + nm_ovsdb_del_interface(nm_ovsdb_get(), nm_device_get_iface(port), add_del_iface_cb, data); + ret = NM_TERNARY_DEFAULT; } if (configure) { @@ -248,6 +252,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) if (NM_IS_DEVICE_OVS_INTERFACE(port)) nm_device_update_from_platform_link(port, NULL); } + + return ret; } /*****************************************************************************/ diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c index 50023778..2ca1a0b5 100644 --- a/src/core/devices/ovs/nm-ovs-factory.c +++ b/src/core/devices/ovs/nm-ovs-factory.c @@ -240,9 +240,11 @@ ovsdb_interface_failed(NMOvsdb *ovsdb, return; if (connection) { - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + nm_device_get_manager(device), + device, connection, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, TRUE); } diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c index 85b7953f..68366f73 100644 --- a/src/core/devices/ovs/nm-ovsdb.c +++ b/src/core/devices/ovs/nm-ovsdb.c @@ -12,6 +12,7 @@ #include "libnm-glib-aux/nm-jansson.h" #include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "devices/nm-device.h" @@ -134,14 +135,18 @@ enum { static guint signals[LAST_SIGNAL] = {0}; typedef struct { - NMPlatform *platform; - GSocketConnection *conn; - GCancellable *conn_cancellable; - char buf[4096]; /* Input buffer */ - size_t bufp; /* Last decoded byte in the input buffer. */ - GString *input; /* JSON stream waiting for decoding. */ - GString *output; /* JSON stream to be sent. */ - guint64 call_id_counter; + NMPlatform *platform; + int conn_fd; + GSource *conn_fd_in_source; + GSource *conn_fd_out_source; + GCancellable *conn_cancellable; + + NMStrBuf input_buf; + NMStrBuf output_buf; + + GSource *input_timeout_source; + + guint64 call_id_counter; CList calls_lst_head; @@ -177,12 +182,13 @@ NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB); /*****************************************************************************/ -static void ovsdb_try_connect(NMOvsdb *self); -static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing); -static void ovsdb_read(NMOvsdb *self); -static void ovsdb_write(NMOvsdb *self); -static void ovsdb_next_command(NMOvsdb *self); -static void cleanup_check_ready(NMOvsdb *self); +static void ovsdb_try_connect(NMOvsdb *self); +static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing); +static void ovsdb_read(NMOvsdb *self); +static void ovsdb_write_try(NMOvsdb *self); +static gboolean ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data); +static void ovsdb_next_command(NMOvsdb *self); +static void cleanup_check_ready(NMOvsdb *self); /*****************************************************************************/ @@ -1446,10 +1452,10 @@ ovsdb_next_command(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); OvsdbMethodCall *call; - char *cmd; + nm_auto_free char *cmd = NULL; nm_auto_decref_json json_t *msg = NULL; - if (!priv->conn) + if (priv->conn_fd < 0) return; if (c_list_is_empty(&priv->calls_lst_head)) @@ -1586,10 +1592,9 @@ ovsdb_next_command(NMOvsdb *self) cmd = json_dumps(msg, 0); _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd); - g_string_append(priv->output, cmd); - free(cmd); + nm_str_buf_append(&priv->output_buf, cmd); - ovsdb_write(self); + ovsdb_write_try(self); } /** @@ -2188,20 +2193,18 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg) static void ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data) { - NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - nm_auto_decref_json json_t *msg = NULL; - char *reply; - gboolean output_was_empty; - - output_was_empty = priv->output->len == 0; + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + nm_auto_decref_json json_t *msg = NULL; + nm_auto_free char *reply = NULL; msg = json_pack("{s:I, s:O}", "id", id, "result", data); reply = json_dumps(msg, 0); - g_string_append(priv->output, reply); - free(reply); - if (output_was_empty) - ovsdb_write(self); + _LOGT("send: echo: %s", reply); + + nm_str_buf_append(&priv->output_buf, reply); + + ovsdb_write_try(self); } /** @@ -2274,13 +2277,13 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg) /* This is a response to a method call. */ if (c_list_is_empty(&priv->calls_lst_head)) { - _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id); + _LOGW("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id); ovsdb_disconnect(self, FALSE, FALSE); return; } call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); if (call->call_id != id) { - _LOGE("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, + _LOGW("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->call_id, (guint64) id); ovsdb_disconnect(self, FALSE, FALSE); @@ -2305,7 +2308,7 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg) /* Don't progress further commands in case the callback hit an error * and disconnected us. */ - if (!priv->conn) + if (priv->conn_fd < 0) return; /* Now we're free to serialize and send the next command, if any. */ @@ -2320,138 +2323,197 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg) /*****************************************************************************/ +typedef struct { + gsize bufp; + NMStrBuf *input; +} JsonReadMsgData; + /* Lower level marshalling and demarshalling of the JSON-RPC traffic on the * ovsdb socket. */ static size_t -_json_callback(void *buffer, size_t buflen, void *user_data) +_json_read_msg_cb(void *buffer, size_t buflen, void *user_data) { - NMOvsdb *self = NM_OVSDB(user_data); - NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + JsonReadMsgData *data = user_data; + + nm_assert(buffer); + nm_assert(buflen > 0); - if (priv->bufp == priv->input->len) { + if (data->bufp == data->input->len) { /* No more bytes buffered for decoding. */ return 0; } /* Pass one more byte to the JSON decoder. */ - *(char *) buffer = priv->input->str[priv->bufp]; - priv->bufp++; - - return (size_t) 1; + *(char *) buffer = nm_str_buf_get_char(data->input, data->bufp); + data->bufp++; + return 1; } -/** - * ovsdb_read_cb: - * - * Read out the data available from the ovsdb socket and try to deserialize - * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg(). - */ -static void -ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +static json_t * +_json_read_msg(NMOvsdb *self, NMStrBuf *input) { - NMOvsdb *self = NM_OVSDB(user_data); - NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - GInputStream *stream = G_INPUT_STREAM(source_object); - GError *error = NULL; - gssize size; - json_t *msg; - json_error_t json_error = { + gs_free char *ss = NULL; + JsonReadMsgData data = { + .bufp = 0, + .input = input, + }; + json_error_t json_error = { 0, }; + json_t *msg; - size = g_input_stream_read_finish(stream, res, &error); - if (size == -1) { - /* ovsdb-server was possibly restarted */ - _LOGW("short read from ovsdb: %s", error->message); - priv->num_failures++; - g_clear_error(&error); - ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); - return; - } + /* The callback always eats up only up to a single byte. This makes it + * possible for us to identify complete JSON objects in spite of us not + * knowing the length in advance. */ + msg = json_load_callback(_json_read_msg_cb, &data, JSON_DISABLE_EOF_CHECK, &json_error); + if (!msg) + return NULL; - g_string_append_len(priv->input, priv->buf, size); - do { - priv->bufp = 0; - /* The callback always eats up only up to a single byte. This makes - * it possible for us to identify complete JSON objects in spite of - * us not knowing the length in advance. */ - msg = json_load_callback(_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error); - if (msg) { - ovsdb_got_msg(self, msg); - g_string_erase(priv->input, 0, priv->bufp); - } - json_decref(msg); - } while (msg); + nm_assert(data.bufp > 0); - if (!priv->conn) - return; + _LOGT("json: parse %zu bytes: \"%s\"", + data.bufp, + (ss = g_strndup(nm_str_buf_get_str_at_unsafe(input, 0), data.bufp))); - if (size) - ovsdb_read(self); + nm_str_buf_erase(input, 0, data.bufp, FALSE); + return msg; } -static void -ovsdb_read(NMOvsdb *self) +static gboolean +_ovsdb_read_input_timeout_cb(gpointer user_data) { + NMOvsdb *self = user_data; NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - g_input_stream_read_async(g_io_stream_get_input_stream(G_IO_STREAM(priv->conn)), - priv->buf, - sizeof(priv->buf), - G_PRIORITY_DEFAULT, - NULL, - ovsdb_read_cb, - self); + _LOGW("invalid/incomplete data in receive buffer. Reset"); + priv->num_failures++; + ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); + return G_SOURCE_CONTINUE; } static void -ovsdb_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +ovsdb_read(NMOvsdb *self) { - GOutputStream *stream = G_OUTPUT_STREAM(source_object); - NMOvsdb *self = NM_OVSDB(user_data); - NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - GError *error = NULL; + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); gssize size; - size = g_output_stream_write_finish(stream, res, &error); - if (size == -1) { +again: + size = nm_utils_fd_read(priv->conn_fd, &priv->input_buf); + + if (size <= 0) { + if (size == -EAGAIN) { + if (priv->input_buf.len == 0) + nm_clear_g_source_inst(&priv->input_timeout_source); + else if (!priv->input_timeout_source) { + /* We have data in the buffer but nothing further to read. Schedule a timer, + * if we don't get the rest within timeout, it means that the buffer + * content is broken (_json_read_msg() cannot extract any data) and + * we disconnect. */ + priv->input_timeout_source = + nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, NULL); + } + return; + } + /* ovsdb-server was possibly restarted */ - _LOGW("short write to ovsdb: %s", error->message); + _LOGW("short read from ovsdb: %s", nm_strerror_native(-size)); priv->num_failures++; - g_clear_error(&error); ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); return; } - if (!priv->conn) - return; + nm_assert(priv->input_buf.len > 0); + + while (TRUE) { + nm_auto_decref_json json_t *msg = NULL; + + msg = _json_read_msg(self, &priv->input_buf); + if (!msg) + break; - g_string_erase(priv->output, 0, size); + nm_clear_g_source_inst(&priv->input_timeout_source); + ovsdb_got_msg(self, msg); - ovsdb_write(self); + if (priv->input_buf.len == 0) + break; + } + + if (priv->input_buf.len > 0) { + if (priv->input_buf.len > 50 * 1024 * 1024) { + _LOGW("received too much data from ovsdb that is not valid JSON"); + priv->num_failures++; + ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); + return; + } + /* We have an incomplete message in the message buffer. Don't wait for another round + * of "poll", instead try to read it again. */ + goto again; + } + + nm_clear_g_source_inst(&priv->input_timeout_source); +} + +static gboolean +ovsdb_read_cb(int fd, GIOCondition condition, gpointer user_data) +{ + ovsdb_read(user_data); + return G_SOURCE_CONTINUE; } static void ovsdb_write(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - GOutputStream *stream; + gssize n; - if (!priv->output->len) +again: + if (priv->output_buf.len == 0) { + nm_clear_g_source_inst(&priv->conn_fd_out_source); return; + } + + n = write(priv->conn_fd, + nm_str_buf_get_str_at_unsafe(&priv->output_buf, 0), + priv->output_buf.len); + + if (n < 0) + n = -NM_ERRNO_NATIVE(errno); - stream = g_io_stream_get_output_stream(G_IO_STREAM(priv->conn)); - if (g_output_stream_has_pending(stream)) + if (n == -EAGAIN) { + if (!priv->conn_fd_out_source) { + priv->conn_fd_out_source = + nm_g_unix_fd_add_source(priv->conn_fd, G_IO_OUT, ovsdb_write_cb, self); + } return; + } + + if (n <= 0) { + /* ovsdb-server was possibly restarted */ + _LOGW("short write to ovsdb: %s", nm_strerror_native(-n)); + priv->num_failures++; + ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); + return; + } - g_output_stream_write_async(stream, - priv->output->str, - priv->output->len, - G_PRIORITY_DEFAULT, - NULL, - ovsdb_write_cb, - self); + nm_str_buf_erase(&priv->output_buf, 0, n, FALSE); + goto again; +} + +static void +ovsdb_write_try(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + if (priv->conn_fd >= 0 && !priv->conn_fd_out_source) + ovsdb_write(self); +} + +static gboolean +ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data) +{ + ovsdb_write(user_data); + return G_SOURCE_CONTINUE; } /*****************************************************************************/ @@ -2474,7 +2536,7 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing) nm_assert(!retry || !is_disposing); - if (!priv->conn && !priv->conn_cancellable) + if (priv->conn_fd < 0 && !priv->conn_cancellable) return; _LOGD("disconnecting from ovsdb, retry %d", retry); @@ -2498,10 +2560,12 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing) _call_complete(call, NULL, error); } - priv->bufp = 0; - g_string_truncate(priv->input, 0); - g_string_truncate(priv->output, 0); - g_clear_object(&priv->conn); + nm_str_buf_reset(&priv->input_buf); + nm_str_buf_reset(&priv->output_buf); + nm_clear_fd(&priv->conn_fd); + nm_clear_g_source_inst(&priv->conn_fd_in_source); + nm_clear_g_source_inst(&priv->conn_fd_out_source); + nm_clear_g_source_inst(&priv->input_timeout_source); nm_clear_g_free(&priv->db_uuid); nm_clear_g_cancellable(&priv->conn_cancellable); @@ -2702,15 +2766,12 @@ _ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take) gs_unref_object GSocket *socket = NULL; gs_free_error GError *error = NULL; - socket = g_socket_new_from_fd(nm_steal_fd(&fd_take), &error); - if (!socket) { - _LOGT("connect: failure to open socket for new FD: %s", error->message); - ovsdb_disconnect(self, FALSE, FALSE); - return; - } + nm_clear_g_cancellable(&priv->conn_cancellable); + + nm_io_fcntl_setfl_update_nonblock(fd_take); - priv->conn = g_socket_connection_factory_create_connection(socket); - g_clear_object(&priv->conn_cancellable); + priv->conn_fd = nm_steal_fd(&fd_take); + priv->conn_fd_in_source = nm_g_unix_fd_add_source(priv->conn_fd, G_IO_IN, ovsdb_read_cb, self); ovsdb_read(self); ovsdb_next_command(self); @@ -2784,7 +2845,7 @@ ovsdb_try_connect(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - if (priv->conn || priv->conn_cancellable) + if (priv->conn_fd >= 0 || priv->conn_cancellable) return; _LOGT("connect: start connecting socket %s on idle", NM_OVSDB_SOCKET); @@ -2964,11 +3025,15 @@ nm_ovsdb_init(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + priv->conn_fd = -1; + + priv->input_buf = NM_STR_BUF_INIT(0, FALSE); + priv->output_buf = NM_STR_BUF_INIT(0, FALSE); + c_list_init(&priv->calls_lst_head); priv->platform = g_object_ref(NM_PLATFORM_GET); - priv->input = g_string_new(NULL); - priv->output = g_string_new(NULL); + priv->bridges = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL); priv->ports = @@ -2989,14 +3054,8 @@ dispose(GObject *object) nm_assert(c_list_is_empty(&priv->calls_lst_head)); - if (priv->input) { - g_string_free(priv->input, TRUE); - priv->input = NULL; - } - if (priv->output) { - g_string_free(priv->output, TRUE); - priv->output = NULL; - } + nm_str_buf_destroy(&priv->input_buf); + nm_str_buf_destroy(&priv->output_buf); g_clear_object(&priv->platform); nm_clear_pointer(&priv->bridges, g_hash_table_destroy); diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c index 5c955986..4d748362 100644 --- a/src/core/devices/team/nm-device-team.c +++ b/src/core/devices/team/nm-device-team.c @@ -899,8 +899,13 @@ attach_port(NMDevice *device, return TRUE; } -static void -detach_port(NMDevice *device, NMDevice *port, gboolean configure) +static NMTernary +detach_port(NMDevice *device, + NMDevice *port, + gboolean configure, + GCancellable *cancellable, + NMDeviceAttachPortCallback callback, + gpointer user_data) { NMDeviceTeam *self = NM_DEVICE_TEAM(device); NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); @@ -950,6 +955,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure) _update_port_config(self, port_iface, "{}"); g_hash_table_remove(priv->port_configs, port_iface); } + + return TRUE; } static gboolean diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c index 40e38321..73fd4716 100644 --- a/src/core/devices/wifi/nm-device-iwd-p2p.c +++ b/src/core/devices/wifi/nm-device-iwd-p2p.c @@ -126,14 +126,17 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMSettingWifiP2P *s_wifi_p2p; GBytes *wfd_ies; NMSettingIPConfig *s_ip; if (!NM_DEVICE_CLASS(nm_device_iwd_p2p_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_wifi_p2p = diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c index e03227cd..47407a1e 100644 --- a/src/core/devices/wifi/nm-device-iwd.c +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -159,7 +159,7 @@ ap_add_remove(NMDeviceIwd *self, } if (priv->enabled && !priv->iwd_autoconnect) - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); if (recheck_available_connections) nm_device_recheck_available_connections(NM_DEVICE(self)); @@ -208,7 +208,7 @@ remove_all_aps(NMDeviceIwd *self) ap_add_remove(self, FALSE, ap, FALSE); if (!priv->iwd_autoconnect) - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); nm_device_recheck_available_connections(NM_DEVICE(self)); } @@ -401,7 +401,7 @@ get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data) if (changed) { if (!priv->iwd_autoconnect) - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); nm_device_recheck_available_connections(NM_DEVICE(self)); } @@ -723,7 +723,10 @@ is_ap_known_network(NMIwdManager *manager, NMWifiAP *ap) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceIwd *self = NM_DEVICE_IWD(device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); @@ -739,7 +742,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * gsize ssid_len; if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_wireless = nm_connection_get_setting_wireless(connection); @@ -1682,7 +1685,7 @@ failed: if (!priv->nm_autoconnect) { priv->nm_autoconnect = true; - nm_device_emit_recheck_auto_activate(device); + nm_device_recheck_auto_activate_schedule(device); } } g_variant_unref(value); @@ -2302,9 +2305,10 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) * to reset the retry count so we set no timeout. */ if (priv->iwd_autoconnect) { - NMSettingsConnection *sett_conn = nm_act_request_get_settings_connection(req); - - nm_settings_connection_autoconnect_retries_set(sett_conn, 0); + nm_manager_devcon_autoconnect_retries_set(nm_device_get_manager(device), + device, + nm_act_request_get_settings_connection(req), + 0); } /* With priv->iwd_autoconnect, if we're assuming a connection because @@ -2908,7 +2912,7 @@ state_changed(NMDeviceIwd *self, const char *new_state) if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) { priv->nm_autoconnect = TRUE; if (!can_connect) - nm_device_emit_recheck_auto_activate(device); + nm_device_recheck_auto_activate_schedule(device); } } @@ -3104,12 +3108,12 @@ config_changed(NMConfig *config, NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); gboolean old_iwd_ac = priv->iwd_autoconnect; - priv->iwd_autoconnect = - nm_config_data_get_device_config_boolean(config_data, - NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, - NM_DEVICE(self), - TRUE, - TRUE); + priv->iwd_autoconnect = nm_config_data_get_device_config_boolean_by_device( + config_data, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, + NM_DEVICE(self), + TRUE, + TRUE); if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) { gs_unref_variant GVariant *value = NULL; diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c index 4705f75c..436c7847 100644 --- a/src/core/devices/wifi/nm-device-olpc-mesh.c +++ b/src/core/devices/wifi/nm-device-olpc-mesh.c @@ -270,7 +270,7 @@ companion_state_changed_cb(NMDeviceWifi *companion, NMDeviceState self_state = nm_device_get_state(NM_DEVICE(self)); if (old_state > NM_DEVICE_STATE_DISCONNECTED && state <= NM_DEVICE_STATE_DISCONNECTED) { - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); } if (self_state < NM_DEVICE_STATE_PREPARE || self_state > NM_DEVICE_STATE_ACTIVATED diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c index 424464c1..fa8cb8fa 100644 --- a/src/core/devices/wifi/nm-device-wifi-p2p.c +++ b/src/core/devices/wifi/nm-device-wifi-p2p.c @@ -233,10 +233,13 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { if (!NM_DEVICE_CLASS(nm_device_wifi_p2p_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; /* TODO: Allow limitting the interface using the HW-address? */ diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c index 03625f8d..43772834 100644 --- a/src/core/devices/wifi/nm-device-wifi.c +++ b/src/core/devices/wifi/nm-device-wifi.c @@ -483,7 +483,7 @@ _scan_notify_is_scanning(NMDeviceWifi *self) if (!_scan_is_scanning_eval(priv)) { if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED) - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); nm_device_remove_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE); } @@ -843,7 +843,7 @@ ap_add_remove(NMDeviceWifi *self, nm_dbus_object_clear_and_unexport(&ap); } - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_auto_activate_schedule(NM_DEVICE(self)); if (recheck_available_connections) nm_device_recheck_available_connections(NM_DEVICE(self)); } @@ -981,7 +981,10 @@ deactivate_reset_hw_addr(NMDevice *device) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { NMDeviceWifi *self = NM_DEVICE_WIFI(device); NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); @@ -995,7 +998,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * const char *key_mgmt; if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; s_wireless = nm_connection_get_setting_wireless(connection); @@ -1395,7 +1398,7 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset) priv = NM_DEVICE_WIFI_GET_PRIVATE(self); - randomize = nm_config_data_get_device_config_boolean( + randomize = nm_config_data_get_device_config_boolean_by_device( NM_CONFIG_GET_DATA, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS, device, @@ -1428,7 +1431,7 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset) * a new one.*/ priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC; - generate_mac_address_mask = nm_config_data_get_device_config( + generate_mac_address_mask = nm_config_data_get_device_config_by_device( NM_CONFIG_GET_DATA, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK, device, diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c index b83120f9..a1050c3f 100644 --- a/src/core/devices/wwan/nm-device-modem.c +++ b/src/core/devices/wwan/nm-device-modem.c @@ -383,12 +383,15 @@ get_type_description(NMDevice *device) } static gboolean -check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) { GError *local = NULL; if (!NM_DEVICE_CLASS(nm_device_modem_parent_class) - ->check_connection_compatible(device, connection, error)) + ->check_connection_compatible(device, connection, check_properties, error)) return FALSE; if (!nm_modem_check_connection_compatible(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c index f0907c46..a150040f 100644 --- a/src/core/devices/wwan/nm-modem-broadband.c +++ b/src/core/devices/wwan/nm-modem-broadband.c @@ -20,6 +20,8 @@ #define NM_MODEM_BROADBAND_MODEM "modem" +#define MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS MM_CHECK_VERSION(1, 10, 0) + #if !MM_CHECK_VERSION(1, 14, 0) #define MM_MODEM_CAPABILITY_5GNR ((MMModemCapability) (1 << 6)) #endif @@ -44,6 +46,7 @@ typedef enum { CONNECT_STEP_WAIT_FOR_SIM, CONNECT_STEP_UNLOCK, CONNECT_STEP_WAIT_FOR_READY, + CONNECT_STEP_INTIAL_EPS_BEARER, CONNECT_STEP_CONNECT, CONNECT_STEP_LAST, } ConnectStep; @@ -560,6 +563,36 @@ out: return TRUE; } +#if MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS +static void +set_initial_eps_bearer_settings_ready(MMModem3gpp *modem_3gpp_iface, + GAsyncResult *res, + NMModemBroadband *self) +{ + gs_free_error GError *error = NULL; + + if (!mm_modem_3gpp_set_initial_eps_bearer_settings_finish(modem_3gpp_iface, res, &error)) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (!g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) { + _LOGW("failed to set initial EPS bearer settings: %s", error->message); + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_GSM_APN_FAILED); + connect_context_clear(self); + return; + } + + _LOGD("failed to set initial EPS bearer settings due to lack of support: %s", + error->message); + } + + self->_priv.ctx->step++; + connect_context_step(self); +} +#endif + static void connect_context_step(NMModemBroadband *self) { @@ -629,6 +662,56 @@ connect_context_step(NMModemBroadband *self) ctx->step++; } /* fall-through */ + + case CONNECT_STEP_INTIAL_EPS_BEARER: + if (MODEM_CAPS_3GPP(ctx->caps)) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection); + const char *apn = nm_setting_gsm_get_initial_eps_apn(s_gsm); + gboolean do_config = nm_setting_gsm_get_initial_eps_config(s_gsm); + + /* assume do_config is true if an APN is set */ + if (apn || do_config) { +#if MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS + gs_unref_object MMBearerProperties *config = NULL; + NMModemIPType ip_type = nm_modem_get_initial_eps_bearer_ip_type(ctx->ip_types); + + config = mm_bearer_properties_new(); + switch (ip_type) { + case NM_MODEM_IP_TYPE_IPV4: + mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV4); + break; + case NM_MODEM_IP_TYPE_IPV6: + mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV6); + break; + case NM_MODEM_IP_TYPE_IPV4V6: + mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV4V6); + break; + default: + /* do nothing */ + break; + } + if (apn) + mm_bearer_properties_set_apn(config, apn); + + /* + * Setting the initial EPS bearer settings is a no-op in + * ModemManager if the desired configuration is already active. + */ + mm_modem_3gpp_set_initial_eps_bearer_settings( + self->_priv.modem_3gpp_iface, + config, + ctx->cancellable, + (GAsyncReadyCallback) set_initial_eps_bearer_settings_ready, + self); + break; +#else + _LOGD("cannot set initial EPS bearer settings due to old ModemManager version"); +#endif + } + } + ctx->step++; + /* fall-through */ + case CONNECT_STEP_CONNECT: if (!ctx->connect_properties) break; diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c index 0159d351..ea0fa7aa 100644 --- a/src/core/devices/wwan/nm-modem.c +++ b/src/core/devices/wwan/nm-modem.c @@ -558,6 +558,37 @@ nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError return NULL; } +/** + * nm_modem_get_initial_eps_bearer_ip_type: + * @connection_ip_types: the #NMModemIPType as returned by + * nm_modem_get_connection_ip_type + * + * Given the connection IP types, this function returns which IP type to use when + * configuring the initial EPS bearer. + * + * Returns: the #NMModemIpType value to use for the initial EPS bearer + */ +NMModemIPType +nm_modem_get_initial_eps_bearer_ip_type(const GArray *connection_ip_types) +{ + NMModemIPType ip_types = NM_MODEM_IP_TYPE_UNKNOWN; + guint i; + + nm_assert(connection_ip_types); + + for (i = 0; i < connection_ip_types->len; i++) + ip_types |= nm_g_array_index(connection_ip_types, NMModemIPType, i); + + nm_assert(ip_types != NM_MODEM_IP_TYPE_UNKNOWN); + + if (ip_types & NM_MODEM_IP_TYPE_IPV4V6) + return NM_MODEM_IP_TYPE_IPV4V6; + if (ip_types & NM_MODEM_IP_TYPE_IPV4) + return NM_MODEM_IP_TYPE_IPV4; + + return NM_MODEM_IP_TYPE_IPV6; +} + const char * nm_modem_get_device_id(NMModem *self) { diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h index ec001102..021d77b2 100644 --- a/src/core/devices/wwan/nm-modem.h +++ b/src/core/devices/wwan/nm-modem.h @@ -226,6 +226,8 @@ void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason); GArray *nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error); +NMModemIPType nm_modem_get_initial_eps_bearer_ip_type(const GArray *connection_ip_types); + /* For subclasses */ void nm_modem_emit_signal_new_config(NMModem *self, diff --git a/src/core/dhcp/nm-dhcp-client-logging.h b/src/core/dhcp/nm-dhcp-client-logging.h index 2b0d8d06..3a2b927a 100644 --- a/src/core/dhcp/nm-dhcp-client-logging.h +++ b/src/core/dhcp/nm-dhcp-client-logging.h @@ -41,16 +41,19 @@ _nm_dhcp_client_get_domain(NMDhcpClient *self) if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ NMDhcpClient *_self = (NMDhcpClient *) (self); \ const char *__ifname = _self ? nm_dhcp_client_get_iface(_self) : NULL; \ + const char *_type = nm_dhcp_client_get_iface_type_for_log(_self); \ const NMLogDomain _domain = _nm_dhcp_client_get_domain(_self); \ \ nm_log(_level, \ _domain, \ __ifname, \ NULL, \ - "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + "%s%s%s%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \ - NM_PRINT_FMT_QUOTED(__ifname, " (", __ifname, ")", "") \ + (__ifname || _type) ? " " : "", \ + NM_PRINT_FMT_QUOTED(__ifname, "(", __ifname, ")", ""), \ + NM_PRINT_FMT_QUOTED(_type, "[", _type, "]", "") \ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index b10ce410..6978bd3c 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -196,6 +196,14 @@ nm_dhcp_client_get_iface(NMDhcpClient *self) return priv->config.iface; } +const char * +nm_dhcp_client_get_iface_type_for_log(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + return priv->config.iface_type_log; +} + NMDedupMultiIndex * nm_dhcp_client_get_multi_idx(NMDhcpClient *self) { @@ -1823,6 +1831,7 @@ config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src) nm_g_bytes_ref(config->client_id); config->iface = g_strdup(config->iface); + config->iface_type_log = g_strdup(config->iface_type_log); config->uuid = g_strdup(config->uuid); config->anycast_address = g_strdup(config->anycast_address); config->hostname = g_strdup(config->hostname); @@ -1885,6 +1894,7 @@ config_clear(NMDhcpClientConfig *config) nm_clear_pointer(&config->client_id, g_bytes_unref); nm_clear_g_free((gpointer *) &config->iface); + nm_clear_g_free((gpointer *) &config->iface_type_log); nm_clear_g_free((gpointer *) &config->uuid); nm_clear_g_free((gpointer *) &config->anycast_address); nm_clear_g_free((gpointer *) &config->hostname); diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h index f43770a0..903ea6ac 100644 --- a/src/core/dhcp/nm-dhcp-client.h +++ b/src/core/dhcp/nm-dhcp-client.h @@ -103,6 +103,10 @@ typedef struct { const char *iface; + /* Interface type for logging; only set for some devices whose names can be + * ambiguous. */ + const char *iface_type_log; + /* The hardware address */ GBytes *hwaddr; @@ -174,6 +178,10 @@ typedef struct { /* Number to prefixes (IA_PD) to request */ guint needed_prefixes; + /* A hint to send to server for prefix delegation (IA_PD). */ + struct in6_addr pd_hint_addr; + guint8 pd_hint_length; + /* Use Information-request to get stateless configuration * parameters (don't request a IA_NA) */ bool info_only : 1; @@ -260,6 +268,7 @@ gboolean nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer int nm_dhcp_client_get_addr_family(NMDhcpClient *self); const char *nm_dhcp_client_get_iface(NMDhcpClient *self); +const char *nm_dhcp_client_get_iface_type_for_log(NMDhcpClient *self); NMDedupMultiIndex *nm_dhcp_client_get_multi_idx(NMDhcpClient *self); int nm_dhcp_client_get_ifindex(NMDhcpClient *self); diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c index 35b2fb2e..4aab4b1e 100644 --- a/src/core/dhcp/nm-dhcp-dhclient.c +++ b/src/core/dhcp/nm-dhcp-dhclient.c @@ -356,6 +356,7 @@ dhclient_start(NMDhcpClient *client, gs_free char *preferred_leasefile_path = NULL; int addr_family; const NMDhcpClientConfig *client_config; + char pd_length_str[16]; g_return_val_if_fail(!priv->pid_file, FALSE); client_config = nm_dhcp_client_get_config(client); @@ -463,6 +464,17 @@ dhclient_start(NMDhcpClient *client, if (mode_opt) g_ptr_array_add(argv, (gpointer) mode_opt); + + if (prefixes > 0 && client_config->v6.pd_hint_length > 0) { + if (!IN6_IS_ADDR_UNSPECIFIED(&client_config->v6.pd_hint_addr)) { + _LOGW("dhclient only supports a length as prefix delegation hint, not a prefix"); + } + + nm_sprintf_buf(pd_length_str, "%u", client_config->v6.pd_hint_length); + g_ptr_array_add(argv, "--prefix-len-hint"); + g_ptr_array_add(argv, pd_length_str); + } + while (prefixes--) g_ptr_array_add(argv, (gpointer) "-P"); } diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c index 213d9496..ee95abb7 100644 --- a/src/core/dhcp/nm-dhcp-helper.c +++ b/src/core/dhcp/nm-dhcp-helper.c @@ -21,21 +21,22 @@ #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR) #endif -#define _NMLOG(always_enabled, level, ...) \ - G_STMT_START \ - { \ - if ((always_enabled) || _NMLOG_ENABLED(level)) { \ - GTimeVal _tv; \ - \ - g_get_current_time(&_tv); \ - g_print( \ - "nm-dhcp-helper[%ld] %-7s [%ld.%04ld] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \ - (long) getpid(), \ - nm_utils_syslog_to_str(level), \ - _tv.tv_sec, \ - _tv.tv_usec / 100 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } \ +#define _NMLOG(always_enabled, level, ...) \ + G_STMT_START \ + { \ + if ((always_enabled) || _NMLOG_ENABLED(level)) { \ + gint64 _tv; \ + \ + _tv = g_get_real_time(); \ + g_print("nm-dhcp-helper[%ld] %-7s [%" G_GINT64_FORMAT \ + ".%04d] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \ + (long) getpid(), \ + nm_utils_syslog_to_str(level), \ + (_tv / NM_UTILS_USEC_PER_SEC), \ + ((int) ((_tv % NM_UTILS_USEC_PER_SEC) / (((gint64) 100)))) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ G_STMT_END #define _LOGD(...) _NMLOG(TRUE, LOG_INFO, __VA_ARGS__) diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c index 33a9f4ed..7c47c82e 100644 --- a/src/core/dhcp/nm-dhcp-options.c +++ b/src/core/dhcp/nm-dhcp-options.c @@ -199,6 +199,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = { REQ(NM_DHCP_OPTION_DHCP6_DNS_SERVERS, "dhcp6_name_servers", TRUE), REQ(NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, "dhcp6_domain_search", TRUE), + REQ(NM_DHCP_OPTION_DHCP6_IA_PD, "ip6_prefix", FALSE), REQ(NM_DHCP_OPTION_DHCP6_SNTP_SERVERS, "dhcp6_sntp_servers", TRUE), REQ(NM_DHCP_OPTION_DHCP6_FQDN, "fqdn_fqdn", FALSE), REQ(NM_DHCP_OPTION_DHCP6_NTP_SERVER, "dhcp6_ntp_servers", TRUE), @@ -237,6 +238,7 @@ static const NMDhcpOption *const _sorted_options_6[G_N_ELEMENTS(_nm_dhcp_option_ A(14), A(15), A(16), + A(17), #undef A }; diff --git a/src/core/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h index 050080d9..1c61c74d 100644 --- a/src/core/dhcp/nm-dhcp-options.h +++ b/src/core/dhcp/nm-dhcp-options.h @@ -161,6 +161,7 @@ typedef enum { NM_DHCP_OPTION_DHCP6_SERVER_ID = 2, NM_DHCP_OPTION_DHCP6_DNS_SERVERS = 23, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST = 24, + NM_DHCP_OPTION_DHCP6_IA_PD = 25, /* RFC 8415 */ NM_DHCP_OPTION_DHCP6_SNTP_SERVERS = 31, NM_DHCP_OPTION_DHCP6_FQDN = 39, NM_DHCP_OPTION_DHCP6_NTP_SERVER = 56, /* RFC 5908 */ @@ -188,7 +189,7 @@ typedef struct { } NMDhcpOption; extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[143]; -extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[17]; +extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[18]; static inline const char * nm_dhcp_option_get_name(const NMDhcpOption *option) diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c index 6f9312da..0fc5f928 100644 --- a/src/core/dhcp/nm-dhcp-systemd.c +++ b/src/core/dhcp/nm-dhcp-systemd.c @@ -155,6 +155,26 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro str->str); } + { + struct in6_addr prefix; + uint8_t prefix_len; + + nm_gstring_prepare(&str); + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + while (!sd_dhcp6_lease_get_pd(lease, &prefix, &prefix_len, NULL, NULL)) { + nm_gstring_add_space_delimiter(str); + nm_inet6_ntop(&prefix, addr_str); + g_string_append_printf(str, "%s/%u", addr_str, prefix_len); + } + if (str->len > 0) { + nm_dhcp_option_add_option(options, + TRUE, + AF_INET6, + NM_DHCP_OPTION_DHCP6_IA_PD, + str->str); + } + } + num = sd_dhcp6_lease_get_domains(lease, &domains); if (num > 0) { nm_gstring_prepare(&str); @@ -366,6 +386,15 @@ ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error) _LOGW("dhcp-client6: only one prefix request is supported"); } prefix_delegation = TRUE; + if (client_config->v6.pd_hint_length > 0) { + r = sd_dhcp6_client_set_prefix_delegation_hint(sd_client, + client_config->v6.pd_hint_length, + &client_config->v6.pd_hint_addr); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set prefix delegation hint: %s"); + return FALSE; + } + } } r = sd_dhcp6_client_set_prefix_delegation(sd_client, prefix_delegation); if (r < 0) { diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c index 05aeff49..53e40f59 100644 --- a/src/core/dns/nm-dns-dnsmasq.c +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -527,7 +527,6 @@ _gl_pid_spawn_next_step(void) argv[argv_idx++] = "--cache-size=400"; argv[argv_idx++] = "--clear-on-reload"; /* clear cache when dns server changes */ argv[argv_idx++] = "--conf-file=/dev/null"; /* avoid loading /etc/dnsmasq.conf */ - argv[argv_idx++] = "--proxy-dnssec"; /* Allow DNSSEC to pass through */ argv[argv_idx++] = "--enable-dbus=" DNSMASQ_DBUS_SERVICE; /* dnsmasq exits if the conf dir is not present */ diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 6ee2e816..53564693 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -125,6 +125,9 @@ typedef struct { NMConfig *config; + NMDnsConfigIPData *best_ip_config_4; + NMDnsConfigIPData *best_ip_config_6; + struct { guint64 ts; guint num_restarts; @@ -173,14 +176,46 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER /*****************************************************************************/ static gboolean -domain_is_valid(const char *domain, gboolean check_public_suffix) +domain_is_valid(const char *domain, + gboolean reject_public_suffix, + gboolean assume_any_tld_is_public) { if (*domain == '\0') return FALSE; -#if WITH_LIBPSL - if (check_public_suffix && psl_is_public_suffix(psl_builtin(), domain)) - return FALSE; + + if (reject_public_suffix) { + int is_pub; + +#if !WITH_LIBPSL + /* Without libpsl, we cannot detect that the domain is a public suffix, we assume + * the domain is not and valid. */ + is_pub = FALSE; +#elif defined(PSL_TYPE_NO_STAR_RULE) + /* + * If we use PSL_TYPE_ANY, any TLD (top-level domain, i.e., domain + * with no dots) is considered *public* by the PSL library even if + * it is *not* on the official suffix list. This is the implicit + * behavior of the older API function psl_is_public_suffix(). + * To inhibit that and only deem TLDs explicitly listed in the PSL + * as public, we need to turn off the "prevailing star rule" with + * PSL_TYPE_NO_STAR_RULE. + * For documentation on psl_is_public_suffix2(), see: + * https://rockdaboot.github.io/libpsl/libpsl-Public-Suffix-List-functions.html#psl-is-public-suffix2 + * For more on the public suffix format, including wildcards: + * https://github.com/publicsuffix/list/wiki/Format#format + */ + is_pub = + psl_is_public_suffix2(psl_builtin(), + domain, + assume_any_tld_is_public ? PSL_TYPE_ANY : PSL_TYPE_NO_STAR_RULE); +#else + is_pub = psl_is_public_suffix(psl_builtin(), domain); #endif + + if (is_pub) + return FALSE; + } + return TRUE; } @@ -533,7 +568,7 @@ add_dns_domains(GPtrArray *array, str = searches[i]; if (!include_routing && domain_is_routing(str)) continue; - if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE)) + if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) continue; add_string_item(array, str, dup); } @@ -542,7 +577,7 @@ add_dns_domains(GPtrArray *array, str = domains[i]; if (!include_routing && domain_is_routing(str)) continue; - if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE)) + if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) continue; add_string_item(array, str, dup); } @@ -647,7 +682,7 @@ run_netconfig(NMDnsManager *self, GError **error, int *stdin_fd) if (!g_spawn_async_with_pipes(NULL, argv, NULL, - G_SPAWN_DO_NOT_REAP_CHILD, + G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, @@ -1236,7 +1271,7 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) for (i = 0; searches[i]; i++) { if (domain_is_routing(searches[i])) continue; - if (!domain_is_valid(searches[i], FALSE)) + if (!domain_is_valid(searches[i], FALSE, TRUE)) continue; add_string_item(rc->searches, searches[i], TRUE); } @@ -1946,6 +1981,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *self, NMDnsConfigIPData *ip_data = NULL; int dns_priority; gboolean any_removed = FALSE; + NMDnsConfigIPData **p_best; g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); g_return_val_if_fail(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd), FALSE); @@ -2013,6 +2049,12 @@ nm_dns_manager_set_ip_config(NMDnsManager *self, } any_removed = TRUE; + + if (priv->best_ip_config_4 == ip_data_iter) + priv->best_ip_config_4 = NULL; + if (priv->best_ip_config_6 == ip_data_iter) + priv->best_ip_config_6 = NULL; + _dns_config_ip_data_free(ip_data_iter); } } @@ -2063,6 +2105,19 @@ nm_dns_manager_set_ip_config(NMDnsManager *self, changed = TRUE; } + p_best = NM_IS_IPv4(addr_family) ? &priv->best_ip_config_4 : &priv->best_ip_config_6; + if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) { + /* Only one best-device per IP version is allowed */ + if (*p_best != ip_data) { + if (*p_best) + (*p_best)->ip_config_type = NM_DNS_IP_CONFIG_TYPE_DEFAULT; + *p_best = ip_data; + } + } else { + if (*p_best == ip_data) + *p_best = NULL; + } + if (changed) priv->ip_data_lst_need_sort = TRUE; @@ -2100,7 +2155,8 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s /* Certain hostnames we don't want to include in resolv.conf 'searches' */ if (hostname && nm_utils_is_specific_hostname(hostname) - && !g_str_has_suffix(hostname, ".in-addr.arpa") && !nm_inet_is_valid(AF_UNSPEC, hostname)) { + && !NM_STR_HAS_SUFFIX(hostname, ".in-addr.arpa") + && !nm_inet_is_valid(AF_UNSPEC, hostname)) { domain = strchr(hostname, '.'); if (domain) { domain++; @@ -2111,11 +2167,16 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s * specified, this makes a good default.) However, if the * hostname is the top level of a domain (eg, "example.com"), * then use the hostname itself as the search (since the user - * is unlikely to want "com" as a search domain).a + * is unlikely to want "com" as a search domain). + * + * Because that logic only applies to public domains, the + * "assume_any_tld_is_public" parameter is FALSE. For + * example, it is likely that the user *does* want "local" + * or "localdomain" as a search domain. */ - if (domain_is_valid(domain, TRUE)) { + if (domain_is_valid(domain, TRUE, FALSE)) { /* pass */ - } else if (domain_is_valid(hostname, TRUE)) { + } else if (domain_is_valid(hostname, TRUE, FALSE)) { domain = hostname; } @@ -2127,6 +2188,8 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s if (!nm_strdup_reset(&priv->hostdomain, domain)) return; + _LOGT("set host domain to %s%s%s", NM_PRINT_FMT_QUOTE_STRING(priv->hostdomain)); + if (skip_update) return; @@ -2779,6 +2842,9 @@ dispose(GObject *object) nm_clear_g_source_inst(&priv->update_pending_unblock); + priv->best_ip_config_4 = NULL; + priv->best_ip_config_6 = NULL; + c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_data_lst_head, ip_data_lst) _dns_config_ip_data_free(ip_data); diff --git a/src/core/main.c b/src/core/main.c index 2eb230d9..4c7de6cd 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -266,13 +266,14 @@ _dbus_manager_init(NMConfig *config) c_a_q_type = nm_config_get_configure_and_quit(config); - if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED) - return nm_dbus_manager_acquire_bus(busmgr, TRUE); + if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) { + /* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM + * connection. And of course don't claim the D-Bus name. */ + return TRUE; + } - nm_assert(c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD); - /* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM - * connection. And of course don't claim the D-Bus name. */ - return TRUE; + nm_assert(c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED); + return nm_dbus_manager_setup(busmgr); } /* @@ -507,6 +508,9 @@ main(int argc, char *argv[]) nm_log_dbg(LOGD_CORE, "setting up local loopback"); nm_platform_link_change_flags(NM_PLATFORM_GET, 1, IFF_UP, TRUE); + if (!nm_dbus_manager_request_name_sync(nm_dbus_manager_get())) + goto done; + success = TRUE; if (configure_and_quit == FALSE) { diff --git a/src/core/nm-act-request.c b/src/core/nm-act-request.c index 55d1829e..dce18ba4 100644 --- a/src/core/nm-act-request.c +++ b/src/core/nm-act-request.c @@ -416,8 +416,8 @@ nm_act_request_init(NMActRequest *req) /** * nm_act_request_new: * - * @settings_connection: (allow-none): the connection to activate @device with - * @applied_connection: (allow-none): the applied connection + * @settings_connection: (nullable): the connection to activate @device with + * @applied_connection: (nullable): the applied connection * @specific_object: the object path of the specific object (ie, Wi-Fi access point, * etc) that will be used to activate @connection and @device * @subject: the #NMAuthSubject representing the requestor of the activation diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c index 6f62a601..36a11f71 100644 --- a/src/core/nm-active-connection.c +++ b/src/core/nm-active-connection.c @@ -1153,7 +1153,7 @@ auth_done(NMAuthManager *auth_mgr, /** * nm_active_connection_authorize: * @self: the #NMActiveConnection - * @initial_connection: (allow-none): for add-and-activate, there + * @initial_connection: (nullable): for add-and-activate, there * is no @settings_connection available when creating the active connection. * Instead pass an alternative connection. * @result_func: function to be called on success or error diff --git a/src/core/nm-auth-utils.c b/src/core/nm-auth-utils.c index cef926fd..7739f443 100644 --- a/src/core/nm-auth-utils.c +++ b/src/core/nm-auth-utils.c @@ -251,7 +251,7 @@ nm_auth_chain_steal_data(NMAuthChain *self, const char *tag) * @tag: the tag for referencing the attached data. * @data: the data to attach. If %NULL, this call has no effect * and nothing is attached. - * @data_destroy: (allow-none): the destroy function for the data pointer. + * @data_destroy: (nullable): the destroy function for the data pointer. * * @tag string is not cloned and must outlive @self. That is why * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c index 2d15b0b5..9985fccf 100644 --- a/src/core/nm-bond-manager.c +++ b/src/core/nm-bond-manager.c @@ -438,6 +438,7 @@ _nft_call(NMBondManager *self, { gs_unref_bytes GBytes *stdin_buf = NULL; gs_free const char *const *previous_members_strv = NULL; + gboolean with_counters; if (up) { gs_unref_ptrarray GPtrArray *arr = NULL; @@ -480,11 +481,16 @@ _nft_call(NMBondManager *self, } } + /* counters in the nft rules are convenient for debugging, but have a performance overhead. + * Enable counters based on whether NM logging is enabled. */ + with_counters = _NMLOG_ENABLED(LOGL_TRACE); + stdin_buf = nm_firewall_nft_stdio_mlag(up, bond_ifname, bond_ifnames_down, active_members, - previous_members_strv); + previous_members_strv, + with_counters); nm_clear_g_cancellable(&self->cancellable); self->cancellable = g_cancellable_new(); diff --git a/src/core/nm-checkpoint.c b/src/core/nm-checkpoint.c index cd0e17fa..5c4d4e53 100644 --- a/src/core/nm-checkpoint.c +++ b/src/core/nm-checkpoint.c @@ -259,17 +259,19 @@ restore_and_activate_connection(NMCheckpoint *self, DeviceCheckpoint *dev_checkp g_clear_error(&local_error); return FALSE; } - - /* If the device is software, a brand new NMDevice may have been created */ - if (dev_checkpoint->is_software && !dev_checkpoint->device) { - dev_checkpoint->device = nm_manager_get_device(priv->manager, - dev_checkpoint->original_dev_name, - dev_checkpoint->dev_type); - nm_g_object_ref(dev_checkpoint->device); - } need_activation = TRUE; } + /* If the device is software, a brand new NMDevice may have been created + * after adding the new connection; or the old device might have been + * deleted and we need to fetch it again. */ + if (dev_checkpoint->is_software && !dev_checkpoint->device) { + dev_checkpoint->device = nm_manager_get_device(priv->manager, + dev_checkpoint->original_dev_name, + dev_checkpoint->dev_type); + nm_g_object_ref(dev_checkpoint->device); + } + if (!dev_checkpoint->device) { _LOGD("rollback: device cannot be restored"); return FALSE; @@ -368,7 +370,7 @@ nm_checkpoint_rollback(NMCheckpoint *self) _LOGD("rollback: device was not realized, unmanage it"); nm_device_set_unmanaged_by_flags_queue(device, NM_UNMANAGED_USER_EXPLICIT, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_NOW_UNMANAGED); goto next_dev; } @@ -402,7 +404,7 @@ nm_checkpoint_rollback(NMCheckpoint *self) _LOGD("rollback: explicitly unmanage device"); nm_device_set_unmanaged_by_flags_queue(device, NM_UNMANAGED_USER_EXPLICIT, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_NOW_UNMANAGED); } goto next_dev; @@ -458,8 +460,25 @@ next_dev: NMDeviceState state; nm_manager_for_each_device (priv->manager, device, tmp_lst) { + gboolean found = FALSE; + if (g_hash_table_contains(priv->devices, device)) continue; + + /* Also ignore devices that were in the checkpoint initially and + * were moved to 'removed_devices' because they got removed from + * the system. */ + for (i = 0; i < priv->removed_devices->len; i++) { + dev_checkpoint = priv->removed_devices->pdata[i]; + if (dev_checkpoint->dev_type == nm_device_get_device_type(device) + && nm_streq0(dev_checkpoint->original_dev_name, nm_device_get_iface(device))) { + found = TRUE; + break; + } + } + if (found) + continue; + state = nm_device_get_state(device); if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) { _LOGD("rollback: disconnecting new device %s", nm_device_get_iface(device)); diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c index ff44bc46..ed6d8381 100644 --- a/src/core/nm-config-data.c +++ b/src/core/nm-config-data.c @@ -130,6 +130,12 @@ G_DEFINE_TYPE(NMConfigData, nm_config_data, G_TYPE_OBJECT) static const char * _match_section_info_get_str(const MatchSectionInfo *m, GKeyFile *keyfile, const char *property); +static const char *_config_data_get_device_config(const NMConfigData *self, + const char *property, + const NMMatchSpecDeviceData *match_data, + NMDevice *device, + gboolean *has_match); + /*****************************************************************************/ const char * @@ -366,7 +372,55 @@ nm_config_data_get_iwd_config_path(const NMConfigData *self) } gboolean -nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device) +nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self, + const char *master, + const char *slave_type) +{ + const char *value; + gboolean has_match; + int m; + NMMatchSpecDeviceData match_data; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + + if (!master || !slave_type) + goto out_default; + + if (!nm_utils_ifname_valid_kernel(master, NULL)) + goto out_default; + + match_data = (NMMatchSpecDeviceData){ + .interface_name = master, + .device_type = slave_type, + }; + + value = _config_data_get_device_config(self, + NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, + &match_data, + NULL, + &has_match); + if (has_match) + m = nm_config_parse_boolean(value, -1); + else { + NMMatchSpecMatchType x; + + x = nm_match_spec_device(NM_CONFIG_DATA_GET_PRIVATE(self)->ignore_carrier, &match_data); + m = nm_match_spec_match_type_to_bool(x, -1); + } + + if (NM_IN_SET(m, TRUE, FALSE)) + return m; + +out_default: + /* if ignore-carrier is not explicitly or detected for the master, then we assume it's + * enabled. This is in line with nm_config_data_get_ignore_carrier_by_device(), where + * ignore-carrier is enabled based on nm_device_ignore_carrier_by_default(). + */ + return TRUE; +} + +gboolean +nm_config_data_get_ignore_carrier_by_device(const NMConfigData *self, NMDevice *device) { const char *value; gboolean has_match; @@ -375,10 +429,10 @@ nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device) g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); - value = nm_config_data_get_device_config(self, - NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, - device, - &has_match); + value = nm_config_data_get_device_config_by_device(self, + NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, + device, + &has_match); if (has_match) m = nm_config_parse_boolean(value, -1); else @@ -706,6 +760,7 @@ static const struct { } default_values[] = { {NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NM_CONFIG_DEFAULT_MAIN_PLUGINS}, {NM_CONFIG_KEYFILE_GROUP_MAIN, "rc-manager", NM_CONFIG_DEFAULT_MAIN_RC_MANAGER}, + {NM_CONFIG_KEYFILE_GROUP_MAIN, "migrate-ifcfg-rh", NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH}, {NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT, NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT}, @@ -1488,21 +1543,23 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new) /*****************************************************************************/ static const MatchSectionInfo * -_match_section_infos_lookup(const MatchSectionInfo *match_section_infos, - GKeyFile *keyfile, - const char *property, - NMDevice *device, - const NMPlatformLink *pllink, - const char *match_device_type, - const char **out_value) +_match_section_infos_lookup(const MatchSectionInfo *match_section_infos, + GKeyFile *keyfile, + const char *property, + const NMMatchSpecDeviceData *match_data, + NMDevice *device, + const char **out_value) { - const char *match_dhcp_plugin; + NMMatchSpecDeviceData match_data_local; + + /* Caller must either provide a "match_data" or a "device" (actually, + * neither is also fine, albeit unusual). */ + nm_assert(!match_data || !device); + nm_assert(!device || NM_IS_DEVICE(device)); if (!match_section_infos) goto out; - match_dhcp_plugin = nm_dhcp_manager_get_config(nm_dhcp_manager_get()); - for (; match_section_infos->group_name; match_section_infos++) { const char *value; gboolean match; @@ -1519,16 +1576,17 @@ _match_section_infos_lookup(const MatchSectionInfo *match_section_infos, continue; if (match_section_infos->match_device.has) { - if (device) - match = nm_device_spec_match_list(device, match_section_infos->match_device.spec); - else if (pllink) - match = nm_match_spec_device_by_pllink(pllink, - match_device_type, - match_dhcp_plugin, - match_section_infos->match_device.spec, - FALSE); - else - match = FALSE; + NMMatchSpecMatchType m; + + if (G_UNLIKELY(!match_data)) { + /* In most cases, we don't actually have any matches. So we "optimize" + * here by allowing the user to specify a NMDEvice directly, and only + * initialize the match-data when needed. */ + match_data = nm_match_spec_device_data_init_from_device(&match_data_local, device); + } + + m = nm_match_spec_device(match_section_infos->match_device.spec, match_data); + match = nm_match_spec_match_type_to_bool(m, FALSE); } else match = TRUE; @@ -1543,11 +1601,12 @@ out: return NULL; } -const char * -nm_config_data_get_device_config(const NMConfigData *self, - const char *property, - NMDevice *device, - gboolean *has_match) +static const char * +_config_data_get_device_config(const NMConfigData *self, + const char *property, + const NMMatchSpecDeviceData *match_data, + NMDevice *device, + gboolean *has_match) { const NMConfigDataPrivate *priv; const MatchSectionInfo *connection_info; @@ -1558,20 +1617,40 @@ nm_config_data_get_device_config(const NMConfigData *self, g_return_val_if_fail(self, NULL); g_return_val_if_fail(property && *property, NULL); + nm_assert(!match_data || !device); + nm_assert(!device || NM_IS_DEVICE(device)); + priv = NM_CONFIG_DATA_GET_PRIVATE(self); connection_info = _match_section_infos_lookup(&priv->device_infos[0], priv->keyfile, property, + match_data, device, - NULL, - NULL, &value); NM_SET_OUT(has_match, !!connection_info); return value; } const char * +nm_config_data_get_device_config(const NMConfigData *self, + const char *property, + const NMMatchSpecDeviceData *match_data, + gboolean *has_match) +{ + return _config_data_get_device_config(self, property, match_data, NULL, has_match); +} + +const char * +nm_config_data_get_device_config_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + gboolean *has_match) +{ + return _config_data_get_device_config(self, property, NULL, device, has_match); +} + +const char * nm_config_data_get_device_config_by_pllink(const NMConfigData *self, const char *property, const NMPlatformLink *pllink, @@ -1581,53 +1660,58 @@ nm_config_data_get_device_config_by_pllink(const NMConfigData *self, const NMConfigDataPrivate *priv; const MatchSectionInfo *connection_info; const char *value; + NMMatchSpecDeviceData match_data; g_return_val_if_fail(self, NULL); g_return_val_if_fail(property && *property, NULL); priv = NM_CONFIG_DATA_GET_PRIVATE(self); + nm_match_spec_device_data_init_from_platform(&match_data, + pllink, + match_device_type, + nm_dhcp_manager_get_config(nm_dhcp_manager_get())); + connection_info = _match_section_infos_lookup(&priv->device_infos[0], priv->keyfile, property, + &match_data, NULL, - pllink, - match_device_type, &value); NM_SET_OUT(has_match, !!connection_info); return value; } gboolean -nm_config_data_get_device_config_boolean(const NMConfigData *self, - const char *property, - NMDevice *device, - int val_no_match, - int val_invalid) +nm_config_data_get_device_config_boolean_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + int val_no_match, + int val_invalid) { const char *value; gboolean has_match; - value = nm_config_data_get_device_config(self, property, device, &has_match); + value = nm_config_data_get_device_config_by_device(self, property, device, &has_match); if (!has_match) return val_no_match; return nm_config_parse_boolean(value, val_invalid); } gint64 -nm_config_data_get_device_config_int64(const NMConfigData *self, - const char *property, - NMDevice *device, - int base, - gint64 min, - gint64 max, - gint64 val_no_match, - gint64 val_invalid) +nm_config_data_get_device_config_int64_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + int base, + gint64 min, + gint64 max, + gint64 val_no_match, + gint64 val_invalid) { const char *value; gboolean has_match; - value = nm_config_data_get_device_config(self, property, device, &has_match); + value = nm_config_data_get_device_config_by_device(self, property, device, &has_match); if (!has_match) { errno = ENOENT; return val_no_match; @@ -1651,9 +1735,8 @@ nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self, connection_info = _match_section_infos_lookup(&priv->device_infos[0], priv->keyfile, NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS, - device, - NULL, NULL, + device, NULL); if (connection_info) { @@ -1696,9 +1779,8 @@ nm_config_data_get_connection_default(const NMConfigData *self, _match_section_infos_lookup(&priv->connection_infos[0], priv->keyfile, property, - device, - NULL, NULL, + device, &value); return value; } diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h index e3dc90dd..9e7a50fc 100644 --- a/src/core/nm-config-data.h +++ b/src/core/nm-config-data.h @@ -185,7 +185,11 @@ const char *nm_config_data_get_dns_mode(const NMConfigData *self); const char *nm_config_data_get_rc_manager(const NMConfigData *self); gboolean nm_config_data_get_systemd_resolved(const NMConfigData *self); -gboolean nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device); +gboolean nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self, + const char *master, + const char *slave_type); + +gboolean nm_config_data_get_ignore_carrier_by_device(const NMConfigData *self, NMDevice *device); gboolean nm_config_data_get_assume_ipv6ll_only(const NMConfigData *self, NMDevice *device); int nm_config_data_get_sriov_num_vfs(const NMConfigData *self, NMDevice *device); @@ -219,10 +223,17 @@ gint64 nm_config_data_get_connection_default_int64(const NMConfigData *self, gint64 max, gint64 fallback); -const char *nm_config_data_get_device_config(const NMConfigData *self, - const char *property, - NMDevice *device, - gboolean *has_match); +struct _NMMatchSpecDeviceData; + +const char *nm_config_data_get_device_config(const NMConfigData *self, + const char *property, + const struct _NMMatchSpecDeviceData *match_data, + gboolean *has_match); + +const char *nm_config_data_get_device_config_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + gboolean *has_match); const char *nm_config_data_get_device_config_by_pllink(const NMConfigData *self, const char *property, @@ -230,19 +241,19 @@ const char *nm_config_data_get_device_config_by_pllink(const NMConfigData *sel const char *match_device_type, gboolean *has_match); -gboolean nm_config_data_get_device_config_boolean(const NMConfigData *self, - const char *property, - NMDevice *device, - int val_no_match, - int val_invalid); -gint64 nm_config_data_get_device_config_int64(const NMConfigData *self, - const char *property, - NMDevice *device, - int base, - gint64 min, - gint64 max, - gint64 val_no_match, - gint64 val_invalid); +gboolean nm_config_data_get_device_config_boolean_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + int val_no_match, + int val_invalid); +gint64 nm_config_data_get_device_config_int64_by_device(const NMConfigData *self, + const char *property, + NMDevice *device, + int base, + gint64 min, + gint64 max, + gint64 val_no_match, + gint64 val_invalid); const GSList *nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self, NMDevice *device, diff --git a/src/core/nm-config.c b/src/core/nm-config.c index b7445362..b8df41b7 100644 --- a/src/core/nm-config.c +++ b/src/core/nm-config.c @@ -853,6 +853,7 @@ static const ConfigGroup config_groups[] = { NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER, NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH, + NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH, NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT, NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS, @@ -878,6 +879,7 @@ static const ConfigGroup config_groups[] = { .group = NM_CONFIG_KEYFILE_GROUP_KEYFILE, .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME, NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH, + NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, ), }, { @@ -1531,7 +1533,7 @@ nm_config_keyfile_has_global_dns_config(GKeyFile *keyfile, gboolean internal) * intern_config_read: * @filename: the filename where to store the internal config * @keyfile_conf: the merged configuration from user (/etc/NM/NetworkManager.conf). - * @out_needs_rewrite: (allow-none): whether the read keyfile contains inconsistent + * @out_needs_rewrite: (out) (optional): whether the read keyfile contains inconsistent * data (compared to @keyfile_conf). If %TRUE, you might want to rewrite * the file. * @@ -2058,7 +2060,7 @@ nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled) /** * nm_config_set_values: * @self: the NMConfig instance - * @keyfile_intern_new: (allow-none): the new internal settings to set. + * @keyfile_intern_new: (nullable): the new internal settings to set. * If %NULL, it is equal to an empty keyfile. * @allow_write: only if %TRUE, allow writing the changes to file. Otherwise, * do the changes in-memory only. diff --git a/src/core/nm-config.h b/src/core/nm-config.h index d56770d0..acec8d05 100644 --- a/src/core/nm-config.h +++ b/src/core/nm-config.h @@ -150,6 +150,8 @@ extern char *_nm_config_match_env; #define NM_CONFIG_DEVICE_STATE_DIR "" NMRUNDIR "/devices" #define NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL (nm_streq("" NM_CONFIG_DEFAULT_LOGGING_AUDIT, "true")) +#define NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH_BOOL \ + (nm_streq("" NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH, "true")) typedef enum { NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN = -1, diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c index aaf9d810..92de44f1 100644 --- a/src/core/nm-connectivity.c +++ b/src/core/nm-connectivity.c @@ -981,9 +981,7 @@ check_platform_config(NMConnectivity *self, return NM_CONNECTIVITY_NONE; } - switch (addr_family) { - case AF_INET: - { + if (NM_IS_IPv4(addr_family)) { const NMPlatformIP4Route *route; gboolean found_global = FALSE; NMDedupMultiIter iter; @@ -1002,13 +1000,8 @@ check_platform_config(NMConnectivity *self, NM_SET_OUT(reason, "no global route configured"); return NM_CONNECTIVITY_LIMITED; } - break; - } - case AF_INET6: + } else { /* Route scopes aren't meaningful for IPv6 so any route is fine. */ - break; - default: - g_return_val_if_reached(FALSE); } NM_SET_OUT(reason, NULL); @@ -1050,11 +1043,12 @@ nm_connectivity_check_start(NMConnectivity *self, cb_data->concheck.con_config = _con_config_ref(priv->con_config); if (iface && ifindex > 0 && priv->enabled && priv->uri_valid) { - gboolean has_systemd_resolved; - NMConnectivityState state; - const char *reason; + gboolean has_systemd_resolved; if (platform) { + const char *reason; + NMConnectivityState state; + state = check_platform_config(self, platform, ifindex, addr_family, &reason); nm_assert((state == NM_CONNECTIVITY_UNKNOWN) == !reason); if (state != NM_CONNECTIVITY_UNKNOWN) { diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 28d9a788..5442efbf 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -468,7 +468,7 @@ _kc_invoke_callback(pid_t pid, * @log_name: for logging, the name of the processes to kill * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored. - * @callback: (allow-none): callback after the child terminated. This function will always + * @callback: (nullable): callback after the child terminated. This function will always * be invoked asynchronously. * @user_data: passed on to callback * @@ -507,16 +507,13 @@ nm_utils_kill_child_async(pid_t pid, return; } else if (ret != 0) { errsv = errno; - /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ - if (errsv != ECHILD) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", - LOG_NAME_ARGS, - nm_strerror_native(errsv), - errsv); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); - return; - } + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, + nm_strerror_native(errsv), + errsv); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); + return; } /* send the first signal. */ @@ -601,7 +598,7 @@ _sleep_duration_convert_ms_to_us(guint32 sleep_duration_msec) * @log_domain: log debug information for this domain. Errors and warnings are logged both * as %LOGD_CORE and @log_domain. * @log_name: name of the process to kill for logging. - * @child_status: (out) (allow-none): return the exit status of the child, if no error occurred. + * @child_status: (out) (optional): return the exit status of the child, if no error occurred. * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect. * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. @@ -647,15 +644,12 @@ nm_utils_kill_child_sync(pid_t pid, goto out; } else if (ret != 0) { errsv = errno; - /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ - if (errsv != ECHILD) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", - LOG_NAME_ARGS, - nm_strerror_native(errsv), - errsv); - goto out; - } + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, + nm_strerror_native(errsv), + errsv); + goto out; } /* send first signal @sig */ @@ -1154,25 +1148,26 @@ nm_utils_read_link_absolute(const char *link_file, GError **error) #define MATCH_TAG_CONFIG_ENV "env:" typedef struct { - const char *interface_name; - const char *device_type; - const char *driver; - const char *driver_version; - const char *dhcp_plugin; + /* This struct contains pre-processed data from NMMatchSpecDeviceData so + * we only need to parse it once. */ + const NMMatchSpecDeviceData *data; + const char *device_type; + const char *driver; + const char *driver_version; + const char *dhcp_plugin; struct { - const char *value; - gboolean is_parsed; - guint len; - guint8 bin[_NM_UTILS_HWADDR_LEN_MAX]; + gboolean is_parsed; + guint len; + guint8 bin[_NM_UTILS_HWADDR_LEN_MAX]; } hwaddr; struct { - const char *value; - gboolean is_parsed; - guint32 a; - guint32 b; - guint32 c; + gboolean is_parsed; + gboolean is_good; + guint32 a; + guint32 b; + guint32 c; } s390_subchannels; -} MatchDeviceData; +} MatchSpecDeviceData; static gboolean match_device_s390_subchannels_parse(const char *s390_subchannels, @@ -1240,22 +1235,25 @@ match_device_s390_subchannels_parse(const char *s390_subchannels, } static gboolean -match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_data) +match_data_s390_subchannels_eval(const char *spec_str, MatchSpecDeviceData *match_data) { - guint32 a, b, c; + guint32 a; + guint32 b; + guint32 c; if (G_UNLIKELY(!match_data->s390_subchannels.is_parsed)) { + nm_assert(!match_data->s390_subchannels.is_good); match_data->s390_subchannels.is_parsed = TRUE; - if (!match_data->s390_subchannels.value - || !match_device_s390_subchannels_parse(match_data->s390_subchannels.value, + if (!match_data->data->s390_subchannels + || !match_device_s390_subchannels_parse(match_data->data->s390_subchannels, &match_data->s390_subchannels.a, &match_data->s390_subchannels.b, &match_data->s390_subchannels.c)) { - match_data->s390_subchannels.value = NULL; return FALSE; } - } else if (!match_data->s390_subchannels.value) + match_data->s390_subchannels.is_good = TRUE; + } else if (!match_data->s390_subchannels.is_good) return FALSE; if (!match_device_s390_subchannels_parse(spec_str, &a, &b, &c)) @@ -1265,15 +1263,16 @@ match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_da } static gboolean -match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data) +match_device_hwaddr_eval(const char *spec_str, MatchSpecDeviceData *match_data) { if (G_UNLIKELY(!match_data->hwaddr.is_parsed)) { match_data->hwaddr.is_parsed = TRUE; + nm_assert(match_data->hwaddr.len == 0); - if (match_data->hwaddr.value) { + if (match_data->data->hwaddr) { gsize l; - if (!_nm_utils_hwaddr_aton(match_data->hwaddr.value, + if (!_nm_utils_hwaddr_aton(match_data->data->hwaddr, match_data->hwaddr.bin, sizeof(match_data->hwaddr.bin), &l)) @@ -1281,7 +1280,7 @@ match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data) match_data->hwaddr.len = l; } else return FALSE; - } else if (!match_data->hwaddr.len) + } else if (match_data->hwaddr.len == 0) return FALSE; return nm_utils_hwaddr_matches(spec_str, -1, match_data->hwaddr.bin, match_data->hwaddr.len); @@ -1336,7 +1335,7 @@ match_except(const char *spec_str, gboolean *out_except) } static gboolean -match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *match_data) +match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchSpecDeviceData *match_data) { if (spec_str[0] == '*' && spec_str[1] == '\0') return TRUE; @@ -1359,10 +1358,10 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m use_pattern = TRUE; } - if (match_data->interface_name) { - if (nm_streq(spec_str, match_data->interface_name)) + if (match_data->data->interface_name) { + if (nm_streq(spec_str, match_data->data->interface_name)) return TRUE; - if (use_pattern && g_pattern_match_simple(spec_str, match_data->interface_name)) + if (use_pattern && g_pattern_match_simple(spec_str, match_data->data->interface_name)) return TRUE; } return FALSE; @@ -1408,7 +1407,8 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m if (allow_fuzzy) { if (match_device_hwaddr_eval(spec_str, match_data)) return TRUE; - if (match_data->interface_name && nm_streq(spec_str, match_data->interface_name)) + if (match_data->data->interface_name + && nm_streq(spec_str, match_data->data->interface_name)) return TRUE; } @@ -1416,42 +1416,40 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m } NMMatchSpecMatchType -nm_match_spec_device(const GSList *specs, - const char *interface_name, - const char *device_type, - const char *driver, - const char *driver_version, - const char *hwaddr, - const char *s390_subchannels, - const char *dhcp_plugin) -{ - const GSList *iter; - gboolean has_match = FALSE; - gboolean has_match_except = FALSE; - gboolean has_except = FALSE; - gboolean has_not_except = FALSE; - const char *spec_str; - MatchDeviceData match_data = { - .interface_name = interface_name, - .device_type = nm_str_not_empty(device_type), - .driver = nm_str_not_empty(driver), - .driver_version = nm_str_not_empty(driver_version), - .dhcp_plugin = nm_str_not_empty(dhcp_plugin), +nm_match_spec_device(const GSList *specs, const NMMatchSpecDeviceData *data) +{ + const GSList *iter; + gboolean has_match = FALSE; + gboolean has_match_except = FALSE; + gboolean has_except = FALSE; + gboolean has_not_except = FALSE; + const char *spec_str; + MatchSpecDeviceData match_data; + + nm_assert(data); + nm_assert(!data->hwaddr || nm_utils_hwaddr_valid(data->hwaddr, -1)); + + if (!specs) + return NM_MATCH_SPEC_NO_MATCH; + + match_data = (MatchSpecDeviceData){ + .data = data, + .device_type = nm_str_not_empty(data->device_type), + .driver = nm_str_not_empty(data->driver), + .driver_version = nm_str_not_empty(data->driver_version), + .dhcp_plugin = nm_str_not_empty(data->dhcp_plugin), .hwaddr = { - .value = hwaddr, + .is_parsed = FALSE, + .len = 0, }, .s390_subchannels = { - .value = s390_subchannels, + .is_parsed = FALSE, + .is_good = FALSE, }, }; - nm_assert(!hwaddr || nm_utils_hwaddr_valid(hwaddr, -1)); - - if (!specs) - return NM_MATCH_SPEC_NO_MATCH; - for (iter = specs; iter; iter = iter->next) { gboolean except; @@ -1484,6 +1482,20 @@ nm_match_spec_device(const GSList *specs, return _match_result(has_except, has_not_except, has_match, has_match_except); } +int +nm_match_spec_match_type_to_bool(NMMatchSpecMatchType m, int no_match_value) +{ + switch (m) { + case NM_MATCH_SPEC_MATCH: + return TRUE; + case NM_MATCH_SPEC_NEG_MATCH: + return FALSE; + case NM_MATCH_SPEC_NO_MATCH: + return no_match_value; + } + return nm_assert_unreachable_val(no_match_value); +} + typedef struct { const char *uuid; const char *id; @@ -3366,7 +3378,7 @@ nm_utils_stable_id_generated_complete(const char *stable_id_generated) } static void -_stable_id_append(GString *str, const char *substitution) +_stable_id_append(NMStrBuf *str, const char *substitution) { if (!substitution) { /* Would have been nicer to append "=NIL;" to differentiate between @@ -3375,7 +3387,7 @@ _stable_id_append(GString *str, const char *substitution) * Can't do that now, as it would change behavior. */ substitution = ""; } - g_string_append_printf(str, "=%zu{%s}", strlen(substitution), substitution); + nm_str_buf_append_printf(str, "=%zu{%s}", strlen(substitution), substitution); } NMUtilsStableType @@ -3386,8 +3398,9 @@ nm_utils_stable_id_parse(const char *stable_id, const char *uuid, char **out_generated) { - gsize i, idx_start; - GString *str = NULL; + nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_232, FALSE); + gsize i; + gsize idx_start; g_return_val_if_fail(out_generated, NM_UTILS_STABLE_TYPE_RANDOM); @@ -3396,6 +3409,14 @@ nm_utils_stable_id_parse(const char *stable_id, return NM_UTILS_STABLE_TYPE_UUID; } + if (nm_streq(stable_id, "default${CONNECTION}")) { + /* This changed behavior in 1.44. Explicitly setting "default${CONNECTION}" + * the same as the built-in default that we get by not configuring + * the property. */ + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_UUID; + } + /* the stable-id allows for some dynamic by performing text-substitutions * of ${...} patterns. * @@ -3403,7 +3424,7 @@ nm_utils_stable_id_parse(const char *stable_id, * In contrast however, the process is unambiguous so that the resulting * effective id differs if: * - the original, untranslated stable-id differs - * - or any of the subsitutions differs. + * - or any of the substitution differs. * * The reason for that is, for example if you specify "${CONNECTION}" in the * stable-id, then the resulting ID should be always(!) unique for this connection. @@ -3440,28 +3461,26 @@ nm_utils_stable_id_parse(const char *stable_id, continue; } -#define CHECK_PREFIX(prefix) \ - ({ \ - gboolean _match = FALSE; \ - \ - if (NM_STR_HAS_PREFIX(&stable_id[i], "" prefix "")) { \ - _match = TRUE; \ - if (!str) \ - str = g_string_sized_new(256); \ - i += NM_STRLEN(prefix); \ - g_string_append_len(str, &(stable_id)[idx_start], i - idx_start); \ - idx_start = i; \ - } \ - _match; \ +#define CHECK_PREFIX(prefix) \ + ({ \ + gboolean _match = FALSE; \ + \ + if (NM_STR_HAS_PREFIX(&stable_id[i], "" prefix "")) { \ + _match = TRUE; \ + i += NM_STRLEN(prefix); \ + nm_str_buf_append_len(&str, &(stable_id)[idx_start], i - idx_start); \ + idx_start = i; \ + } \ + _match; \ }) if (CHECK_PREFIX("${CONNECTION}")) - _stable_id_append(str, uuid); + _stable_id_append(&str, uuid); else if (CHECK_PREFIX("${BOOT}")) - _stable_id_append(str, bootid); + _stable_id_append(&str, bootid); else if (CHECK_PREFIX("${DEVICE}")) - _stable_id_append(str, deviceid); + _stable_id_append(&str, deviceid); else if (CHECK_PREFIX("${MAC}")) - _stable_id_append(str, hwaddr); + _stable_id_append(&str, hwaddr); else if (g_str_has_prefix(&stable_id[i], "${RANDOM}")) { /* RANDOM makes not so much sense for cloned-mac-address * as the result is similar to specifying "cloned-mac-address=random". @@ -3474,8 +3493,6 @@ nm_utils_stable_id_parse(const char *stable_id, * by toggling only the stable-id property of the connection. * With RANDOM being the most short-lived, ~non-stable~ variant. */ - if (str) - g_string_free(str, TRUE); *out_generated = NULL; return NM_UTILS_STABLE_TYPE_RANDOM; } else { @@ -3494,14 +3511,14 @@ nm_utils_stable_id_parse(const char *stable_id, } #undef CHECK_PREFIX - if (!str) { + if (str.len == 0) { *out_generated = NULL; return NM_UTILS_STABLE_TYPE_STABLE_ID; } if (idx_start < i) - g_string_append_len(str, &stable_id[idx_start], i - idx_start); - *out_generated = g_string_free(str, FALSE); + nm_str_buf_append_len(&str, &stable_id[idx_start], i - idx_start); + *out_generated = nm_str_buf_finalize(&str, NULL); return NM_UTILS_STABLE_TYPE_GENERATED; } @@ -4877,25 +4894,25 @@ typedef struct { gsize out_buffer_offset; } HelperInfo; -#define _NMLOG_PREFIX_NAME "helper" -#define _NMLOG_DOMAIN LOGD_CORE -#define _NMLOG2(level, info, ...) \ - G_STMT_START \ - { \ - if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ - HelperInfo *_info = (info); \ - \ - _nm_log((level), \ - (_NMLOG_DOMAIN), \ - 0, \ - NULL, \ - NULL, \ - _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ - ",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - NM_HASH_OBFUSCATE_PTR(_info), \ - _info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } \ +#define _NMLOG2_PREFIX_NAME "nm-daemon-helper" +#define _NMLOG2_DOMAIN LOGD_CORE +#define _NMLOG2(level, info, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) { \ + HelperInfo *_info = (info); \ + \ + _nm_log((level), \ + (_NMLOG2_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + _NMLOG2_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(_info), \ + _info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ G_STMT_END static void @@ -4913,17 +4930,13 @@ helper_info_free(gpointer data) nm_clear_g_source_inst(&info->input_source); nm_clear_g_source_inst(&info->output_source); nm_clear_g_source_inst(&info->error_source); - - if (info->child_stdout != -1) - nm_close(info->child_stdout); - if (info->child_stdin != -1) - nm_close(info->child_stdin); - if (info->child_stderr != -1) - nm_close(info->child_stderr); + nm_clear_fd(&info->child_stdout); + nm_clear_fd(&info->child_stdin); + nm_clear_fd(&info->child_stderr); if (info->pid != -1) { nm_assert(info->pid > 1); - nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, _NMLOG_PREFIX_NAME, 0, NULL, NULL); + nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, "nm-daemon-helper", 0, NULL, NULL); } g_free(info); @@ -5015,8 +5028,7 @@ helper_have_data(int fd, GIOCondition condition, gpointer user_data) return G_SOURCE_CONTINUE; nm_clear_g_source_inst(&info->input_source); - nm_close(info->child_stdout); - info->child_stdout = -1; + nm_clear_fd(&info->child_stdout); _LOG2T(info, "stdout closed"); @@ -5044,9 +5056,7 @@ helper_have_err_data(int fd, GIOCondition condition, gpointer user_data) return G_SOURCE_CONTINUE; nm_clear_g_source_inst(&info->error_source); - nm_close(info->child_stderr); - info->child_stderr = -1; - + nm_clear_fd(&info->child_stderr); return G_SOURCE_CONTINUE; } @@ -5103,23 +5113,21 @@ nm_utils_spawn_helper(const char *const *args, gs_free_error GError *error = NULL; gs_free char *commands = NULL; HelperInfo *info; - int fd_flags; const char *const *arg; + GMainContext *context; + gsize n; nm_assert(args && args[0]); info = g_new(HelperInfo, 1); *info = (HelperInfo){ - .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data), - .child_stdin = -1, - .child_stdout = -1, - .pid = -1, + .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data), }; if (!g_spawn_async_with_pipes("/", (char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"), (char **) NM_MAKE_STRV(), - G_SPAWN_DO_NOT_REAP_CHILD, + G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &info->pid, @@ -5142,27 +5150,45 @@ nm_utils_spawn_helper(const char *const *args, _LOG2D(info, "spawned process with args: %s", (commands = g_strjoinv(" ", (char **) args))); - info->child_watch_source = g_child_watch_source_new(info->pid); - g_source_set_callback(info->child_watch_source, - G_SOURCE_FUNC(helper_child_terminated), - info, - NULL); - g_source_attach(info->child_watch_source, g_main_context_get_thread_default()); + context = g_task_get_context(info->task); + + /* The async function makes a lukewarm attempt to honor the current thread default + * context. However, it later uses nm_utils_kill_child_async() which always uses + * g_main_context_default(). For now, the function really can only be used with the + * main context. */ + nm_assert(context == g_main_context_default()); + + /* We are using a GChildWatchSource in combination with kill()/waitpid() + * (where helper_info_free() clears the source and calls + * nm_utils_kill_child_async()). That leads to races where glib might have + * already reaped the process and our waitpid() call fails with: + * + * <error> [TIMESTAMP] kill child process 'nm-daemon-helper' (PID): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending SIGKILL (9) + * + * This is a bug in glib, addressed by [1]. Maybe there should be a + * workaround here, and not using the child watcher? + * + * [1] https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3353 + */ + info->child_watch_source = nm_g_child_watch_source_new(info->pid, + G_PRIORITY_DEFAULT, + helper_child_terminated, + info, + NULL); + g_source_attach(info->child_watch_source, context); info->timeout_source = nm_g_timeout_source_new_seconds(20, G_PRIORITY_DEFAULT, helper_timeout, info, NULL); - g_source_attach(info->timeout_source, g_main_context_get_thread_default()); + g_source_attach(info->timeout_source, context); - /* Set file descriptors as non-blocking */ - fd_flags = fcntl(info->child_stdin, F_GETFL, 0); - fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK); - fd_flags = fcntl(info->child_stdout, F_GETFL, 0); - fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK); - fd_flags = fcntl(info->child_stderr, F_GETFL, 0); - fcntl(info->child_stderr, F_SETFL, fd_flags | O_NONBLOCK); + nm_io_fcntl_setfl_update_nonblock(info->child_stdin); + nm_io_fcntl_setfl_update_nonblock(info->child_stdout); + nm_io_fcntl_setfl_update_nonblock(info->child_stderr); /* Watch process stdin */ - info->out_buffer = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, TRUE); + for (n = 1, arg = args; *arg; arg++) + n += strlen(*arg) + 1u; + info->out_buffer = NM_STR_BUF_INIT(n, TRUE); for (arg = args; *arg; arg++) { nm_str_buf_append(&info->out_buffer, *arg); nm_str_buf_append_c(&info->out_buffer, '\0'); @@ -5173,7 +5199,7 @@ nm_utils_spawn_helper(const char *const *args, helper_can_write, info, NULL); - g_source_attach(info->output_source, g_main_context_get_thread_default()); + g_source_attach(info->output_source, context); /* Watch process stdout */ info->in_buffer = NM_STR_BUF_INIT(0, FALSE); @@ -5183,7 +5209,7 @@ nm_utils_spawn_helper(const char *const *args, helper_have_data, info, NULL); - g_source_attach(info->input_source, g_main_context_get_thread_default()); + g_source_attach(info->input_source, context); /* Watch process stderr */ info->err_buffer = NM_STR_BUF_INIT(0, FALSE); @@ -5193,7 +5219,7 @@ nm_utils_spawn_helper(const char *const *args, helper_have_err_data, info, NULL); - g_source_attach(info->error_source, g_main_context_get_thread_default()); + g_source_attach(info->error_source, context); if (cancellable) { gulong signal_id; diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index bc936496..55112504 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -193,14 +193,20 @@ typedef enum { NM_MATCH_SPEC_NEG_MATCH = 2, } NMMatchSpecMatchType; -NMMatchSpecMatchType nm_match_spec_device(const GSList *specs, - const char *interface_name, - const char *device_type, - const char *driver, - const char *driver_version, - const char *hwaddr, - const char *s390_subchannels, - const char *dhcp_plugin); +int nm_match_spec_match_type_to_bool(NMMatchSpecMatchType m, int no_match_value); + +typedef struct _NMMatchSpecDeviceData { + const char *interface_name; + const char *device_type; + const char *driver; + const char *driver_version; + const char *dhcp_plugin; + const char *hwaddr; + const char *s390_subchannels; +} NMMatchSpecDeviceData; + +NMMatchSpecMatchType nm_match_spec_device(const GSList *specs, const NMMatchSpecDeviceData *data); + NMMatchSpecMatchType nm_match_spec_config(const GSList *specs, guint nm_version, const char *env); GSList *nm_match_spec_split(const char *value); char *nm_match_spec_join(GSList *specs); @@ -262,18 +268,17 @@ _nmtst_auto_utils_host_id_context_pop(const char *const *unused) nmtst_utils_host_id_pop(); } -#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id) \ - _nm_unused nm_auto(_nmtst_auto_utils_host_id_context_pop) const char *const NM_UNIQ_T( \ - _host_id_context_, \ - uniq) = ({ \ - const gint64 _timestamp_ns = 1631000672; \ - \ - nmtst_utils_host_id_push((const guint8 *) "" host_id "", \ - NM_STRLEN(host_id), \ - TRUE, \ - &_timestamp_ns); \ - "" host_id ""; \ - }) +#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id) \ + _nm_unused nm_auto(_nmtst_auto_utils_host_id_context_pop) \ + const char *const NM_UNIQ_T(_host_id_context_, uniq) = ({ \ + const gint64 _timestamp_ns = 1631000672; \ + \ + nmtst_utils_host_id_push((const guint8 *) "" host_id "", \ + NM_STRLEN(host_id), \ + TRUE, \ + &_timestamp_ns); \ + "" host_id ""; \ + }) #define NMTST_UTILS_HOST_ID_CONTEXT(host_id) _NMTST_UTILS_HOST_ID_CONTEXT(NM_UNIQ, host_id) diff --git a/src/core/nm-dbus-manager.c b/src/core/nm-dbus-manager.c index af7de8c4..0bde5971 100644 --- a/src/core/nm-dbus-manager.c +++ b/src/core/nm-dbus-manager.c @@ -1410,48 +1410,23 @@ nm_dbus_manager_start(NMDBusManager *self, } gboolean -nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name) +nm_dbus_manager_request_name_sync(NMDBusManager *self) { NMDBusManagerPrivate *priv; gs_free_error GError *error = NULL; gs_unref_variant GVariant *ret = NULL; guint32 result; - guint registration_id; g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); - /* Create the D-Bus connection and registering the name synchronously. - * That is necessary because we need to exit right away if we can't - * acquire the name despite connecting to the bus successfully. - * It means that something is gravely broken -- such as another NetworkManager - * instance running. */ - priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); - if (!priv->main_dbus_connection) { - _LOGE("cannot connect to D-Bus: %s", error->message); - return FALSE; - } - - g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE); - - if (!request_name) { - _LOGD("D-Bus connection created"); + if (priv->objmgr_registration_id == 0) { + /* Do nothing. We're presumably in the configure-and-quit mode. */ return TRUE; } - registration_id = g_dbus_connection_register_object( - priv->main_dbus_connection, - OBJECT_MANAGER_SERVER_BASE_PATH, - NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr), - &dbus_vtable_objmgr, - self, - NULL, - &error); - if (!registration_id) { - _LOGE("failure to register object manager: %s", error->message); - return FALSE; - } + g_return_val_if_fail(G_IS_DBUS_CONNECTION(priv->main_dbus_connection), FALSE); ret = g_dbus_connection_call_sync( priv->main_dbus_connection, @@ -1465,12 +1440,12 @@ nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name) -1, NULL, &error); + if (!ret) { _LOGE("fatal failure to acquire D-Bus service \"%s" ": %s", NM_DBUS_SERVICE, error->message); - g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); return FALSE; } @@ -1479,13 +1454,55 @@ nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name) _LOGE("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken", NM_DBUS_SERVICE, (guint) result); - g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); + return FALSE; + } + + _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE); + return TRUE; +} + +gboolean +nm_dbus_manager_setup(NMDBusManager *self) +{ + NMDBusManagerPrivate *priv; + gs_free_error GError *error = NULL; + guint registration_id; + + g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); + + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + g_return_val_if_fail(!priv->main_dbus_connection, FALSE); + + /* Create the D-Bus connection and registering the name synchronously. + * That is necessary because we need to exit right away if we can't + * acquire the name despite connecting to the bus successfully. + * It means that something is gravely broken -- such as another NetworkManager + * instance running. */ + priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!priv->main_dbus_connection) { + _LOGE("cannot connect to D-Bus: %s", error->message); + return FALSE; + } + + g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE); + + registration_id = g_dbus_connection_register_object( + priv->main_dbus_connection, + OBJECT_MANAGER_SERVER_BASE_PATH, + NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr), + &dbus_vtable_objmgr, + self, + NULL, + &error); + if (!registration_id) { + _LOGE("failure to register object manager: %s", error->message); return FALSE; } priv->objmgr_registration_id = registration_id; - _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE); + _LOGD("D-Bus connection created and ObjectManager object registered"); return TRUE; } diff --git a/src/core/nm-dbus-manager.h b/src/core/nm-dbus-manager.h index b68161db..078dbdd2 100644 --- a/src/core/nm-dbus-manager.h +++ b/src/core/nm-dbus-manager.h @@ -37,7 +37,9 @@ typedef void (*NMDBusManagerSetPropertyHandler)(NMDBusObject GVariant *value, gpointer user_data); -gboolean nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name); +gboolean nm_dbus_manager_setup(NMDBusManager *self); + +gboolean nm_dbus_manager_request_name_sync(NMDBusManager *self); GDBusConnection *nm_dbus_manager_get_dbus_connection(NMDBusManager *self); diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c index 03f1a9a5..45dab093 100644 --- a/src/core/nm-firewall-utils.c +++ b/src/core/nm-firewall-utils.c @@ -74,7 +74,7 @@ _nft_ifname_valid(const char *str) return NULL; } } - if (i >= NMP_IFNAMSIZ) + if (i >= NM_IFNAMSIZ) return NULL; return str; @@ -154,10 +154,10 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c nm_str_buf_append(&strbuf, prefix); ip_iface_len = strlen(ip_iface); - G_STATIC_ASSERT_EXPR(NMP_IFNAMSIZ == 16); - if (ip_iface_len >= NMP_IFNAMSIZ) { + G_STATIC_ASSERT_EXPR(NM_IFNAMSIZ == 16); + if (ip_iface_len >= NM_IFNAMSIZ) { nm_assert_not_reached(); - ip_iface_len = NMP_IFNAMSIZ - 1; + ip_iface_len = NM_IFNAMSIZ - 1; } if (NM_STRCHAR_ALL(ip_iface, @@ -763,13 +763,15 @@ nm_firewall_nft_stdio_mlag(gboolean up, const char *bond_ifname, const char *const *bond_ifnames_down, const char *const *active_members, - const char *const *previous_members) + const char *const *previous_members, + gboolean with_counters) { nm_auto_str_buf NMStrBuf strbuf_table_name = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_32, FALSE); nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); const char *table_name; gsize i; + const char *const s_counter = with_counters ? " counter" : ""; if (NM_MORE_ASSERTS > 10 && active_members) { /* No duplicates. We make certain assumptions here, and we don't @@ -876,9 +878,10 @@ nm_firewall_nft_stdio_mlag(gboolean up, _append(&strbuf, "add rule netdev %s %s pkttype {" " broadcast, multicast " - "} counter drop", + "}%s drop", table_name, - chain_name); + chain_name, + s_counter); } /* OVS SLB rule 2 @@ -905,15 +908,17 @@ nm_firewall_nft_stdio_mlag(gboolean up, table_name, bond_ifname); _append(&strbuf, - "add rule netdev %s tx-snoop-source-mac set update ether saddr . vlan id" - " timeout 5s @macset-tagged counter return" + "add rule netdev %s tx-snoop-source-mac set update ether saddr . vlan id " + "timeout 5s @macset-tagged%s return" "", /* tagged */ - table_name); + table_name, + s_counter); _append(&strbuf, - "add rule netdev %s tx-snoop-source-mac set update ether saddr" - " timeout 5s @macset-untagged counter" + "add rule netdev %s tx-snoop-source-mac set update ether saddr timeout 5s " + "@macset-untagged%s" "", /* untagged*/ - table_name); + table_name, + s_counter); _append(&strbuf, "add chain netdev %s rx-drop-looped-packets {" @@ -921,18 +926,20 @@ nm_firewall_nft_stdio_mlag(gboolean up, "}", table_name, bond_ifname); + _append( + &strbuf, + "add rule netdev %s rx-drop-looped-packets ether saddr . vlan id @macset-tagged%s drop", + table_name, + s_counter); _append(&strbuf, - "add rule netdev %s rx-drop-looped-packets ether saddr . vlan id" - " @macset-tagged counter drop", - table_name); - _append(&strbuf, - "add rule netdev %s rx-drop-looped-packets ether type vlan counter return" + "add rule netdev %s rx-drop-looped-packets ether type vlan%s return" "", /* avoid looking up tagged packets in untagged table */ - table_name); + table_name, + s_counter); _append(&strbuf, - "add rule netdev %s rx-drop-looped-packets ether saddr @macset-untagged" - " counter drop", - table_name); + "add rule netdev %s rx-drop-looped-packets ether saddr @macset-untagged%s drop", + table_name, + s_counter); } out: diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h index ca138ccf..9f13a512 100644 --- a/src/core/nm-firewall-utils.h +++ b/src/core/nm-firewall-utils.h @@ -39,6 +39,7 @@ GBytes *nm_firewall_nft_stdio_mlag(gboolean up, const char *bond_ifname, const char *const *bond_ifnames_down, const char *const *active_members, - const char *const *previous_members); + const char *const *previous_members, + gboolean with_counters); #endif /* __NM_FIREWALL_UTILS_H__ */ diff --git a/src/core/nm-keep-alive.c b/src/core/nm-keep-alive.c index e147163c..3ab5c36e 100644 --- a/src/core/nm-keep-alive.c +++ b/src/core/nm-keep-alive.c @@ -364,7 +364,7 @@ nm_keep_alive_disarm(NMKeepAlive *self) /** * nm_keep_alive_destroy: - * @self: (allow-none): the #NMKeepAlive instance to destroy. + * @self: (nullable): the #NMKeepAlive instance to destroy. * * This does 3 things in one: * diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index d5dedb9c..96274ba9 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2606,7 +2606,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, int addr_family, guint32 route_table, guint32 route_metric, - gboolean force_commit, const NML3ConfigData *source) { const int IS_IPv4 = NM_IS_IPv4(addr_family); @@ -2651,7 +2650,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, self->ifindex, route_table, route_metric, - force_commit, &r_stack.r4); if (r) nm_l3_config_data_add_route(self, addr_family, NULL, r); @@ -2687,13 +2685,12 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, } rx.r6 = (NMPlatformIP6Route){ - .ifindex = self->ifindex, - .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, - .table_coerced = nm_platform_route_table_coerce(route_table), - .metric = route_metric, - .network = *a6, - .plen = plen, - .r_force_commit = force_commit, + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .network = *a6, + .plen = plen, }; nm_platform_ip_route_normalize(addr_family, &rx.rx); @@ -3199,7 +3196,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, NMPlatformIPXAddress a; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, - .force_commit = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_a() \ @@ -3232,12 +3228,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready); } - if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT - && (!!hook_result.force_commit) != a_src->a_force_commit) { - _ensure_a(); - a.ax.a_force_commit = (!!hook_result.force_commit); - } - nm_l3_config_data_add_address_full(self, addr_family, a_src == &a.ax ? NULL : obj, @@ -3257,7 +3247,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, NMPlatformIPXRoute r; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, - .force_commit = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_r() \ @@ -3283,12 +3272,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, r.rx.ifindex = self->ifindex; } - if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT - && (!!hook_result.force_commit) != r_src->r_force_commit) { - _ensure_r(); - r.rx.r_force_commit = (!!hook_result.force_commit); - } - if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) { if (r_src->table_any) { _ensure_r(); diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index bfab04d9..80abb00d 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -137,7 +137,6 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex *mu typedef struct { NMOptionBool ip4acd_not_ready; - NMOptionBool force_commit; } NML3ConfigMergeHookResult; typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd, @@ -164,7 +163,6 @@ void nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, int addr_family, guint32 route_table, guint32 route_metric, - gboolean force_commit, const NML3ConfigData *source); /*****************************************************************************/ diff --git a/src/core/nm-l3-ipv6ll.c b/src/core/nm-l3-ipv6ll.c index 0133ebe6..38aa98fc 100644 --- a/src/core/nm-l3-ipv6ll.c +++ b/src/core/nm-l3-ipv6ll.c @@ -420,9 +420,7 @@ _lladdr_handle_changed(NML3IPv6LL *self, gboolean force_commit) NM_DNS_PRIORITY_DEFAULT_NORMAL, NM_L3_ACD_DEFEND_TYPE_ALWAYS, 0, - /* Even if the address was removed from platform, it must - * be re-added, hence FORCE_ONCE. */ - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, + NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_NONE)) changed = TRUE; } else { @@ -667,17 +665,13 @@ _nm_l3_ipv6ll_new(NML3Cfg *l3cfg, }; if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) { - char sbuf_token[sizeof(self->addrgen.token.iid) * 3]; + char sbuf_token[INET6_ADDRSTRLEN]; self->addrgen.token.iid = *token_iid; _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d, token=%s%s", NM_HASH_OBFUSCATE_PTR(l3cfg), nm_l3cfg_get_ifindex(l3cfg), - nm_utils_bin2hexstr_full(&self->addrgen.token.iid, - sizeof(self->addrgen.token.iid), - ':', - FALSE, - sbuf_token), + nm_utils_inet6_interface_identifier_to_token(&self->addrgen.token.iid, sbuf_token), self->assume ? ", assume" : ""); } else { self->addrgen.stable_privacy.ifname = g_strdup(ifname); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index a49654fe..3c2d3ec8 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -11,6 +11,7 @@ #include <linux/if_ether.h> #include <linux/rtnetlink.h> +#include "libnm-glib-aux/nm-prioq.h" #include "libnm-glib-aux/nm-time-utils.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-object.h" @@ -123,25 +124,34 @@ typedef struct { CList os_lst; - /* If we have a timeout pending, we link the instance to - * self->priv.p->obj_state_temporary_not_available_lst_head. */ - CList os_temporary_not_available_lst; - /* If a NMPObject is no longer to be configured (but was configured * during a previous commit), then we need to remember it so that the * next commit can delete the address/route in kernel. It becomes a zombie. */ CList os_zombie_lst; - /* We might want to configure "obj" in platform, but it's currently not possible. - * For example, certain IPv6 routes can only be added after the IPv6 address - * becomes non-tentative (*sigh*). In such a case, we need to remember that, and - * retry later. If this timestamp is set to a non-zero value, then it means - * we tried to configure the obj (at that timestamp) and failed, but we are - * waiting to retry. + /* Used by _handle_routes_failed() mechanism. If "os_plobj" is set, then + * this is meaningless but should be set to zero. + * + * If set to a non-zero value, this means adding the object failed. Until + * "os_failedobj_expiry_msec" we are still waiting whether we would be able to + * configure the object. Afterwards, we consider the element failed. * - * See also self->priv.p->obj_state_temporary_not_available_lst_head - * and self->priv.p->obj_state_temporary_not_available_timeout_source. */ - gint64 os_temporary_not_available_timestamp_msec; + * Depending on "os_failedobj_prioq_idx", we are currently waiting whether the + * condition can resolve itself or becomes a failure. */ + gint64 os_failedobj_expiry_msec; + + /* The index into the "priv->failedobj_prioq" queue for objects that are failed. + * - this field is meaningless in case "os_plobj" is set (but it should be + * set to NM_PRIOQ_IDX_NULL). + * - otherwise, if "os_failedobj_expiry_msec" is 0, no error was detected so + * far. The index should be set to NM_PRIOQ_IDX_NULL. + * - otherwise, if the index is NM_PRIOQ_IDX_NULL it means that the object + * is not tracked by the queue, no grace timer is pending, and the object + * is considered failed. + * - otherwise, the index is used for tracking the element in the queue. + * It means, we are currently waiting to decide whether this will be a + * failure or not. */ + guint os_failedobj_prioq_idx; /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but * now no longer), it needs to be deleted from platform. This ratelimits the time @@ -206,7 +216,6 @@ typedef struct { guint32 acd_timeout_msec_confdata; NML3AcdDefendType acd_defend_type_confdata : 3; bool dirty_confdata : 1; - gboolean force_commit_once : 1; } L3ConfigData; struct _NML3CfgBlockHandle { @@ -241,7 +250,6 @@ typedef struct _NML3CfgPrivate { CList obj_state_lst_head; CList obj_state_zombie_lst_head; - CList obj_state_temporary_not_available_lst_head; GHashTable *acd_ipv4_addresses_on_link; @@ -288,12 +296,22 @@ typedef struct _NML3CfgPrivate { guint64 pseudo_timestamp_counter; - GSource *obj_state_temporary_not_available_timeout_source; + NMPrioq failedobj_prioq; + GSource *failedobj_timeout_source; + gint64 failedobj_timeout_expiry_msec; NML3CfgCommitType commit_on_idle_type; gint8 commit_reentrant_count; + union { + struct { + gint8 commit_reentrant_count_ip_address_sync_6; + gint8 commit_reentrant_count_ip_address_sync_4; + }; + gint8 commit_reentrant_count_ip_address_sync_x[2]; + }; + /* The value that was set before we touched the sysctl (this only is * meaningful if "ip6_privacy_set" is true. At the end, we want to restore * this value. */ @@ -340,6 +358,9 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) #define _MPTCP_TAG(self, IS_IPv4) ((gconstpointer) (&(((const char *) (self))[2 + (!(IS_IPv4))]))) +#define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \ + ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)])) + /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE @@ -410,8 +431,6 @@ static NM_UTILS_ENUM2STR_DEFINE( NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, "pre-commit"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"), - NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, - "routes-temporary-not-available-expired"), NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), ); static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string, @@ -754,51 +773,51 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp /*****************************************************************************/ -#define nm_assert_obj_state(self, obj_state) \ - G_STMT_START \ - { \ - if (NM_MORE_ASSERTS > 0) { \ - const NML3Cfg *_self = (self); \ - const ObjStateData *_obj_state = (obj_state); \ - \ - nm_assert(_obj_state); \ - nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \ - NMP_OBJECT_TYPE_IP4_ADDRESS, \ - NMP_OBJECT_TYPE_IP6_ADDRESS, \ - NMP_OBJECT_TYPE_IP4_ROUTE, \ - NMP_OBJECT_TYPE_IP6_ROUTE)); \ - nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ - nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ - == c_list_is_empty(&_obj_state->os_temporary_not_available_lst)); \ - if (_self) { \ - if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \ - nm_assert(_self->priv.p->combined_l3cd_commited); \ - \ - if (NM_MORE_ASSERTS > 5) { \ - nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \ - &_obj_state->os_lst)); \ - nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ - || c_list_contains( \ - &_self->priv.p->obj_state_temporary_not_available_lst_head, \ - &_obj_state->os_temporary_not_available_lst)); \ - nm_assert(_obj_state->os_plobj \ - == nm_platform_lookup_obj(_self->priv.platform, \ - NMP_CACHE_ID_TYPE_OBJECT_TYPE, \ - _obj_state->obj)); \ - nm_assert( \ - c_list_is_empty(&obj_state->os_zombie_lst) \ - ? (_obj_state->obj \ - == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \ - _self->priv.p->combined_l3cd_commited, \ - _obj_state->obj))) \ - : (!nm_l3_config_data_lookup_obj( \ - _self->priv.p->combined_l3cd_commited, \ - _obj_state->obj))); \ - } \ - } \ - } \ - } \ - } \ +#define nm_assert_obj_state(self, obj_state) \ + G_STMT_START \ + { \ + if (NM_MORE_ASSERTS > 0) { \ + const NML3Cfg *_self = (self); \ + const ObjStateData *_obj_state = (obj_state); \ + \ + nm_assert(_obj_state); \ + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \ + NMP_OBJECT_TYPE_IP4_ADDRESS, \ + NMP_OBJECT_TYPE_IP6_ADDRESS, \ + NMP_OBJECT_TYPE_IP4_ROUTE, \ + NMP_OBJECT_TYPE_IP6_ROUTE)); \ + nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ + nm_assert(_obj_state->os_failedobj_expiry_msec != 0 \ + || _obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || !_obj_state->os_plobj); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 \ + || c_list_is_empty(&_obj_state->os_zombie_lst)); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || _obj_state->obj); \ + if (_self) { \ + if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \ + nm_assert(_self->priv.p->combined_l3cd_commited); \ + \ + if (NM_MORE_ASSERTS > 5) { \ + nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \ + &_obj_state->os_lst)); \ + nm_assert(_obj_state->os_plobj \ + == nm_platform_lookup_obj(_self->priv.platform, \ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, \ + _obj_state->obj)); \ + nm_assert( \ + c_list_is_empty(&obj_state->os_zombie_lst) \ + ? (_obj_state->obj \ + == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))) \ + : (!nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))); \ + } \ + } \ + } \ + } \ + } \ G_STMT_END static ObjStateData * @@ -808,13 +827,14 @@ _obj_state_data_new(const NMPObject *obj, const NMPObject *plobj) obj_state = g_slice_new(ObjStateData); *obj_state = (ObjStateData){ - .obj = nmp_object_ref(obj), - .os_plobj = nmp_object_ref(plobj), - .os_was_in_platform = !!plobj, - .os_nm_configured = FALSE, - .os_dirty = FALSE, - .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst), - .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst), + .obj = nmp_object_ref(obj), + .os_plobj = nmp_object_ref(plobj), + .os_was_in_platform = !!plobj, + .os_nm_configured = FALSE, + .os_dirty = FALSE, + .os_failedobj_expiry_msec = 0, + .os_failedobj_prioq_idx = NM_PRIOQ_IDX_NULL, + .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst), }; return obj_state; } @@ -824,9 +844,10 @@ _obj_state_data_free(gpointer data) { ObjStateData *obj_state = data; + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); + c_list_unlink_stale(&obj_state->os_lst); c_list_unlink_stale(&obj_state->os_zombie_lst); - c_list_unlink_stale(&obj_state->os_temporary_not_available_lst); nmp_object_unref(obj_state->obj); nmp_object_unref(obj_state->os_plobj); nm_g_slice_free(obj_state); @@ -864,15 +885,17 @@ _obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_si } else if (obj_state->os_was_in_platform) nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform"); - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { + if (obj_state->os_failedobj_expiry_msec > 0) { nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - nm_strbuf_append( - &buf, - &buf_size, - ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d", - (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000, - (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000)); - } + nm_strbuf_append(&buf, + &buf_size, + ", %s-since=%" G_GINT64_FORMAT ".%03d", + (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) ? "failed" + : "failed-wait", + (obj_state->os_failedobj_expiry_msec - now_msec) / 1000, + (int) ((obj_state->os_failedobj_expiry_msec - now_msec) % 1000)); + } else + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); return buf0; } @@ -934,6 +957,7 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) { /* this is a zombie. We can forget about it.*/ + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); nm_clear_nmp_object(&obj_state->os_plobj); c_list_unlink(&obj_state->os_zombie_lst); _LOGD("obj-state: zombie gone (untrack): %s", @@ -949,8 +973,23 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole if (in_platform) { nmp_object_ref_set(&obj_state->os_plobj, obj); obj_state->os_was_in_platform = TRUE; - _LOGD("obj-state: appeared in platform: %s", - _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + if (obj_state->os_failedobj_expiry_msec != 0) { + obj_state->os_failedobj_expiry_msec = 0; + if (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) { + _LOGT("obj-state: failed-obj: object now configured after failed earlier: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } else { + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); + _LOGT("obj-state: failed-obj: object now configured after waiting: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } + } else { + _LOGD("obj-state: appeared in platform: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); goto out; } @@ -1039,6 +1078,7 @@ _obj_states_update_all(NML3Cfg *self) continue; if (obj_state->os_plobj && obj_state->os_nm_configured) { + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head, &obj_state->os_zombie_lst); obj_state->os_zombie_count = ZOMBIE_COUNT_START; @@ -1049,6 +1089,9 @@ _obj_states_update_all(NML3Cfg *self) _LOGD("obj-state: untrack: %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); g_hash_table_iter_remove(&h_iter); } } @@ -1086,16 +1129,32 @@ _obj_states_sync_filter(NML3Cfg *self, const NMPObject *obj, NML3CfgCommitType c return TRUE; } - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { - /* we currently try to configure this address (but failed earlier). - * Definitely retry. */ - return TRUE; - } - - if (!obj_state->os_plobj && commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY - && !nmp_object_get_force_commit(obj)) - return FALSE; - + /* One goal would be that we don't forcefully re-add routes which were + * externally removed (e.g. by the user via `ip route del`). + * + * However, + * + * - some routes get automatically deleted by kernel (for example, + * when we have an IPv4 route with RTA_PREFSRC set and the referenced + * IPv4 address gets removed). The absence of such a route does not + * mean that the user doesn't want the route there. It means, kernel + * removed it because of some consistency check, but we want it back. + * - a route with a non-zero gateway requires that the gateway is + * directly reachable via an onlink route. The rules for this are + * complex, but kernel will reject adding a route which has such a + * gateway. If the user manually removed the needed onlink route, the + * gateway route cannot be added in kernel ("Nexthop has invalid + * gateway"). To handle that is a nightmare, so we always ensure that + * the onlink route is there. + * - a route with RTA_PREFSRC requires that such an address is + * configured otherwise kernel rejects adding the route with "Invalid + * prefsrc address"/"Invalid source address". Removing an address can + * thus prevent adding the route, which is a problem for us. + * + * So the goal is not tenable and causes problems. NetworkManager will + * try hard to re-add routes and address that it thinks should be + * present. If you externally remove them, then you are starting a + * fight where NetworkManager tries to re-add them on every commit. */ return TRUE; } @@ -1129,6 +1188,7 @@ static void _commit_collect_routes(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type, + gboolean any_addrs, GPtrArray **routes, GPtrArray **routes_nodev) { @@ -1154,6 +1214,24 @@ _commit_collect_routes(NML3Cfg *self, else { nm_assert(NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex == self->priv.ifindex); + if (!any_addrs) { + /* This is a unicast route (or a similar route, which has an + * ifindex). + * + * However, during this commit we don't plan to configure any + * IP addresses. With `ipvx.method=manual` that should not be + * possible. More likely, this is because the profile has + * `ipvx.method=auto` and static routes. + * + * Don't configure any such routes before we also have at least + * one IP address. + * + * This code applies to IPv4 and IPv6, however for IPv6 we + * early on configure a link local address, so in practice the + * branch is not taken for IPv6. */ + continue; + } + if (IS_IPv4 && NMP_OBJECT_CAST_IP4_ROUTE(obj)->weight > 0) { /* This route needs to be registered as ECMP route. */ nm_netns_ip_route_ecmp_register(self->priv.netns, self, obj); @@ -1246,6 +1324,7 @@ _obj_state_zombie_lst_get_prune_lists(NML3Cfg *self, if (--obj_state->os_zombie_count == 0) { _LOGD("obj-state: prune zombie (untrack): %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); continue; } @@ -1280,6 +1359,7 @@ _obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family) if (--obj_state->os_zombie_count == 0) { _LOGD("obj-state: zombie pruned during reapply (untrack): %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); continue; } @@ -3017,16 +3097,14 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr) /*****************************************************************************/ gboolean -nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family) +nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family) { ObjStateData *obj_state; nm_assert(NM_IS_L3CFG(self)); nm_assert_addr_family(addr_family); - c_list_for_each_entry (obj_state, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { + nm_prioq_for_each (&self->priv.p->failedobj_prioq, obj_state) { if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) == addr_family) return TRUE; } @@ -3413,8 +3491,7 @@ nm_l3cfg_add_config(NML3Cfg *self, .acd_timeout_msec_confdata = acd_timeout_msec, .priority_confdata = priority, .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter, - .force_commit_once = NM_FLAGS_HAS(config_flags, NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE), - .dirty_confdata = FALSE, + .dirty_confdata = FALSE, }; changed = TRUE; } else { @@ -3611,7 +3688,6 @@ typedef struct { NML3Cfg *self; gconstpointer tag; bool to_commit; - bool force_commit_once; } L3ConfigMergeHookAddObjData; static gboolean @@ -3629,9 +3705,6 @@ _l3_hook_add_obj_cb(const NML3ConfigData *l3cd, nm_assert(obj); nm_assert(hook_result); nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); - nm_assert(hook_result->force_commit == NM_OPTION_BOOL_DEFAULT); - - hook_result->force_commit = hook_data->force_commit_once; switch (NMP_OBJECT_GET_TYPE(obj)) { case NMP_OBJECT_TYPE_IP4_ADDRESS: @@ -3787,8 +3860,7 @@ _l3cfg_update_combined_config(NML3Cfg *self, if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD)) continue; - hook_data.tag = l3cd_data->tag_confdata; - hook_data.force_commit_once = l3cd_data->force_commit_once; + hook_data.tag = l3cd_data->tag_confdata; nm_l3_config_data_merge(l3cd, l3cd_data->l3cd, @@ -3846,7 +3918,6 @@ _l3cfg_update_combined_config(NML3Cfg *self, IS_IPv4 ? AF_INET : AF_INET6, l3cd_data->default_route_table_x[IS_IPv4], l3cd_data->default_route_metric_x[IS_IPv4], - l3cd_data->force_commit_once, l3cd_data->l3cd); } } @@ -3921,79 +3992,91 @@ out: /*****************************************************************************/ static gboolean -_routes_temporary_not_available_timeout(gpointer user_data) +_failedobj_timeout_cb(gpointer user_data) { - NML3Cfg *self = NM_L3CFG(user_data); - ObjStateData *obj_state; - gint64 now_msec; - gint64 expiry_msec; + NML3Cfg *self = NM_L3CFG(user_data); - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); + _LOGT("obj-state: failed-obj: handle timeout"); - obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, - ObjStateData, - os_temporary_not_available_lst); + nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source); - if (!obj_state) - return G_SOURCE_CONTINUE; + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); - now_msec = nm_utils_get_monotonic_timestamp_msec(); + return G_SOURCE_CONTINUE; +} - expiry_msec = obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; +static void +_failedobj_reschedule(NML3Cfg *self, gint64 now_msec) +{ + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + ObjStateData *obj_state; - if (now_msec < expiry_msec) { - /* the timeout is not yet reached. Restart the timer... */ - self->priv.p->obj_state_temporary_not_available_timeout_source = - nm_g_timeout_add_source(expiry_msec - now_msec, - _routes_temporary_not_available_timeout, - self); - return G_SOURCE_CONTINUE; + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + +again: + obj_state = nm_prioq_peek(&self->priv.p->failedobj_prioq); + + if (obj_state && obj_state->os_failedobj_expiry_msec <= now_msec) { + /* The object is already expired... */ + + /* we shouldn't have a "os_plobj", because if we had, we should have + * removed "obj_state" from the queue. */ + nm_assert(!obj_state->os_plobj); + + /* we need to have an "obj", otherwise the "obj_state" instance + * shouldn't exist (as it also has not "os_plobj"). */ + nm_assert(obj_state->obj); + + /* It seems that nm_platform_ip_route_sync() signaled success and did + * not report the route as missing. Regardless, it is still not + * configured and the timeout expired. */ + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); + _LOGW( + "missing IPv%c route: %s", + nm_utils_addr_family_to_char(NMP_OBJECT_GET_TYPE(obj_state->obj)), + nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + goto again; } - /* One (or several) routes expired. We emit a signal, but we don't schedule it again. - * We expect the callers to commit again, which will one last time try to configure - * the route. If that again fails, we detect the timeout, log a warning and don't - * track the object as not temporary-not-available anymore. */ - _nm_l3cfg_emit_signal_notify_simple( - self, - NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED); - return G_SOURCE_CONTINUE; + if (!obj_state) { + if (nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source)) + _LOGT("obj-state: failed-obj: cancel timeout"); + return; + } + + if (nm_g_timeout_reschedule(&self->priv.p->failedobj_timeout_source, + &self->priv.p->failedobj_timeout_expiry_msec, + obj_state->os_failedobj_expiry_msec, + _failedobj_timeout_cb, + self)) { + _LOGT( + "obj-state: failed-obj: schedule timeout in %" G_GINT64_FORMAT " msec", + NM_MAX((gint64) 0, + obj_state->os_failedobj_expiry_msec - nm_utils_get_monotonic_timestamp_msec())); + } } -static gboolean -_routes_temporary_not_available_update(NML3Cfg *self, - int addr_family, - GPtrArray *routes_temporary_not_available_arr) +static void +_failedobj_handle_routes(NML3Cfg *self, int addr_family, GPtrArray *routes_failed) { - ObjStateData *obj_state; - ObjStateData *obj_state_safe; - gint64 now_msec; - gboolean prune_all = FALSE; - gboolean success = TRUE; - guint i; - const NMPClass *klass; - - klass = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); - now_msec = nm_utils_get_monotonic_timestamp_msec(); - - if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) { - prune_all = TRUE; - goto out_prune; - } + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + ObjStateData *obj_state; + guint i; - c_list_for_each_entry (obj_state, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { - if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { - nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0); - obj_state->os_tna_dirty = TRUE; - } - } + if (!routes_failed) + return; - for (i = 0; i < routes_temporary_not_available_arr->len; i++) { - const NMPObject *o = routes_temporary_not_available_arr->pdata[i]; - char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + for (i = 0; i < routes_failed->len; i++) { + const NMPObject *o = routes_failed->pdata[i]; + const NMPlatformIPXRoute *rt = NMP_OBJECT_CAST_IPX_ROUTE(o); + gboolean just_started_to_fail = FALSE; + gboolean just_failed = FALSE; + gboolean arm_timer = FALSE; + int grace_timeout_msec; + gint64 grace_expiry_mesc; nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); @@ -4005,70 +4088,83 @@ _routes_temporary_not_available_update(NML3Cfg *self, continue; } - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { - nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0 - && obj_state->os_temporary_not_available_timestamp_msec <= now_msec); - - if (!obj_state->os_tna_dirty) { - /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates. - * It should not. */ - nm_assert_not_reached(); - continue; - } - - if (now_msec > obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { - /* Timeout. Could not add this address. - * - * For now, keep it obj_state->os_tna_dirty and prune it below. */ - _LOGW("failure to add IPv%c route: %s", - nm_utils_addr_family_to_char(addr_family), - nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); - success = FALSE; - continue; - } - - obj_state->os_tna_dirty = FALSE; + if (obj_state->os_plobj) { + /* This object is apparently present in platform. Not sure what this failure report + * is about. Probably some harmless glitch. Ignore. */ continue; } - _LOGT("(temporarily) unable to add IPv%c route: %s", - nm_utils_addr_family_to_char(addr_family), - nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + /* This route failed, but why? That determines the grace time that we + * give before considering it bad. */ + if (!nm_ip_addr_is_null(addr_family, + nm_platform_ip_route_get_pref_src(addr_family, &rt->rx))) { + /* This route has a pref_src. A common cause for being unable to + * configure such routes, is that the referenced IP address is not + * configured/ready (yet). Give a longer timeout to this case. */ + grace_timeout_msec = 10000; + } else { + /* Other route don't have any grace time. There is no retry/wait, + * they are a failure right away. */ + grace_timeout_msec = 0; + } - obj_state->os_tna_dirty = FALSE; - obj_state->os_temporary_not_available_timestamp_msec = now_msec; - c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head, - &obj_state->os_temporary_not_available_lst); - } + grace_expiry_mesc = now_msec + grace_timeout_msec; -out_prune: - c_list_for_each_entry_safe (obj_state, - obj_state_safe, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { - if (prune_all || obj_state->os_tna_dirty) { - if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { - obj_state->os_temporary_not_available_timestamp_msec = 0; - c_list_unlink(&obj_state->os_temporary_not_available_lst); + if (obj_state->os_failedobj_expiry_msec == 0) { + /* This is a new failure that we didn't see before... */ + obj_state->os_failedobj_expiry_msec = grace_expiry_mesc; + if (grace_timeout_msec == 0) + just_failed = TRUE; + else { + arm_timer = TRUE; + just_started_to_fail = TRUE; } + } else { + if (obj_state->os_failedobj_expiry_msec > grace_expiry_mesc) { + /* Shorten the grace timeout. We anyway rearm below... */ + obj_state->os_failedobj_expiry_msec = grace_expiry_mesc; + } + if (obj_state->os_failedobj_expiry_msec <= now_msec) { + /* The grace period is (already) expired. */ + if (obj_state->os_failedobj_prioq_idx != NM_PRIOQ_IDX_NULL) { + /* We are still tracking the element. It just is about to become failed. */ + just_failed = TRUE; + } + } else + arm_timer = TRUE; } - } - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - - obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, - ObjStateData, - os_temporary_not_available_lst); - if (obj_state) { - self->priv.p->obj_state_temporary_not_available_timeout_source = - nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec), - _routes_temporary_not_available_timeout, - self); + nm_prioq_update(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx, + arm_timer); + + if (just_failed) { + _LOGW("unable to configure IPv%c route: %s", + nm_utils_addr_family_to_char(addr_family), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + } else if (just_started_to_fail) { + _LOGT("obj-state: failed-obj: unable to configure %s. Wait for %d msec", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)), + grace_timeout_msec); + } } +} + +static int +_failedobj_prioq_cmp(gconstpointer a, gconstpointer b) +{ + const ObjStateData *object_state_a = a; + const ObjStateData *object_state_b = b; + + nm_assert(object_state_a); + nm_assert(object_state_a->os_failedobj_expiry_msec > 0); + nm_assert(object_state_b); + nm_assert(object_state_b->os_failedobj_expiry_msec > 0); - return success; + NM_CMP_SELF(object_state_a, object_state_b); + NM_CMP_FIELD(object_state_a, object_state_b, os_failedobj_expiry_msec); + return 0; } /*****************************************************************************/ @@ -4391,6 +4487,147 @@ _rp_filter_update(NML3Cfg *self, gboolean reapply) /*****************************************************************************/ +static void +_routes_watch_ip_addrs_cb(NMNetns *netns, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + const NMNetnsWatcherEventData *event_data, + gpointer user_data) +{ + const int IS_IPv4 = NM_IS_IPv4(watcher_data->ip_addr.addr.addr_family); + NML3Cfg *self = user_data; + char sbuf[NM_INET_ADDRSTRLEN]; + + if (NMP_OBJECT_CAST_IP_ADDRESS(event_data->ip_addr.obj)->ifindex == self->priv.ifindex) { + if (self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4] > 0) { + /* We are currently commiting IP addresses on this very interface. + * We can ignore the event. Also, because we will sync the routes + * immediately after already. So even if somebody externally added + * the address just this very moment, we would still do the commit + * at the right time to ensure our routes are there. */ + return; + } + } + + if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED) + return; + + _LOGT("watched ip-address %s changed. Schedule an idle commit", + nm_inet_ntop(watcher_data->ip_addr.addr.addr_family, + &watcher_data->ip_addr.addr.addr, + sbuf)); + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); +} + +static void +_routes_watch_ip_addrs(NML3Cfg *self, int addr_family, GPtrArray *addresses, GPtrArray *routes) +{ + gconstpointer const TAG = _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family); + NMNetnsWatcherData watcher_data = { + .ip_addr = + { + .addr = + { + .addr_family = addr_family, + }, + }, + }; + guint i; + guint j; + + /* IP routes that have a pref_src, can only be configured in kernel if that + * address exists (and is non-tentative, in case of IPv6). That address + * might be on another interface. So we actually watch all other + * interfaces. + * + * Note that while we track failure to configure routes via "failedobj" + * mechanism, we eagerly register watchers, even if the route is already + * successfully configured or if the route is to be configure the first + * time. Maybe that could be improved, but + * - watchers should be cheap unless they notify the event. + * - upon change we do an async commit, which is maybe not entirely cheap + * but cheap enough. More importantly, committing is something that + * *always* should be permissible -- because NML3Cfg has multiple, + * independent users, that don't know about each other and which + * independently are allowed to issue a commit when they think something + * relevant changed. If there are really too many, unnecessary commits, + * then the cause needs to be understood and addressed explicitly. */ + + if (!routes) + goto out; + + for (i = 0; i < routes->len; i++) { + const NMPlatformIPRoute *rt = NMP_OBJECT_CAST_IP_ROUTE(routes->pdata[i]); + gconstpointer pref_src; + + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(routes->pdata[i]) == addr_family); + + pref_src = nm_platform_ip_route_get_pref_src(addr_family, rt); + + if (nm_ip_addr_is_null(addr_family, pref_src)) + continue; + + if (NM_IS_IPv4(addr_family)) { + if (addresses) { + /* This nested loop makes the whole operation O(n*m). We still + * do it that way, because it's probably faster to just iterate + * over the few addresses instead of building a lookup index to + * get it in O(n+m). */ + for (j = 0; j < addresses->len; j++) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(addresses->pdata[j]); + + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(addresses->pdata[j]) == addr_family); + + if (nm_ip_addr_equal(addr_family, pref_src, a->address_ptr)) { + /* We optimize for the case where the required address + * is about be configured in the same commit. That is a + * common case, because our DHCP routes have prefsrc + * set, and we commonly have the respective IP address + * ready. Otherwise, the very common DHCP case would + * also require the overhead of registering a watcher + * (every time). + */ + goto next; + } + } + } + } else { + /* For IPv6, the prefsrc address must also be non-tentative (or + * IFA_F_OPTIMISTIC). So by only looking at the addresses we are + * about to configure, it's not clear whether we will be able to + * configure the route too. + * + * Maybe we could check current platform, whether the address + * exists there as non-tentative, but that seems fragile. + * + * Maybe we should only register watchers, after we encountered a + * failure to configure a route, but that seems complicated (and + * has the potential to be wrong). + * + * The overhead for always watching the IPv6 address should be + * acceptably small. So just do that. + */ + } + + nm_assert(watcher_data.ip_addr.addr.addr_family == addr_family); + nm_ip_addr_set(addr_family, &watcher_data.ip_addr.addr.addr, pref_src); + + nm_netns_watcher_add(self->priv.netns, + NM_NETNS_WATCHER_TYPE_IP_ADDR, + &watcher_data, + TAG, + _routes_watch_ip_addrs_cb, + self); +next: + (void) 0; + } + +out: + nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE); +} +/*****************************************************************************/ + static gboolean _global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family) { @@ -4576,24 +4813,22 @@ _l3_commit_mptcp(NML3Cfg *self, NML3CfgCommitType commit_type) _rp_filter_update(self, reapply); } -static gboolean +static void _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type, gboolean changed_combined_l3cd, const NML3ConfigData *l3cd_old) { - const int IS_IPv4 = NM_IS_IPv4(addr_family); - gs_unref_ptrarray GPtrArray *addresses = NULL; - gs_unref_ptrarray GPtrArray *routes = NULL; - gs_unref_ptrarray GPtrArray *routes_nodev = NULL; - gs_unref_ptrarray GPtrArray *addresses_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *routes_nodev = NULL; + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_failed = NULL; NMIPRouteTableSyncMode route_table_sync; - gboolean final_failure_for_temporary_not_available = FALSE; char sbuf_commit_type[50]; - gboolean success = TRUE; guint i; nm_assert(NM_IS_L3CFG(self)); @@ -4609,7 +4844,12 @@ _l3_commit_one(NML3Cfg *self, addresses = _commit_collect_addresses(self, addr_family, commit_type); - _commit_collect_routes(self, addr_family, commit_type, &routes, &routes_nodev); + _commit_collect_routes(self, + addr_family, + commit_type, + nm_g_ptr_array_len(addresses) > 0, + &routes, + &routes_nodev); route_table_sync = self->priv.p->combined_l3cd_commited @@ -4689,9 +4929,14 @@ _l3_commit_one(NML3Cfg *self, } } } + + _routes_watch_ip_addrs(self, addr_family, addresses, routes); + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */ /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */ + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]++; + nm_platform_ip_address_sync(self->priv.platform, addr_family, self->priv.ifindex, @@ -4701,26 +4946,18 @@ _l3_commit_one(NML3Cfg *self, ? NMP_IP_ADDRESS_SYNC_FLAGS_NONE : NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE); - _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); - - if (!nm_platform_ip_route_sync(self->priv.platform, - addr_family, - self->priv.ifindex, - routes, - routes_prune, - &routes_temporary_not_available_arr)) - success = FALSE; + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--; - final_failure_for_temporary_not_available = FALSE; - if (!_routes_temporary_not_available_update(self, - addr_family, - routes_temporary_not_available_arr)) - final_failure_for_temporary_not_available = TRUE; + _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); - /* FIXME(l3cfg) */ - (void) final_failure_for_temporary_not_available; + nm_platform_ip_route_sync(self->priv.platform, + addr_family, + self->priv.ifindex, + routes, + routes_prune, + &routes_failed); - return success; + _failedobj_handle_routes(self, addr_family, routes_failed); } static void @@ -4733,7 +4970,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) gboolean is_sticky_update = FALSE; char sbuf_ct[30]; gboolean changed_combined_l3cd; - guint i; g_return_if_fail(NM_IS_L3CFG(self)); nm_assert(NM_IN_SET(commit_type, @@ -4794,19 +5030,12 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old); _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old); + _failedobj_reschedule(self, 0); + _l3_commit_mptcp(self, commit_type); _l3_acd_data_process_changes(self); - if (self->priv.p->l3_config_datas) { - for (i = 0; i < self->priv.p->l3_config_datas->len; i++) { - L3ConfigData *l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, i); - - if (l3_config_data->force_commit_once) - l3_config_data->force_commit_once = FALSE; - } - } - nm_assert(self->priv.p->commit_reentrant_count == 1); self->priv.p->commit_reentrant_count--; @@ -5164,7 +5393,6 @@ nm_l3cfg_init(NML3Cfg *self) c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); c_list_init(&self->priv.p->obj_state_lst_head); - c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head); c_list_init(&self->priv.p->obj_state_zombie_lst_head); c_list_init(&self->priv.p->blocked_lst_head_4); c_list_init(&self->priv.p->blocked_lst_head_6); @@ -5176,6 +5404,8 @@ nm_l3cfg_init(NML3Cfg *self) nmp_object_indirect_id_equal, _obj_state_data_free, NULL); + + nm_prioq_init(&self->priv.p->failedobj_prioq, _failedobj_prioq_cmp); } static void @@ -5214,6 +5444,18 @@ finalize(GObject *object) NML3Cfg *self = NM_L3CFG(object); gboolean changed; + if (self->priv.netns) { + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET), + TRUE); + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6), + TRUE); + } + + nm_prioq_destroy(&self->priv.p->failedobj_prioq); + nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source); + nm_assert(c_list_is_empty(&self->internal_netns.signal_pending_lst)); nm_assert(c_list_is_empty(&self->internal_netns.ecmp_track_ifindex_lst_head)); @@ -5241,11 +5483,8 @@ finalize(GObject *object) nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source); - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy); nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head)); - nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head)); nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head)); if (_nodev_routes_untrack(self, AF_INET)) diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 9d622b4a..5ee201e7 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -55,15 +55,11 @@ typedef enum _nm_packed { * "don't change" behavior. At least once. If the address/route * is still not (no longer) configured on the subsequent * commit, it's not getting added again. - * @NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE: if set, objects in the - * NML3ConfigData are committed to platform even if they were - * removed externally. */ typedef enum _nm_packed { NM_L3CFG_CONFIG_FLAGS_NONE = 0, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1), - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE = (1LL << 2), } NML3CfgConfigFlags; typedef enum _nm_packed { @@ -132,8 +128,6 @@ typedef enum { * and neither should you call into NML3Cfg again (reentrancy). */ NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED, - NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, - NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, /* emitted before the merged l3cd is committed to platform. @@ -412,7 +406,7 @@ gboolean nm_l3cfg_check_ready(NML3Cfg *self, NML3CfgCheckReadyFlags flags, GArray **conflicts); -gboolean nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family); +gboolean nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family); /*****************************************************************************/ diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 19ca1d1e..9c721220 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -69,6 +69,36 @@ typedef struct { bool os_owner : 1; } RfkillRadioState; +#define AUTOCONNECT_RESET_RETRIES_TIMER_SEC 300 + +typedef struct { + NMDevice *device; + NMSettingsConnection *sett_conn; + CList dev_lst; + CList con_lst; + + /* Autoconnet retries needs to be tracked for each (device, connection) + * tuple because when a connection is a multiconnect one, each valid device + * must try to autoconnect the retries defined in the connection. */ + struct { + guint32 retries; + gint32 blocked_until_sec; + NMSettingsAutoconnectBlockedReason blocked_reason; + bool initialized : 1; + } autoconnect; + +} DevConData; + +#define DEV_CON_DATA_LOG_FMT \ + "device[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]-profile[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]" + +/* This is an unsafe macro (it evaluates the macro arguments multiple times and is non-function-like). */ +#define DEV_CON_DATA_LOG_ARGS(device, sett_conn) \ + NM_HASH_OBFUSCATE_PTR(device), nm_device_get_iface(device), NM_HASH_OBFUSCATE_PTR(sett_conn), \ + nm_settings_connection_get_id(sett_conn) + +#define DEV_CON_DATA_LOG_ARGS_DATA(data) DEV_CON_DATA_LOG_ARGS((data)->device, (data)->sett_conn) + typedef enum { ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL, ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER, @@ -173,6 +203,8 @@ typedef struct { } prop_filter; NMRfkillManager *rfkill_mgr; + GHashTable *devcon_data_dict; + CList link_cb_lst; NMCheckpointManager *checkpoint_mgr; @@ -415,6 +447,12 @@ static void _activation_auth_done(NMManager *self, static void _rfkill_update(NMManager *self, NMRfkillType rtype); +static DevConData *_devcon_lookup_data(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + gboolean create, + gboolean log_creation); + /*****************************************************************************/ static NM_CACHED_QUARK_FCN("autoconnect-root", autoconnect_root_quark); @@ -1212,6 +1250,473 @@ active_connection_get_by_path(NMManager *self, const char *path) /*****************************************************************************/ +static guint32 +_autoconnect_retries_initial(NMSettingsConnection *sett_conn) +{ + NMSettingConnection *s_con; + int retries = -1; + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + if (s_con) + retries = nm_setting_connection_get_autoconnect_retries(s_con); + + if (retries == -1) + retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA); + + nm_assert(retries >= 0 && retries <= G_MAXINT32); + + if (retries == 0) + return NM_AUTOCONNECT_RETRIES_FOREVER; + return (guint32) retries; +} + +static gboolean +_autoconnect_retries_set(NMManager *self, DevConData *data, guint32 retries, gboolean is_reset) +{ + gboolean changed = FALSE; + gint32 blocked_until_sec; + + nm_assert(data); + + if (!data->autoconnect.initialized || data->autoconnect.retries != retries) { + data->autoconnect.initialized = TRUE; + data->autoconnect.retries = retries; + changed = TRUE; + } + + if (retries != 0) { + blocked_until_sec = 0; + } else { + /* NOTE: the blocked time must be identical for all connections, otherwise + * the tracking of resetting the retry count in NMPolicy needs adjustment + * in _connection_autoconnect_retries_set() (as it would need to re-evaluate + * the next-timeout every time a connection gets blocked). */ + blocked_until_sec = + nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER_SEC; + } + + if (data->autoconnect.blocked_until_sec != blocked_until_sec) { + data->autoconnect.blocked_until_sec = blocked_until_sec; + changed = TRUE; + } + + if (changed) { + char sbuf[200]; + + _LOGT(LOGD_SETTINGS, + "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": retries set %u%s%s", + DEV_CON_DATA_LOG_ARGS_DATA(data), + retries, + is_reset ? " (is-reset)" : "", + blocked_until_sec == 0 ? "" + : nm_sprintf_buf(sbuf, + " (blocked for %d sec)", + AUTOCONNECT_RESET_RETRIES_TIMER_SEC)); + } + + return changed; +} + +/** + * nm_manager_devcon_autoconnect_retries_get: + * @self: the #NMManager + * @device: the #NMDevice + * @sett_conn: the #NMSettingsConnection + * + * Returns the number of autoconnect retries left for the (device, connection) + * tuple. If the value is not yet set, initialize it with the value from the + * connection or with the global default. + */ +guint32 +nm_manager_devcon_autoconnect_retries_get(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn) +{ + DevConData *data; + + nm_assert(NM_IS_MANAGER(self)); + nm_assert(NM_IS_DEVICE(device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(self == nm_device_get_manager(device)); + nm_assert(self == nm_settings_connection_get_manager(sett_conn)); + + data = _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE); + + if (G_UNLIKELY(!data->autoconnect.initialized)) + _autoconnect_retries_set(self, data, _autoconnect_retries_initial(sett_conn), FALSE); + + return data->autoconnect.retries; +} + +void +nm_manager_devcon_autoconnect_retries_set(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + guint32 retries) +{ + _autoconnect_retries_set(self, + _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE), + retries, + FALSE); +} + +gboolean +nm_manager_devcon_autoconnect_retries_reset(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn) +{ + DevConData *data; + guint32 retries_initial; + gboolean changed = FALSE; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + retries_initial = _autoconnect_retries_initial(sett_conn); + + if (device) { + return _autoconnect_retries_set(self, + _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE), + retries_initial, + TRUE); + } + + c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) { + if (_autoconnect_retries_set(self, data, retries_initial, TRUE)) + changed = TRUE; + } + + return changed; +} + +/** + * nm_manager_devcon_autoconnect_reset_reconnect_all: + * @self: the #NMManager + * @device: the #NMDevice + * @sett_conn: the #NMSettingsConnection + * @only_no_secrets: boolean to reset all reasons or only no secrets. + * + * Returns a boolean indicating if something changed or not when resetting the + * blocked reasons. If a #NMDevice is present then we also reset the reasons + * for the (device, connection) tuple. + */ +gboolean +nm_manager_devcon_autoconnect_reset_reconnect_all(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + gboolean only_no_secrets) +{ + gboolean changed = FALSE; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + if (only_no_secrets) { + /* we only reset the no-secrets blocked flag. */ + if (nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS, + FALSE)) { + /* maybe the connection is still blocked afterwards for other reasons + * and in the larger picture nothing changed. Check if the connection + * is still blocked or not. */ + if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) + changed = TRUE; + } + + return changed; + } + + /* we reset the tries-count and any blocked-reason... */ + + nm_manager_devcon_autoconnect_retries_reset(self, NULL, sett_conn); + + if (device) { + if (nm_manager_devcon_autoconnect_blocked_reason_set( + self, + device, + sett_conn, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, + FALSE)) + changed = TRUE; + } + + /* we remove all the blocked reason from the connection, if something + * happened, then it means the status changed */ + if (nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS + | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, + FALSE)) + changed = TRUE; + + return changed; +} + +gint32 +nm_manager_devcon_autoconnect_retries_blocked_until(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn) +{ + DevConData *data; + gint32 min_stamp; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + if (device) { + data = _devcon_lookup_data(self, device, sett_conn, FALSE, FALSE); + + if (!data) + return 0; + + return data->autoconnect.blocked_until_sec; + } + + min_stamp = 0; + c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) { + gint32 condev_stamp = data->autoconnect.blocked_until_sec; + + if (condev_stamp == 0) + continue; + + if (min_stamp == 0 || min_stamp > condev_stamp) + min_stamp = condev_stamp; + } + + return min_stamp; +} + +gboolean +nm_manager_devcon_autoconnect_is_blocked(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn) +{ + DevConData *data; + + nm_assert(NM_IS_DEVICE(device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + if (nm_settings_connection_autoconnect_is_blocked(sett_conn)) + return TRUE; + + data = _devcon_lookup_data(self, device, sett_conn, FALSE, FALSE); + + if (!data) + return FALSE; + + if (data->autoconnect.blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) + return TRUE; + + if (data->autoconnect.initialized && data->autoconnect.retries == 0) + return TRUE; + + return FALSE; +} + +gboolean +nm_manager_devcon_autoconnect_blocked_reason_set(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + NMSettingsAutoconnectBlockedReason value, + gboolean set) +{ + NMSettingsAutoconnectBlockedReason v; + DevConData *data; + gboolean changed = FALSE; + char buf[100]; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!device || NM_IS_DEVICE(device)); + nm_assert(value != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE); + nm_assert(!NM_FLAGS_ANY(value, ~(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED))); + + if (!sett_conn) { + if (!device) + g_return_val_if_reached(FALSE); + c_list_for_each_entry (data, &device->devcon_dev_lst_head, dev_lst) { + v = data->autoconnect.blocked_reason; + v = NM_FLAGS_ASSIGN(v, value, set); + + if (data->autoconnect.blocked_reason == v) + continue; + + _LOGT(LOGD_SETTINGS, + "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s", + DEV_CON_DATA_LOG_ARGS_DATA(data), + nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf))); + data->autoconnect.blocked_reason = v; + changed = TRUE; + } + return changed; + } + + if (device) { + data = _devcon_lookup_data(self, device, sett_conn, TRUE, TRUE); + v = data->autoconnect.blocked_reason; + v = NM_FLAGS_ASSIGN(v, value, set); + + if (data->autoconnect.blocked_reason == v) + return FALSE; + + data->autoconnect.blocked_reason = v; + _LOGT(LOGD_SETTINGS, + "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s", + DEV_CON_DATA_LOG_ARGS_DATA(data), + nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf))); + return TRUE; + } + + c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) { + v = data->autoconnect.blocked_reason; + v = NM_FLAGS_ASSIGN(v, value, set); + + if (data->autoconnect.blocked_reason == v) + continue; + + _LOGT(LOGD_SETTINGS, + "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s", + DEV_CON_DATA_LOG_ARGS_DATA(data), + nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf))); + data->autoconnect.blocked_reason = v; + changed = TRUE; + } + + return changed; +} + +/*****************************************************************************/ + +static guint +_devcon_data_hash(gconstpointer ptr) +{ + const DevConData *data = ptr; + + nm_assert(NM_IS_DEVICE(data->device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(data->sett_conn)); + + return nm_hash_vals(1832112199u, data->device, data->sett_conn); +} + +static gboolean +_devcon_data_equal(gconstpointer ptr_a, gconstpointer ptr_b) +{ + const DevConData *data_a = ptr_a; + const DevConData *data_b = ptr_b; + + nm_assert(NM_IS_DEVICE(data_a->device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(data_a->sett_conn)); + nm_assert(NM_IS_DEVICE(data_b->device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(data_b->sett_conn)); + + return data_a->device == data_b->device && data_a->sett_conn == data_b->sett_conn; +} + +static DevConData * +_devcon_lookup_data(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + gboolean create, + gboolean log_creation) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + DevConData *data; + DevConData needle; + + nm_assert(NM_IS_DEVICE(device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(self == nm_device_get_manager(device)); + nm_assert(self == nm_settings_connection_get_manager(sett_conn)); + + needle.device = device; + needle.sett_conn = sett_conn; + + data = g_hash_table_lookup(priv->devcon_data_dict, &needle); + + if (data) + return data; + if (!create) + return NULL; + + data = g_slice_new(DevConData); + *data = (DevConData){ + .device = device, + .sett_conn = sett_conn, + .autoconnect = + { + .initialized = FALSE, + .retries = 0, + .blocked_until_sec = 0, + .blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE, + }, + }; + c_list_link_tail(&device->devcon_dev_lst_head, &data->dev_lst); + c_list_link_tail(&sett_conn->devcon_con_lst_head, &data->con_lst); + + g_hash_table_add(priv->devcon_data_dict, data); + + if (log_creation) { + _LOGT(LOGD_SETTINGS, + "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": entry created (not initialized)", + DEV_CON_DATA_LOG_ARGS_DATA(data)); + } + + return data; +} + +static void +_devcon_remove_data(NMManager *self, DevConData *data) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_assert(data); + nm_assert(NM_IS_DEVICE(data->device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(data->sett_conn)); + nm_assert(data == _devcon_lookup_data(self, data->device, data->sett_conn, FALSE, FALSE)); + + c_list_unlink_stale(&data->dev_lst); + c_list_unlink_stale(&data->con_lst); + g_hash_table_remove(priv->devcon_data_dict, data); + nm_g_slice_free(data); +} + +static gboolean +_devcon_remove_device_all(NMManager *self, NMDevice *device) +{ + DevConData *data; + gboolean changed; + + nm_assert(NM_IS_DEVICE(device)); + + while ((data = c_list_first_entry(&device->devcon_dev_lst_head, DevConData, dev_lst))) { + changed = TRUE; + _devcon_remove_data(self, data); + } + + return changed; +} + +static gboolean +_devcon_remove_sett_conn_all(NMManager *self, NMSettingsConnection *sett_conn) +{ + DevConData *data; + gboolean changed = FALSE; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + while ((data = c_list_first_entry(&sett_conn->devcon_con_lst_head, DevConData, con_lst))) { + changed = TRUE; + _devcon_remove_data(self, data); + } + + return changed; +} + +void +nm_manager_notify_delete_settings_connections(NMManager *self, NMSettingsConnection *sett_conn) +{ + _devcon_remove_sett_conn_all(self, sett_conn); +} + +/*****************************************************************************/ + static void _config_changed_cb(NMConfig *config, NMConfigData *config_data, @@ -1429,7 +1934,7 @@ find_device_by_iface(NMManager *self, c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if (!nm_streq(nm_device_get_iface(candidate), iface)) continue; - if (connection && !nm_device_check_connection_compatible(candidate, connection, NULL)) + if (connection && !nm_device_check_connection_compatible(candidate, connection, TRUE, NULL)) continue; if (slave) { if (!nm_device_is_master(candidate)) @@ -1804,7 +2309,7 @@ remove_device(NMManager *self, NMDevice *device, gboolean quitting) nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_PLATFORM_INIT, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_REMOVED); } } @@ -1814,6 +2319,8 @@ remove_device(NMManager *self, NMDevice *device, gboolean quitting) nm_settings_device_removed(priv->settings, device, quitting); + _devcon_remove_device_all(self, device); + c_list_unlink(&device->devices_lst); _parent_notify_changed(self, device, TRUE); @@ -1931,6 +2438,7 @@ find_parent_device_for_connection(NMManager *self, && nm_device_check_connection_compatible( candidate, nm_settings_connection_get_connection(parent_connection), + TRUE, NULL)) first_compatible = candidate; } @@ -2088,9 +2596,10 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) guint i; gs_free char *iface = NULL; const char *parent_spec; - NMDevice *device = NULL, *parent = NULL; + NMDevice *device = NULL; + NMDevice *parent = NULL; NMDevice *dev_candidate; - GError *error = NULL; + gs_free_error GError *error = NULL; NMLogLevel log_level; g_return_val_if_fail(NM_IS_MANAGER(self), NULL); @@ -2099,7 +2608,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, &error); if (!iface) { _LOG3D(LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message); - g_error_free(error); return NULL; } @@ -2110,7 +2618,7 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) /* See if there's a device that is already compatible with this connection */ c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) { - if (nm_device_check_connection_compatible(dev_candidate, connection, NULL)) { + if (nm_device_check_connection_compatible(dev_candidate, connection, FALSE, NULL)) { if (nm_device_is_real(dev_candidate)) { _LOG3D(LOGD_DEVICE, connection, "already created virtual interface name %s", iface); return NULL; @@ -2137,7 +2645,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, &error); if (!device) { _LOG3W(LOGD_DEVICE, connection, "factory can't create the device: %s", error->message); - g_error_free(error); return NULL; } @@ -2148,7 +2655,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) connection, "can't register the device with manager: %s", error->message); - g_error_free(error); g_object_unref(device); return NULL; } @@ -2169,7 +2675,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) if (!find_master(self, connection, device, NULL, NULL, NULL, &error)) { _LOG3D(LOGD_DEVICE, connection, "skip activation: %s", error->message); - g_error_free(error); return device; } @@ -2179,11 +2684,10 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) NMConnection *candidate = nm_settings_connection_get_connection(connections[i]); NMSettingConnection *s_con; - if (!nm_device_check_connection_compatible(device, candidate, NULL)) + if (!nm_device_check_connection_compatible(device, candidate, TRUE, NULL)) continue; s_con = nm_connection_get_setting_connection(candidate); - g_assert(s_con); if (!nm_setting_connection_get_autoconnect(s_con) || nm_settings_connection_autoconnect_is_blocked(connections[i])) continue; @@ -2199,7 +2703,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection) connection, "couldn't create the device: %s", error->message); - g_error_free(error); return NULL; } @@ -2666,6 +3169,16 @@ _rfkill_update_from_user(NMManager *self, NMRfkillType rtype, gboolean enabled) /*****************************************************************************/ +void +nm_manager_device_recheck_auto_activate_schedule(NMManager *self, NMDevice *device) +{ + g_return_if_fail(NM_IS_MANAGER(self)); + + nm_policy_device_recheck_auto_activate_schedule(NM_MANAGER_GET_PRIVATE(self)->policy, device); +} + +/*****************************************************************************/ + static void device_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { @@ -2821,7 +3334,7 @@ new_activation_allowed_for_connection(NMManager *self, NMSettingsConnection *con * get_existing_connection: * @manager: #NMManager instance * @device: #NMDevice instance - * @out_generated: (allow-none): return TRUE, if the connection was generated. + * @out_generated: (out) (optional): return TRUE, if the connection was generated. * * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if * the device does not support assuming existing connections. @@ -2882,11 +3395,12 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat } } - if (nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA, - NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION, - device, - TRUE, - TRUE)) { + if (nm_config_data_get_device_config_boolean_by_device( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION, + device, + TRUE, + TRUE)) { /* The core of the API is nm_device_generate_connection() function, based on * update_connection() virtual method and the @connection_type_supported * class attribute. Devices that support assuming existing connections must @@ -2930,6 +3444,7 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat && nm_device_check_connection_compatible( device, nm_settings_connection_get_connection(connection_checked), + TRUE, NULL)) { if (connection) { NMConnection *con = nm_settings_connection_get_connection(connection_checked); @@ -2968,6 +3483,7 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat && nm_device_check_connection_compatible( device, nm_settings_connection_get_connection(sett_conn), + TRUE, NULL)) sett_conns[j++] = sett_conn; } @@ -3448,15 +3964,15 @@ _device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink * * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */ nm_device_state_changed(device, NM_DEVICE_STATE_UNAVAILABLE, - NM_DEVICE_STATE_REASON_NOW_MANAGED); - nm_device_emit_recheck_auto_activate(device); + nm_device_get_manage_reason_external(device)); + nm_manager_device_recheck_auto_activate_schedule(self, device); } /** * add_device: * @self: the #NMManager * @device: the #NMDevice to add - * @error: (out): the #GError + * @error: the #GError * * If successful, this function will increase the references count of @device. * Callers should decrease the reference count. @@ -3813,7 +4329,7 @@ _check_remove_dev_on_link_deleted(NMManager *self, NMDevice *device) NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) continue; - if (!nm_device_check_connection_compatible(device, con, NULL)) + if (!nm_device_check_connection_compatible(device, con, TRUE, NULL)) continue; /* Found a virtual connection compatible, the device must @@ -4390,64 +4906,136 @@ find_master(NMManager *self, NMActiveConnection **out_master_ac, GError **error) { - NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); - NMSettingConnection *s_con; - const char *master; - NMDevice *master_device = NULL; - NMSettingsConnection *master_connection; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMSettingConnection *s_con; + const char *master; + NMDevice *master_device = NULL; + NMSettingsConnection *master_connection = NULL; + NMSettingsConnection *const *connections; + guint i; - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); + nm_assert(!out_master_connection || !*out_master_connection); + nm_assert(!out_master_device || !*out_master_device); + nm_assert(!out_master_ac || !*out_master_ac); + + s_con = nm_connection_get_setting_connection(connection); master = nm_setting_connection_get_master(s_con); if (master == NULL) return TRUE; /* success, but no master */ - /* Try as an interface name first */ - master_device = find_device_by_iface(self, master, NULL, connection); - if (master_device) { - if (master_device == device) { - g_set_error_literal(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_DEPENDENCY_FAILED, - "Device cannot be its own master"); - return FALSE; + _LOGD(LOGD_CORE, + "Looking for a master '%s' for connection '%s' (%s)", + master, + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + connections = nm_settings_get_connections_sorted_by_autoconnect_priority(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMConnection *master_candidate = nm_settings_connection_get_connection(connections[i]); + NMDevice *device_candidate; + + if (nm_streq(nm_connection_get_uuid(master_candidate), master)) { + if (!is_compatible_with_slave(master_candidate, connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The active connection on %s is not compatible", + nm_device_get_iface(master_device)); + return FALSE; + } + + _LOGD(LOGD_CORE, + "Will consider using connection '%s' (%s) as a master for '%s' (%s) " + "because UUID matches", + nm_connection_get_id(master_candidate), + nm_connection_get_uuid(master_candidate), + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + master_connection = connections[i]; + } else if (nm_connection_get_interface_name(master_candidate) + && nm_streq(nm_connection_get_interface_name(master_candidate), master)) { + if (!is_compatible_with_slave(master_candidate, connection)) + continue; + + /* This might be good enough unless we find a better one (already active or UUID match) */ + if (!master_connection) { + master_connection = connections[i]; + _LOGD(LOGD_CORE, + "Will consider using connection '%s' (%s) as a master for '%s' (%s) " + "because device matches", + nm_connection_get_id(master_candidate), + nm_connection_get_uuid(master_candidate), + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + } + } else { + /* No match. */ + continue; } - master_connection = nm_device_get_settings_connection(master_device); - if (master_connection - && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection), - connection)) { + /* Check if the master connection is activated on some device already */ + c_list_for_each_entry (device_candidate, &priv->devices_lst_head, devices_lst) { + if (device_candidate == device) + continue; + + if (nm_device_get_settings_connection(device_candidate) == connections[i]) { + master_device = device_candidate; + master_connection = connections[i]; + break; + } + } + + if (master_device) { + /* Now we got a connection and also a device. Look no further. */ + _LOGD(LOGD_CORE, + "Will use connection '%s' (%s) as a master for '%s' (%s)", + nm_connection_get_id(master_candidate), + nm_connection_get_uuid(master_candidate), + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + break; + } + } + + if (!master_connection) { + master_device = find_device_by_iface(self, master, NULL, connection); + if (!master_device) { g_set_error(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, - "The active connection on %s is not compatible", - nm_device_get_iface(master_device)); + "Connection or device %s not found", + master); return FALSE; } - } else { - /* Try master as a connection UUID */ - master_connection = nm_settings_get_connection_by_uuid(priv->settings, master); - if (master_connection) { - NMDevice *candidate; - - /* Check if the master connection is activated on some device already */ - c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { - if (candidate == device) - continue; - if (nm_device_get_settings_connection(candidate) == master_connection) { - master_device = candidate; - break; - } - } + if (master_device == device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Device cannot be its own master"); + return FALSE; } + + _LOGD(LOGD_CORE, + "Master connection for '%s' (%s) not found, will use device '%s'", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection), + nm_device_get_iface(master_device)); + } + + if (!master_device && !master_connection) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Master connection not found or invalid"); + return FALSE; } - if (out_master_connection) - *out_master_connection = master_connection; - if (out_master_device) - *out_master_device = master_device; + NM_SET_OUT(out_master_connection, master_connection); + NM_SET_OUT(out_master_device, master_device); if (out_master_ac && master_connection) { *out_master_ac = active_connection_find(self, master_connection, @@ -4457,15 +5045,7 @@ find_master(NMManager *self, NULL); } - if (master_device || master_connection) - return TRUE; - else { - g_set_error_literal(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_UNKNOWN_DEVICE, - "Master connection not found or invalid"); - return FALSE; - } + return TRUE; } /** @@ -4632,7 +5212,9 @@ ensure_master_active_connection(NMManager *self, continue; if (nm_device_is_real(candidate) - && nm_device_get_state(candidate) != NM_DEVICE_STATE_DISCONNECTED) + && !NM_IN_SET(nm_device_get_state(candidate), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_DEACTIVATING)) continue; master_ac = nm_manager_activate_connection( @@ -4710,8 +5292,9 @@ find_slaves(NMManager *manager, &n_all_connections); for (i = 0; i < n_all_connections; i++) { NMSettingsConnection *master_connection = NULL; - NMDevice *master_device = NULL, *slave_device; - NMSettingsConnection *candidate = all_connections[i]; + NMDevice *master_device = NULL; + NMDevice *slave_device; + NMSettingsConnection *candidate = all_connections[i]; find_master(manager, nm_settings_connection_get_connection(candidate), @@ -4934,12 +5517,12 @@ unmanaged_to_disconnected(NMDevice *device) * and force the device to be managed. */ nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_PLATFORM_INIT, - FALSE, + NM_UNMAN_FLAG_OP_SET_MANAGED, NM_DEVICE_STATE_REASON_USER_REQUESTED); nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_USER_EXPLICIT, - FALSE, + NM_UNMAN_FLAG_OP_SET_MANAGED, NM_DEVICE_STATE_REASON_USER_REQUESTED); if (!nm_device_get_managed(device, FALSE)) { @@ -5042,9 +5625,66 @@ active_connection_parent_active(NMActiveConnection *active, } static gboolean +_check_autoconnect_port(NMActiveConnection *active, + NMSettingsConnection *master_connection, + NMDevice *master_device, + NMActiveConnection *master_ac) +{ + NMSettingConnection *s_con; + NMDevice *device; + + if (nm_active_connection_get_activation_reason(active) != NM_ACTIVATION_REASON_AUTOCONNECT) { + /* This is an explicit activation. Proceed. */ + return TRUE; + } + + if (!master_connection) { + /* This is not a port. Proceed. */ + return TRUE; + } + + device = nm_active_connection_get_device(active); + + if (!nm_device_is_real(device)) { + /* The device is not real. We don't know about the carrier. Proceed. */ + return TRUE; + } + + if (nm_device_get_ifindex(device) <= 0) { + /* The device has no ifindex. It has no concept of carrier. Proceed. */ + return TRUE; + } + + if (nm_device_has_carrier(device)) { + /* The device has carrier. Proceed. */ + return TRUE; + } + + s_con = nm_settings_connection_get_setting(master_connection, NM_META_SETTING_TYPE_CONNECTION); + + if (nm_setting_connection_get_autoconnect(s_con)) { + /* The controller profile has autoconnect enabled. Here we want to honor + * "ignore-carrier=no", which -- as configuration -- only makes sense for + * controllers that have autoconnect disable. Proceed. */ + return TRUE; + } + + if (nm_config_data_get_ignore_carrier_for_port( + NM_CONFIG_GET_DATA, + nm_setting_connection_get_interface_name(s_con), + nm_setting_connection_get_connection_type(s_con))) { + /* We ignore carrier on the master (as we would do by default). Proceed. */ + return TRUE; + } + + return FALSE; +} + +static gboolean _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **error) { - NMDevice *device, *master_device = NULL; + NMDevice *device; + NMDevice *master_device = NULL; NMConnection *applied; NMSettingsConnection *sett_conn; NMSettingsConnection *master_connection = NULL; @@ -5120,6 +5760,37 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError ** return FALSE; } + /* FIXME: in _check_autoconnect_port() we decide on whether to abort to + * activation based on the device's carrier state (and the controller's + * ignore-carrier setting). + * + * At this stage, we might be activating a VLAN attached to a bond + * interface. But the VLAN interface may not be created yet, and not have a + * carrier state yet. + * + * We could fix this, by checking again (or exclusively) before attaching + * the port to the controller, whether the conditions from + * _check_autoconnect_port() hold. And if they don't, abort activation at + * a later stage. + * + * The problem is, that we already start activating the controller at this + * point. Hence, aborting later is not good. What instead maybe should be + * done, is that port profiles don't start activating the controller + * before they have layer 2 set up. + */ + + if (!_check_autoconnect_port(active, master_connection, master_device, master_ac)) { + /* Usually, port and controller devices can (auto)connect without carrier. However, + * the controller has "ignore-carrier=no" configured. If the port autoconnects, + * has no carrier and the controller has ignore-carrier=no, then autoconnect + * is going to fail. */ + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "port has no carrier and controller does not ignore carrier"); + return FALSE; + } + /* Create any backing resources the device needs */ if (!nm_device_is_real(device)) { NMDevice *parent; @@ -5151,7 +5822,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError ** if (nm_active_connection_get_activation_reason(active) == NM_ACTIVATION_REASON_AUTOCONNECT && NM_FLAGS_HAS(nm_settings_connection_autoconnect_blocked_reason_get(parent_con), - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) { + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST)) { g_set_error(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, @@ -5529,11 +6200,60 @@ fail: error_desc ?: error->message); } +void +nm_manager_deactivate_ac(NMManager *self, NMSettingsConnection *connection) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + const CList *tmp_list, *tmp_safe; + GError *error = NULL; + AsyncOpData *async_op_data; + AsyncOpData *async_op_data_safe; + + nm_assert(NM_IS_SETTINGS_CONNECTION(connection)); + + nm_manager_for_each_active_connection_safe (self, ac, tmp_list, tmp_safe) { + if (nm_active_connection_get_settings_connection(ac) == connection + && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) { + if (!nm_manager_deactivate_connection(self, + ac, + NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, + &error)) { + _LOGW(LOGD_DEVICE, + "connection '%s' disappeared, but error deactivating it: (%d) %s", + nm_settings_connection_get_id(connection), + error ? error->code : -1, + error ? error->message : "(unknown)"); + g_clear_error(&error); + } + } + } + + c_list_for_each_entry_safe (async_op_data, + async_op_data_safe, + &priv->async_op_lst_head, + async_op_lst) { + if (!NM_IN_SET(async_op_data->async_op_type, + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL, + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2)) + continue; + + ac = async_op_data->ac_auth.active; + if (nm_active_connection_get_settings_connection(ac) == connection) { + nm_active_connection_set_state(ac, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED); + } + } +} + /** * nm_manager_activate_connection(): * @self: the #NMManager * @sett_conn: the #NMSettingsConnection to activate on @device - * @applied: (allow-none): the applied connection to activate on @device + * @applied: (nullable): the applied connection to activate on @device * @specific_object: the specific object path, if any, for the activation * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs. * @subject: the subject which requested activation @@ -5782,7 +6502,7 @@ _activation_auth_done(NMManager *self, nm_settings_connection_autoconnect_blocked_reason_set( connection, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, FALSE); g_dbus_method_invocation_return_value( invocation, @@ -6492,7 +7212,7 @@ device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self) _LOGD(LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface(device)); nm_device_set_unmanaged_by_flags_queue(device, NM_UNMANAGED_SLEEPING, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_SLEEPING); break; case NM_DEVICE_STATE_UNMANAGED: @@ -6541,6 +7261,8 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed) continue; } + nm_device_notify_sleeping(device); + if (nm_device_is_activating(device) || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) { _LOGD(LOGD_SUSPEND, @@ -6554,7 +7276,7 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed) } else { nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_SLEEPING, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_SLEEPING); } } @@ -6574,7 +7296,7 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed) if (device_is_wake_on_lan(priv->platform, device)) nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_SLEEPING, - TRUE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, NM_DEVICE_STATE_REASON_SLEEPING); /* Check if the device is unmanaged but the state transition is still pending. @@ -6597,7 +7319,8 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed) /* Re-manage managed devices */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { - guint i; + NMDeviceStateReason reason; + guint i; if (nm_device_is_software(device) && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_SLEEPING)) { @@ -6628,10 +7351,17 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed) nm_device_set_enabled(device, enabled); } + /* The reason determines whether the device will be sys-iface-state=managed + * or sys-iface-state=external. Pass the correct reason to restore the state + * that was set before sleeping. */ + reason = nm_device_get_sys_iface_state_before_sleep(device) + == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL + ? NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED + : NM_DEVICE_STATE_REASON_NOW_MANAGED; nm_device_set_unmanaged_by_flags(device, NM_UNMANAGED_SLEEPING, - FALSE, - NM_DEVICE_STATE_REASON_NOW_MANAGED); + NM_UNMAN_FLAG_OP_SET_MANAGED, + reason); } /* Give the connections a chance to recreate the virtual devices. @@ -7963,6 +8693,14 @@ nm_settings_get(void) return NM_MANAGER_GET_PRIVATE(singleton_instance)->settings; } +NMPolicy * +nm_manager_get_policy(NMManager *self) +{ + g_return_val_if_fail(NM_IS_MANAGER(self), NULL); + + return NM_MANAGER_GET_PRIVATE(self)->policy; +} + NMManager * nm_manager_setup(void) { @@ -8106,6 +8844,8 @@ nm_manager_init(NMManager *self) priv->state = NM_STATE_DISCONNECTED; priv->startup = TRUE; + priv->devcon_data_dict = g_hash_table_new(_devcon_data_hash, _devcon_data_equal); + /* sleep/wake handling */ priv->sleep_monitor = nm_sleep_monitor_new(); g_signal_connect(priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING, G_CALLBACK(sleeping_cb), self); @@ -8439,6 +9179,8 @@ dispose(GObject *object) nm_clear_pointer(&priv->device_route_metrics, g_hash_table_destroy); + nm_clear_pointer(&priv->devcon_data_dict, g_hash_table_destroy); + G_OBJECT_CLASS(nm_manager_parent_class)->dispose(object); } diff --git a/src/core/nm-manager.h b/src/core/nm-manager.h index 0cbbcbf0..3028eb7e 100644 --- a/src/core/nm-manager.h +++ b/src/core/nm-manager.h @@ -69,6 +69,8 @@ NMManager *nm_manager_setup(void); NMManager *nm_manager_get(void); #define NM_MANAGER_GET (nm_manager_get()) +NMPolicy *nm_manager_get_policy(NMManager *self); + gboolean nm_manager_start(NMManager *manager, GError **error); void nm_manager_stop(NMManager *manager); NMState nm_manager_get_state(NMManager *manager); @@ -121,6 +123,10 @@ NMSettingsConnection **nm_manager_get_activatable_connections(NMManager *manager gboolean sort, guint *out_len); +void nm_manager_deactivate_ac(NMManager *self, NMSettingsConnection *connection); + +void nm_manager_device_recheck_auto_activate_schedule(NMManager *self, NMDevice *device); + void nm_manager_write_device_state_all(NMManager *manager); gboolean nm_manager_write_device_state(NMManager *manager, NMDevice *device, int *out_ifindex); @@ -207,6 +213,11 @@ struct _NMDnsManager *nm_manager_get_dns_manager(NMManager *self); /*****************************************************************************/ +void nm_manager_notify_delete_settings_connections(NMManager *self, + NMSettingsConnection *sett_conn); + +/*****************************************************************************/ + void nm_manager_device_auth_request(NMManager *self, NMDevice *device, GDBusMethodInvocation *context, @@ -219,4 +230,40 @@ void nm_manager_device_auth_request(NMManager *self, void nm_manager_unblock_failed_ovs_interfaces(NMManager *self); +/*****************************************************************************/ + +#define NM_AUTOCONNECT_RETRIES_FOREVER G_MAXUINT32 + +guint32 nm_manager_devcon_autoconnect_retries_get(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn); + +void nm_manager_devcon_autoconnect_retries_set(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + guint32 retries); + +gboolean nm_manager_devcon_autoconnect_retries_reset(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn); + +gboolean nm_manager_devcon_autoconnect_reset_reconnect_all(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + gboolean only_no_secrets); + +gint32 nm_manager_devcon_autoconnect_retries_blocked_until(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn); + +gboolean nm_manager_devcon_autoconnect_is_blocked(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn); + +gboolean nm_manager_devcon_autoconnect_blocked_reason_set(NMManager *self, + NMDevice *device, + NMSettingsConnection *sett_conn, + NMSettingsAutoconnectBlockedReason value, + gboolean set); + #endif /* __NETWORKMANAGER_MANAGER_H__ */ diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index 12ca8508..ad156f99 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -22,6 +22,44 @@ /*****************************************************************************/ +typedef struct { + gconstpointer tag; + CList watcher_by_tag_lst_head; +} WatcherByTag; + +typedef struct { + NMIPAddrTyped addr; + CList watcher_ip_addr_lst_head; +} WatcherDataIPAddr; + +struct _NMNetnsWatcherHandle { + NMNetnsWatcherType watcher_type; + NMNetnsWatcherData watcher_data; + gconstpointer tag; + NMNetnsWatcherCallback callback; + gpointer callback_user_data; + + /* This is linked to "WatcherByTag.watcher_by_tag_lst_head" in + * "priv->watcher_by_tag_idx". */ + CList watcher_tag_lst; + + /* The registration data, which depends on the "watcher_type". */ + union { + struct { + CList watcher_ip_addr_lst; + } ip_addr; + } reg_data; + + /* nm_netns_watcher_add() will mark the handle as non-dirty, while + * nm_netns_watcher_remove_all() can delete only dirty handles (while + * leaving non-dirty handles alive, but marking them as dirty). + * + * That allows a pattern where you just add the new handles that you want + * now, and then call nm_netns_watcher_remove_all() to remove those that + * should no longer be present. */ + bool watcher_dirty : 1; +}; + NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, ); typedef struct { @@ -33,8 +71,20 @@ typedef struct { GHashTable *shared_ips; GHashTable *ecmp_track_by_obj; GHashTable *ecmp_track_by_ecmpid; - CList l3cfg_signal_pending_lst_head; - GSource *signal_pending_idle_source; + + /* Indexes the watcher handles. */ + GHashTable *watcher_idx; + + /* An index of WatcherByTag. It allows to lookup watcher handles by tag. + * Handles without tag are not indexed. */ + GHashTable *watcher_by_tag_idx; + + /* Index for WatcherDataIPAddr instances. Allows to lookup all subscribers + * by IP address. */ + GHashTable *watcher_ip_data_idx; + + CList l3cfg_signal_pending_lst_head; + GSource *signal_pending_idle_source; } NMNetnsPrivate; struct _NMNetns { @@ -87,6 +137,24 @@ NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS); /*****************************************************************************/ +static WatcherDataIPAddr * +_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr); +static void _watcher_handle_notify(NMNetns *self, + NMNetnsWatcherHandle *handle, + const NMNetnsWatcherEventData *event_data); +static const char * +_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size); + +/*****************************************************************************/ + +static gboolean +NM_NETNS_WATCHER_TYPE_VALID(NMNetnsWatcherType watcher_type) +{ + return NM_IN_SET(watcher_type, NM_NETNS_WATCHER_TYPE_IP_ADDR); +} + +/*****************************************************************************/ + typedef struct { const NMPObject *representative_obj; const NMPObject *merged_obj; @@ -437,7 +505,7 @@ _platform_signal_cb(NMPlatform *platform, l3cfg = nm_netns_l3cfg_get(self, ifindex); if (!l3cfg) - return; + goto notify_watcher; l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type); @@ -450,6 +518,55 @@ _platform_signal_cb(NMPlatform *platform, } _nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object)); + +notify_watcher: + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + { + NMNetnsWatcherHandle *handle; + NMNetnsWatcherHandle *handle_safe; + WatcherDataIPAddr *data; + + data = + _watcher_ip_data_lookup(self, + obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS ? AF_INET : AF_INET6, + ((const NMPlatformIPAddress *) platform_object)->address_ptr); + + if (data) { + const NMNetnsWatcherEventData event_data = { + .ip_addr = + { + .change_type = change_type, + .obj = NMP_OBJECT_UP_CAST(platform_object), + }, + }; + char sbuf[500]; + + c_list_for_each_entry_safe (handle, + handle_safe, + &data->watcher_ip_addr_lst_head, + reg_data.ip_addr.watcher_ip_addr_lst) { + _LOGT("netns-watcher: %s %s", + "notify", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + + /* Note that we dispatch these events directly from the platform event + * and while iterating over "data". + * + * From the callback, it's probably a bad idea to do anything in platform + * that might change anything (emit new signals) or to nm_netns_watcher_remove*() + * any other watcher. + * + * The callee needs to be careful. */ + _watcher_handle_notify(self, handle, &event_data); + } + } + break; + } + default: + break; + } } /*****************************************************************************/ @@ -695,7 +812,7 @@ nm_netns_ip_route_ecmp_commit(NMNetns *self, /* This route is onlink. We don't need to configure an onlink route * to the gateway, and the route is immediately ready for configuration. */ track_obj->is_ready = TRUE; - } else if (c_list_length_is(&track_ecmpid->ecmpid_lst_head, 1)) { + } else if (c_list_is_empty_or_single(&track_ecmpid->ecmpid_lst_head)) { /* This route has no merge partner and ends up being a * single hop route. It will be returned and configured by * the calling "l3cfg". @@ -815,7 +932,461 @@ nm_netns_ip_route_ecmp_commit(NMNetns *self, if (changed || is_reapply) { _LOGT("ecmp-route: multi-hop %s", nmp_object_to_string(route_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); - nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, route_obj); + nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, route_obj, NULL); + } + } +} + +/*****************************************************************************/ + +static void +_watcher_data_set(NMNetnsWatcherData *dst, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *src) +{ + nm_assert(dst); + nm_assert(src); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + dst->ip_addr = src->ip_addr; + return; + } + nm_assert_not_reached(); +} + +static void +_watcher_data_hash(NMHashState *h, NMNetnsWatcherType watcher_type, const NMNetnsWatcherData *data) +{ + nm_assert(h); + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + nm_assert(data); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + nm_ip_addr_typed_hash_update(h, &data->ip_addr.addr); + return; + } + nm_assert_not_reached(); +} + +static gboolean +_watcher_data_equal(NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *a, + const NMNetnsWatcherData *b) +{ + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + nm_assert(a); + nm_assert(b); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + return nm_ip_addr_typed_equal(&a->ip_addr.addr, &b->ip_addr.addr); + } + return nm_assert_unreachable_val(FALSE); +} + +static void +_watcher_by_tag_destroy(WatcherByTag *watcher_by_tag) +{ + c_list_unlink_stale(&watcher_by_tag->watcher_by_tag_lst_head); + nm_g_slice_free(watcher_by_tag); +} + +static void +_watcher_handle_init(NMNetnsWatcherHandle *handle, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag) +{ + nm_assert(handle); + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + + *handle = (NMNetnsWatcherHandle){ + .watcher_type = watcher_type, + .tag = tag, + .watcher_tag_lst = C_LIST_INIT(handle->watcher_tag_lst), + }; + _watcher_data_set(&handle->watcher_data, watcher_type, watcher_data); +} + +static guint +_watcher_handle_hash(gconstpointer data) +{ + const NMNetnsWatcherHandle *watcher = data; + NMHashState h; + + nm_assert(watcher); + nm_assert(watcher->tag); + + nm_hash_init(&h, 2696278447u); + nm_hash_update_vals(&h, watcher->tag, watcher->watcher_type); + _watcher_data_hash(&h, watcher->watcher_type, &watcher->watcher_data); + return nm_hash_complete(&h); +} + +static gboolean +_watcher_handle_equal(gconstpointer a, gconstpointer b) +{ + const NMNetnsWatcherHandle *ha = a; + const NMNetnsWatcherHandle *hb = b; + + nm_assert(ha); + nm_assert(hb); + nm_assert(ha->tag); + nm_assert(hb->tag); + + if (ha == hb) + return TRUE; + + return (ha->tag == hb->tag) && (ha->watcher_type == hb->watcher_type) + && _watcher_data_equal(ha->watcher_type, &ha->watcher_data, &hb->watcher_data); +} + +static const char * +_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size) +{ + const char *buf0 = buf; + char sbuf[NM_INET_ADDRSTRLEN]; + + nm_strbuf_append(&buf, + &buf_size, + "h:" NM_HASH_OBFUSCATE_PTR_FMT "[", + NM_HASH_OBFUSCATE_PTR(handle)); + + if (handle->tag) { + nm_strbuf_append(&buf, + &buf_size, + "tag:" NM_HASH_OBFUSCATE_PTR_FMT ",", + NM_HASH_OBFUSCATE_PTR(handle->tag)); + } + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + nm_strbuf_append_str(&buf, &buf_size, "ip-addr:"); + nm_strbuf_append_str(&buf, + &buf_size, + nm_inet_ntop(handle->watcher_data.ip_addr.addr.addr_family, + &handle->watcher_data.ip_addr.addr.addr, + sbuf)); + goto out; + } + nm_assert_not_reached(); + nm_strbuf_append_str(&buf, &buf_size, "unknown"); + +out: + nm_strbuf_append_c(&buf, &buf_size, ']'); + return buf0; +} + +static void +_watcher_handle_notify(NMNetns *self, + NMNetnsWatcherHandle *handle, + const NMNetnsWatcherEventData *event_data) +{ + nm_assert(NM_IS_NETNS(self)); + nm_assert(handle); + nm_assert(handle->callback); + + handle->callback(self, + handle->watcher_type, + &handle->watcher_data, + handle->tag, + event_data, + handle->callback_user_data); +} + +static WatcherDataIPAddr * +_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr) +{ + WatcherDataIPAddr needle; + + needle.addr.addr_family = addr_family; + nm_ip_addr_set(addr_family, &needle.addr.addr, addr); + return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_ip_data_idx, &needle); +} + +static WatcherDataIPAddr * +_watcher_ip_data_lookup_addr(NMNetns *self, const NMIPAddrTyped *addr) +{ + return _watcher_ip_data_lookup(self, addr->addr_family, &addr->addr); +} + +static guint +_watcher_ip_data_hash(gconstpointer _data) +{ + const WatcherDataIPAddr *data = _data; + NMHashState h; + + nm_assert(data); + + nm_hash_init(&h, 3152126191u); + nm_ip_addr_typed_hash_update(&h, &data->addr); + return nm_hash_complete(&h); +} + +static gboolean +_watcher_ip_data_equal(gconstpointer a, gconstpointer b) +{ + const WatcherDataIPAddr *data_a = a; + const WatcherDataIPAddr *data_b = b; + + nm_assert(data_a); + nm_assert(data_b); + + return nm_ip_addr_typed_equal(&data_a->addr, &data_b->addr); +} + +static NMNetnsWatcherHandle * +_watcher_lookup_handle(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag) +{ + NMNetnsWatcherHandle handle_needle; + + nm_assert(NM_IS_NETNS(self)); + nm_assert(tag); + + _watcher_handle_init(&handle_needle, watcher_type, watcher_data, tag); + return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_idx, &handle_needle); +} + +static void +_watcher_register_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + { + WatcherDataIPAddr *data; + + data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + if (!data) { + data = g_slice_new(WatcherDataIPAddr); + *data = (WatcherDataIPAddr){ + .addr = handle->watcher_data.ip_addr.addr, + .watcher_ip_addr_lst_head = C_LIST_INIT(data->watcher_ip_addr_lst_head), + }; + if (!g_hash_table_add(priv->watcher_ip_data_idx, data)) + nm_assert_not_reached(); + } + + c_list_link_tail(&data->watcher_ip_addr_lst_head, + &handle->reg_data.ip_addr.watcher_ip_addr_lst); + return; + } + } + nm_assert_not_reached(); +} + +static void +_watcher_unregister_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + { + gboolean is_last; + + nm_assert(({ + WatcherDataIPAddr *d; + + d = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + d &&c_list_contains(&d->watcher_ip_addr_lst_head, + &handle->reg_data.ip_addr.watcher_ip_addr_lst); + })); + + is_last = c_list_is_empty_or_single(&handle->reg_data.ip_addr.watcher_ip_addr_lst); + + c_list_unlink(&handle->reg_data.ip_addr.watcher_ip_addr_lst); + + if (is_last) { + WatcherDataIPAddr *data; + + data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + nm_assert(data); + nm_assert(c_list_is_empty(&data->watcher_ip_addr_lst_head)); + + if (!g_hash_table_remove(priv->watcher_ip_data_idx, data)) + nm_assert_not_reached(); + + nm_g_slice_free(data); + } + return; + } + } + nm_assert_not_reached(); +} + +void +nm_netns_watcher_add(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + NMNetnsWatcherCallback callback, + gpointer user_data) +{ + NMNetnsPrivate *priv; + NMNetnsWatcherHandle *handle; + gboolean is_new = FALSE; + char sbuf[500]; + + g_return_if_fail(NM_IS_NETNS(self)); + g_return_if_fail(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + g_return_if_fail(callback); + g_return_if_fail(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + handle = _watcher_lookup_handle(self, watcher_type, watcher_data, tag); + + if (!handle) { + WatcherByTag *watcher_by_tag; + + if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0)) + g_object_ref(self); + + handle = g_slice_new(NMNetnsWatcherHandle); + _watcher_handle_init(handle, watcher_type, watcher_data, tag); + + if (!g_hash_table_add(priv->watcher_idx, handle)) + nm_assert_not_reached(); + + watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag); + + if (!watcher_by_tag) { + watcher_by_tag = g_slice_new(WatcherByTag); + *watcher_by_tag = (WatcherByTag){ + .tag = tag, + .watcher_by_tag_lst_head = C_LIST_INIT(watcher_by_tag->watcher_by_tag_lst_head), + }; + g_hash_table_add(priv->watcher_by_tag_idx, watcher_by_tag); + } + + c_list_link_tail(&watcher_by_tag->watcher_by_tag_lst_head, &handle->watcher_tag_lst); + + is_new = TRUE; + } else { + /* Handles are deduplicated/shared. Hence it is error prone (and likely + * a bug) to provide different callback/user_data. Such usage is + * rejected here. + * + * This could be made to work, for example by now allowing handles to + * be merged or simply requiring the caller to be careful to not get + * this wrong. But that is currently not implemented nor needed. + */ + nm_assert(!tag + || (handle->callback == callback && handle->callback_user_data == user_data)); + } + + if (_LOGT_ENABLED() + && (is_new || handle->callback != callback || handle->callback_user_data != user_data)) { + _LOGT("netns-watcher: %s %s", + is_new ? "register" : "update", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + } + + handle->callback = callback; + handle->callback_user_data = user_data; + handle->watcher_dirty = FALSE; + + if (is_new) + _watcher_register_handle(self, handle); + + /* We cannot return a handle here, because handles are deduplicated via the priv->watchers_idx dictionary. + * The usage pattern is to use nm_netns_watcher_remove_all(), and not remove them one by one. + * As nm_netns_watcher_add() can return the same handle more than once, the user + * wouldn't know when it's safe to call nm_netns_watcher_remove_handle(). + * + * This could be extended by adding a ref-count to the handles. But that is not + * used currently, so it's not possible to remove watcher by their handle. */ +} + +static void +nm_netns_watcher_remove_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv; + char sbuf[500]; + + g_return_if_fail(NM_IS_NETNS(self)); + g_return_if_fail(handle); + nm_assert(handle->tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + nm_assert(g_hash_table_lookup(priv->watcher_idx, handle) == handle); + + _LOGT("netns-watcher: %s %s", + "unregister", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + + _watcher_unregister_handle(self, handle); + + if (!g_hash_table_remove(priv->watcher_idx, handle)) + nm_assert_not_reached(); + + if (c_list_is_empty_or_single(&handle->watcher_tag_lst)) { + if (!g_hash_table_remove(priv->watcher_by_tag_idx, &handle->tag)) + nm_assert_not_reached(); + } + + c_list_unlink_stale(&handle->watcher_tag_lst); + nm_g_slice_free(handle); + + if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0)) + g_object_unref(self); +} + +void +nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all) +{ + NMNetnsPrivate *priv; + WatcherByTag *watcher_by_tag; + NMNetnsWatcherHandle *handle; + NMNetnsWatcherHandle *handle_safe; + + g_return_if_fail(NM_IS_NETNS(self)); + + /* remove-all only works with handles that have a tag associated. + * Since NMNetns can have multiple users that are unknown to each + * other, it makes no sense to have a remove-all function which + * would remove all of them. */ + g_return_if_fail(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag); + if (!watcher_by_tag) + return; + + c_list_for_each_entry_safe (handle, + handle_safe, + &watcher_by_tag->watcher_by_tag_lst_head, + watcher_tag_lst) { + gboolean is_last; + + if (!all && !handle->watcher_dirty) { + /* Survivors are marked as dirty. This enables a pattern where you + * call nm_netns_watcher_add() on the elements you care about + * (which clears the dirty flag), and then remove all dirty ones + * with nm_netns_watcher_remove_all() (which marks the remaining + * handles as dirty for the next time). */ + handle->watcher_dirty = TRUE; + continue; + } + + is_last = c_list_is_empty_or_single(&watcher_by_tag->watcher_by_tag_lst_head); + nm_netns_watcher_remove_handle(self, handle); + + if (is_last) { + /* Removing the last handle destroys the "watcher_by_tag" and may even + * destroy "self". We must not touch those pointers hereafter. + * + * If you ever *not* return here, make sure to handle that! */ + return; } } } @@ -850,6 +1421,7 @@ nm_netns_init(NMNetns *self) NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); priv->_self_signal_user_data = self; + c_list_init(&priv->l3cfg_signal_pending_lst_head); G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0); @@ -859,6 +1431,14 @@ nm_netns_init(NMNetns *self) _ecmp_routes_by_ecmpid_equal, _ecmp_routes_by_ecmpid_free, NULL); + + priv->watcher_idx = g_hash_table_new(_watcher_handle_hash, _watcher_handle_equal); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(WatcherByTag, tag) == 0); + priv->watcher_by_tag_idx = g_hash_table_new_full(nm_pdirect_hash, + nm_pdirect_equal, + (GDestroyNotify) _watcher_by_tag_destroy, + NULL); + priv->watcher_ip_data_idx = g_hash_table_new(_watcher_ip_data_hash, _watcher_ip_data_equal); } static void @@ -937,10 +1517,17 @@ dispose(GObject *object) nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0); nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head)); nm_assert(!priv->shared_ips); + nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0); + nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0); + nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0); nm_clear_pointer(&priv->ecmp_track_by_obj, g_hash_table_destroy); nm_clear_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_by_tag_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_ip_data_idx, g_hash_table_destroy); + nm_clear_g_source_inst(&priv->signal_pending_idle_source); if (priv->platform) diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h index 84a78f83..7725ae79 100644 --- a/src/core/nm-netns.h +++ b/src/core/nm-netns.h @@ -59,4 +59,46 @@ void nm_netns_ip_route_ecmp_commit(NMNetns *self, GPtrArray **routes, gboolean is_reapply); +/*****************************************************************************/ + +typedef enum { + NM_NETNS_WATCHER_TYPE_IP_ADDR, +} NMNetnsWatcherType; + +typedef struct { + union { + struct { + NMIPAddrTyped addr; + } ip_addr; + }; +} NMNetnsWatcherData; + +typedef struct { + union { + struct { + const NMPObject *obj; + NMPlatformSignalChangeType change_type; + } ip_addr; + }; +} NMNetnsWatcherEventData; + +typedef struct _NMNetnsWatcherHandle NMNetnsWatcherHandle; + +typedef void (*NMNetnsWatcherCallback)(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + const NMNetnsWatcherEventData *event_data, + gpointer user_data); + +void nm_netns_watcher_add(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + NMNetnsWatcherCallback callback, + gpointer user_data); + +void +nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all /* or only dirty */); + #endif /* __NM_NETNS_H__ */ diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index d7e05b7b..efdb0636 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -51,7 +51,7 @@ typedef struct { NMManager *manager; NMNetns *netns; NMFirewalldManager *firewalld_manager; - CList pending_activation_checks; + CList policy_auto_activate_lst_head; NMAgentManager *agent_mgr; @@ -62,6 +62,10 @@ typedef struct { NMSettings *settings; + GSource *device_recheck_auto_activate_all_idle_source; + + GSource *reset_connections_retries_idle_source; + NMHostnameManager *hostname_manager; NMActiveConnection *default_ac4, *activating_ac4; @@ -70,10 +74,6 @@ typedef struct { NMDnsManager *dns_manager; gulong config_changed_id; - guint reset_retries_id; /* idle handler for resetting the retries count */ - - guint schedule_activate_all_id; /* idle handler for schedule_activate_all(). */ - NMPolicyHostnameMode hostname_mode; char *orig_hostname; /* hostname at NM start time */ char *cur_hostname; /* hostname we want to assign */ @@ -135,8 +135,7 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv) /*****************************************************************************/ static void update_system_hostname(NMPolicy *self, const char *msg); -static void schedule_activate_all(NMPolicy *self); -static void schedule_activate_check(NMPolicy *self, NMDevice *device); +static void nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self); static NMDevice *get_default_device(NMPolicy *self, int addr_family); /*****************************************************************************/ @@ -1283,23 +1282,6 @@ check_activating_active_connections(NMPolicy *self) g_object_thaw_notify(G_OBJECT(self)); } -typedef struct { - CList pending_lst; - NMPolicy *policy; - NMDevice *device; - guint autoactivate_id; -} ActivateData; - -static void -activate_data_free(ActivateData *data) -{ - nm_device_remove_pending_action(data->device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); - c_list_unlink_stale(&data->pending_lst); - nm_clear_g_source(&data->autoactivate_id); - g_object_unref(data->device); - g_slice_free(ActivateData, data); -} - static void pending_ac_gone(gpointer data, GObject *where_the_object_was) { @@ -1326,13 +1308,17 @@ pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPo * device, but block the current connection to avoid an activation * loop. */ - if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED) { + if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED + && reason != NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED) { con = nm_active_connection_get_settings_connection(ac); - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + nm_active_connection_get_device(ac), con, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, TRUE); - schedule_activate_check(self, nm_active_connection_get_device(ac)); + nm_policy_device_recheck_auto_activate_schedule(self, + nm_active_connection_get_device(ac)); } /* Cleanup */ @@ -1345,7 +1331,7 @@ pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPo } static void -auto_activate_device(NMPolicy *self, NMDevice *device) +_auto_activate_device(NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv; NMSettingsConnection *best_connection; @@ -1391,7 +1377,7 @@ auto_activate_device(NMPolicy *self, NMDevice *device) NMSettingConnection *s_con; const char *permission; - if (nm_settings_connection_autoconnect_is_blocked(candidate)) + if (nm_manager_devcon_autoconnect_is_blocked(priv->manager, device, candidate)) continue; cand_conn = nm_settings_connection_get_connection(candidate); @@ -1435,11 +1421,13 @@ auto_activate_device(NMPolicy *self, NMDevice *device) "connection '%s' auto-activation failed: %s", nm_settings_connection_get_id(best_connection), error->message); - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + device, best_connection, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, TRUE); - schedule_activate_check(self, device); + nm_policy_device_recheck_auto_activate_schedule(self, device); return; } @@ -1456,32 +1444,33 @@ auto_activate_device(NMPolicy *self, NMDevice *device) } } -static gboolean -auto_activate_device_cb(gpointer user_data) +static void +_auto_activate_device_clear(NMPolicy *self, NMDevice *device, gboolean do_activate) { - ActivateData *data = user_data; + nm_assert(NM_IS_DEVICE(device)); + nm_assert(NM_IS_POLICY(self)); + nm_assert(c_list_is_linked(&device->policy_auto_activate_lst)); + nm_assert(c_list_contains(&NM_POLICY_GET_PRIVATE(self)->policy_auto_activate_lst_head, + &device->policy_auto_activate_lst)); - g_assert(data); - g_assert(NM_IS_POLICY(data->policy)); - g_assert(NM_IS_DEVICE(data->device)); + c_list_unlink(&device->policy_auto_activate_lst); + nm_clear_g_source_inst(&device->policy_auto_activate_idle_source); - data->autoactivate_id = 0; - auto_activate_device(data->policy, data->device); - activate_data_free(data); - return G_SOURCE_REMOVE; + if (do_activate) + _auto_activate_device(self, device); + + nm_device_remove_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); } -static ActivateData * -find_pending_activation(NMPolicy *self, NMDevice *device) +static gboolean +_auto_activate_idle_cb(gpointer user_data) { - NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); - ActivateData *data; + NMDevice *device = user_data; - c_list_for_each_entry (data, &priv->pending_activation_checks, pending_lst) { - if (data->device == device) - return data; - } - return NULL; + nm_assert(NM_IS_DEVICE(device)); + + _auto_activate_device_clear(nm_manager_get_policy(nm_device_get_manager(device)), device, TRUE); + return G_SOURCE_CONTINUE; } /*****************************************************************************/ @@ -1600,10 +1589,12 @@ nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self) NMConnection *connection = nm_settings_connection_get_connection(sett_conn); if (nm_connection_get_setting_ovs_interface(connection)) { - nm_settings_connection_autoconnect_retries_reset(sett_conn); - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn); + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + NULL, sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, FALSE); } } @@ -1634,36 +1625,15 @@ reset_autoconnect_all( && !nm_device_check_connection_compatible( device, nm_settings_connection_get_connection(sett_conn), + TRUE, NULL)) continue; - if (only_no_secrets) { - /* we only reset the no-secrets blocked flag. */ - if (nm_settings_connection_autoconnect_blocked_reason_set( - sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, - FALSE)) { - /* maybe the connection is still blocked afterwards for other reasons - * and in the larger picture nothing changed. But it's too complicated - * to find out exactly. Just assume, something changed to be sure. */ - if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) - changed = TRUE; - } - } else { - /* we reset the tries-count and any blocked-reason */ - if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0) - changed = TRUE; - nm_settings_connection_autoconnect_retries_reset(sett_conn); - - if (nm_settings_connection_autoconnect_blocked_reason_set( - sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL - & ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, - FALSE)) { - if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) - changed = TRUE; - } - } + if (nm_manager_devcon_autoconnect_reset_reconnect_all(priv->manager, + device, + sett_conn, + only_no_secrets)) + changed = TRUE; } return changed; } @@ -1683,21 +1653,35 @@ sleeping_changed(NMManager *manager, GParamSpec *pspec, gpointer user_data) reset_autoconnect_all(self, NULL, FALSE); } -static void -schedule_activate_check(NMPolicy *self, NMDevice *device) +void +nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device) { - NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); - ActivateData *data; + NMPolicyPrivate *priv; NMActiveConnection *ac; const CList *tmp_list; - if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP) + g_return_if_fail(NM_IS_POLICY(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + nm_assert(g_signal_handler_find(device, + G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + NULL, + NM_POLICY_GET_PRIVATE(self)) + != 0); + + if (!c_list_is_empty(&device->policy_auto_activate_lst)) { + /* already queued. Return. */ return; + } - if (!nm_device_autoconnect_allowed(device)) + priv = NM_POLICY_GET_PRIVATE(self); + + if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP) return; - if (find_pending_activation(self, device)) + if (!nm_device_autoconnect_allowed(device)) return; nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { @@ -1712,11 +1696,8 @@ schedule_activate_check(NMPolicy *self, NMDevice *device) nm_device_add_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); - data = g_slice_new0(ActivateData); - data->policy = self; - data->device = g_object_ref(device); - data->autoactivate_id = g_idle_add(auto_activate_device_cb, data); - c_list_link_tail(&priv->pending_activation_checks, &data->pending_lst); + c_list_link_tail(&priv->policy_auto_activate_lst_head, &device->policy_auto_activate_lst); + device->policy_auto_activate_idle_source = nm_g_idle_add_source(_auto_activate_idle_cb, device); } static gboolean @@ -1729,7 +1710,7 @@ reset_connections_retries(gpointer user_data) gint32 con_stamp, min_stamp, now; gboolean changed = FALSE; - priv->reset_retries_id = 0; + nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source); min_stamp = 0; now = nm_utils_get_monotonic_timestamp_sec(); @@ -1737,91 +1718,83 @@ reset_connections_retries(gpointer user_data) for (i = 0; connections[i]; i++) { NMSettingsConnection *connection = connections[i]; - con_stamp = nm_settings_connection_autoconnect_retries_blocked_until(connection); + con_stamp = + nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager, NULL, connection); if (con_stamp == 0) continue; if (con_stamp <= now) { - nm_settings_connection_autoconnect_retries_reset(connection); + nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, connection); changed = TRUE; } else if (min_stamp == 0 || min_stamp > con_stamp) min_stamp = con_stamp; } /* Schedule the handler again if there are some stamps left */ - if (min_stamp != 0) - priv->reset_retries_id = - g_timeout_add_seconds(min_stamp - now, reset_connections_retries, self); + if (min_stamp != 0) { + priv->reset_connections_retries_idle_source = + nm_g_timeout_add_seconds_source(min_stamp - now, reset_connections_retries, self); + } /* If anything changed, try to activate the newly re-enabled connections */ if (changed) - schedule_activate_all(self); + nm_policy_device_recheck_auto_activate_all_schedule(self); - return FALSE; + return G_SOURCE_CONTINUE; } static void -_connection_autoconnect_retries_set(NMPolicy *self, NMSettingsConnection *connection, int tries) +_connection_autoconnect_retries_set(NMPolicy *self, + NMDevice *device, + NMSettingsConnection *connection, + guint32 tries) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); nm_assert(NM_IS_SETTINGS_CONNECTION(connection)); - nm_assert(tries >= 0); - nm_settings_connection_autoconnect_retries_set(connection, tries); + nm_manager_devcon_autoconnect_retries_set(priv->manager, device, connection, tries); if (tries == 0) { /* Schedule a handler to reset retries count */ - if (!priv->reset_retries_id) { - gint32 retry_time = - nm_settings_connection_autoconnect_retries_blocked_until(connection); - - g_warn_if_fail(retry_time != 0); - priv->reset_retries_id = - g_timeout_add_seconds(MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()), - reset_connections_retries, - self); + if (!priv->reset_connections_retries_idle_source) { + gint32 retry_time; + + retry_time = nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager, + device, + connection); + nm_assert(retry_time != 0); + + priv->reset_connections_retries_idle_source = nm_g_timeout_add_seconds_source( + MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()), + reset_connections_retries, + self); } } } static void -activate_slave_connections(NMPolicy *self, NMDevice *device) +unblock_autoconnect_for_ports(NMPolicy *self, + const char *master_device, + const char *master_uuid_settings, + const char *master_uuid_applied, + gboolean reset_devcon_autoconnect) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); - const char *master_device; - const char *master_uuid_settings = NULL; - const char *master_uuid_applied = NULL; - guint i; - NMActRequest *req; - gboolean internal_activation = FALSE; NMSettingsConnection *const *connections; gboolean changed; + guint i; - master_device = nm_device_get_iface(device); - g_assert(master_device); - - req = nm_device_get_act_request(device); - if (req) { - NMConnection *connection; - NMSettingsConnection *sett_conn; - NMAuthSubject *subject; - - connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req)); - if (connection) - master_uuid_applied = nm_connection_get_uuid(connection); - - sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req)); - if (sett_conn) { - master_uuid_settings = nm_settings_connection_get_uuid(sett_conn); - if (nm_streq0(master_uuid_settings, master_uuid_applied)) - master_uuid_settings = NULL; - } - - subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)); - internal_activation = - subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL); - } + _LOGT(LOGD_CORE, + "block-autoconnect: unblocking port profiles for controller ifname=%s%s%s, uuid=%s%s%s" + "%s%s%s", + NM_PRINT_FMT_QUOTE_STRING(master_device), + NM_PRINT_FMT_QUOTE_STRING(master_uuid_settings), + NM_PRINT_FMT_QUOTED(master_uuid_applied, + ", applied-uuid=\"", + master_uuid_applied, + "\"", + "")); changed = FALSE; connections = nm_settings_get_connections(priv->settings, NULL); @@ -1831,21 +1804,25 @@ activate_slave_connections(NMPolicy *self, NMDevice *device) const char *slave_master; s_slave_con = - nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION); slave_master = nm_setting_connection_get_master(s_slave_con); if (!slave_master) continue; + if (!NM_IN_STRSET(slave_master, master_device, master_uuid_applied, master_uuid_settings)) continue; - if (!internal_activation) { - if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0) + if (reset_devcon_autoconnect) { + if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn)) changed = TRUE; - nm_settings_connection_autoconnect_retries_reset(sett_conn); } - if (nm_settings_connection_autoconnect_blocked_reason_set( + + /* unblock the devices associated with that connection */ + if (nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + NULL, sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, FALSE)) { if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) changed = TRUE; @@ -1853,7 +1830,68 @@ activate_slave_connections(NMPolicy *self, NMDevice *device) } if (changed) - schedule_activate_all(self); + nm_policy_device_recheck_auto_activate_all_schedule(self); +} + +static void +unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection *sett_conn) +{ + const char *master_device; + const char *master_uuid_settings; + NMSettingConnection *s_con; + + nm_assert(NM_IS_POLICY(self)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + s_con = nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION); + + nm_assert(NM_IS_SETTING_CONNECTION(s_con)); + + master_uuid_settings = nm_setting_connection_get_uuid(s_con); + master_device = nm_setting_connection_get_interface_name(s_con); + + unblock_autoconnect_for_ports(self, master_device, master_uuid_settings, NULL, TRUE); +} + +static void +activate_slave_connections(NMPolicy *self, NMDevice *device) +{ + const char *master_device; + const char *master_uuid_settings = NULL; + const char *master_uuid_applied = NULL; + NMActRequest *req; + gboolean internal_activation = FALSE; + + master_device = nm_device_get_iface(device); + nm_assert(master_device); + + req = nm_device_get_act_request(device); + if (req) { + NMConnection *connection; + NMSettingsConnection *sett_conn; + NMAuthSubject *subject; + + sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req)); + if (sett_conn) + master_uuid_settings = nm_settings_connection_get_uuid(sett_conn); + + connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req)); + if (connection) + master_uuid_applied = nm_connection_get_uuid(connection); + + if (nm_streq0(master_uuid_settings, master_uuid_applied)) + master_uuid_applied = NULL; + + subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)); + internal_activation = + subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL); + } + + unblock_autoconnect_for_ports(self, + master_device, + master_uuid_settings, + master_uuid_applied, + !internal_activation); } static gboolean @@ -1968,9 +2006,11 @@ device_state_changed(NMDevice *device, * a missing SIM or wrong modem initialization). */ if (sett_conn) { - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + device, sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, TRUE); } break; @@ -1988,10 +2028,10 @@ device_state_changed(NMDevice *device, if (sett_conn && old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_ACTIVATED) { gboolean blocked = FALSE; - int tries; guint64 con_v; - if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) { + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_NO_SECRETS: /* we want to block the connection from auto-connect if it failed due to no-secrets. * However, if a secret-agent registered, since the connection made the last * secret-request, we do not block it. The new secret-agent might not yet @@ -2008,16 +2048,17 @@ device_state_changed(NMDevice *device, con_v = nm_settings_connection_get_last_secret_agent_version_id(sett_conn); if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) { _LOGD(LOGD_DEVICE, - "connection '%s' now blocked from autoconnect due to no secrets", + "block-autoconnect: connection '%s' now blocked from autoconnect due to " + "no secrets", nm_settings_connection_get_id(sett_conn)); nm_settings_connection_autoconnect_blocked_reason_set( sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS, TRUE); blocked = TRUE; } - } else if (nm_device_state_reason_check(reason) - == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) { + break; + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: /* A connection that fails due to dependency-failed is not * able to reconnect until the master connection activates * again; when this happens, the master clears the blocked @@ -2027,26 +2068,41 @@ device_state_changed(NMDevice *device, * dependency-failed. */ _LOGD(LOGD_DEVICE, - "connection '%s' now blocked from autoconnect due to failed dependency", + "block-autoconnect: connection[%p] (%s) now blocked from autoconnect due to " + "failed " + "dependency", + sett_conn, nm_settings_connection_get_id(sett_conn)); - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + device, sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, TRUE); blocked = TRUE; + break; + default: + break; } if (!blocked) { - tries = nm_settings_connection_autoconnect_retries_get(sett_conn); - if (tries > 0) { + guint32 tries; + + tries = nm_manager_devcon_autoconnect_retries_get(priv->manager, device, sett_conn); + if (tries == 0) { + /* blocked */ + } else if (tries != NM_AUTOCONNECT_RETRIES_FOREVER) { _LOGD(LOGD_DEVICE, - "connection '%s' failed to autoconnect; %d tries left", + "autoconnect: connection[%p] (%s): failed to autoconnect; %u tries left", + sett_conn, nm_settings_connection_get_id(sett_conn), - tries - 1); - _connection_autoconnect_retries_set(self, sett_conn, tries - 1); - } else if (tries != 0) { + tries - 1u); + _connection_autoconnect_retries_set(self, device, sett_conn, tries - 1u); + } else { _LOGD(LOGD_DEVICE, - "connection '%s' failed to autoconnect; infinite tries left", + "autoconnect: connection[%p] (%s) failed to autoconnect; infinite tries " + "left", + sett_conn, nm_settings_connection_get_id(sett_conn)); } } @@ -2055,7 +2111,7 @@ device_state_changed(NMDevice *device, case NM_DEVICE_STATE_ACTIVATED: if (sett_conn) { /* Reset auto retries back to default since connection was successful */ - nm_settings_connection_autoconnect_retries_reset(sett_conn); + nm_manager_devcon_autoconnect_retries_reset(priv->manager, device, sett_conn); } /* Since there is no guarantee that device_l3cd_changed() is called @@ -2087,27 +2143,34 @@ device_state_changed(NMDevice *device, case NM_DEVICE_STATE_DEACTIVATING: if (sett_conn) { NMSettingsAutoconnectBlockedReason blocked_reason = - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE; + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE; switch (nm_device_state_reason_check(reason)) { case NM_DEVICE_STATE_REASON_USER_REQUESTED: - blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST; + blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST; break; case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: - blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED; + blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED; break; default: break; } - if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) { + if (blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) { _LOGD(LOGD_DEVICE, - "blocking autoconnect of connection '%s': %s", + "block-autoconnect: blocking autoconnect of connection '%s': %s", nm_settings_connection_get_id(sett_conn), NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_string, nm_device_state_reason_check(reason))); - nm_settings_connection_autoconnect_blocked_reason_set(sett_conn, - blocked_reason, - TRUE); + if (blocked_reason == NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED) + nm_manager_devcon_autoconnect_blocked_reason_set(priv->manager, + device, + sett_conn, + blocked_reason, + TRUE); + else + nm_settings_connection_autoconnect_blocked_reason_set(sett_conn, + blocked_reason, + TRUE); } } ip6_remove_device_prefix_delegations(self, device); @@ -2126,7 +2189,7 @@ device_state_changed(NMDevice *device, update_routing_and_dns(self, FALSE, device); /* Device is now available for auto-activation */ - schedule_activate_check(self, device); + nm_policy_device_recheck_auto_activate_schedule(self, device); break; case NM_DEVICE_STATE_PREPARE: @@ -2146,9 +2209,11 @@ device_state_changed(NMDevice *device, case NM_DEVICE_STATE_IP_CONFIG: /* We must have secrets if we got here. */ if (sett_conn) - nm_settings_connection_autoconnect_blocked_reason_set( + nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + device, sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, FALSE); break; case NM_DEVICE_STATE_SECONDARIES: @@ -2248,16 +2313,7 @@ device_autoconnect_changed(NMDevice *device, GParamSpec *pspec, gpointer user_da NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF(priv); - schedule_activate_check(self, device); -} - -static void -device_recheck_auto_activate(NMDevice *device, gpointer user_data) -{ - NMPolicyPrivate *priv = user_data; - NMPolicy *self = _PRIV_TO_SELF(priv); - - schedule_activate_check(self, device); + nm_policy_device_recheck_auto_activate_schedule(self, device); } static void @@ -2292,10 +2348,6 @@ devices_list_register(NMPolicy *self, NMDevice *device) "notify::" NM_DEVICE_AUTOCONNECT, G_CALLBACK(device_autoconnect_changed), priv); - g_signal_connect(device, - NM_DEVICE_RECHECK_AUTO_ACTIVATE, - G_CALLBACK(device_recheck_auto_activate), - priv); } static void @@ -2319,16 +2371,13 @@ device_removed(NMManager *manager, NMDevice *device, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF(priv); - ActivateData *data; /* TODO: is this needed? The delegations are cleaned up * on transition to deactivated too. */ ip6_remove_device_prefix_delegations(self, device); - /* Clear any idle callbacks for this device */ - data = find_pending_activation(self, device); - if (data && data->autoactivate_id) - activate_data_free(data); + if (c_list_is_linked(&device->policy_auto_activate_lst)) + _auto_activate_device_clear(self, device, FALSE); if (g_hash_table_remove(priv->devices, device)) devices_list_unregister(self, device); @@ -2508,39 +2557,45 @@ active_connection_removed(NMManager *manager, NMActiveConnection *active, gpoint /*****************************************************************************/ static gboolean -schedule_activate_all_cb(gpointer user_data) +_device_recheck_auto_activate_all_cb(gpointer user_data) { NMPolicy *self = user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); const CList *tmp_lst; NMDevice *device; - priv->schedule_activate_all_id = 0; + nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source); nm_manager_for_each_device (priv->manager, device, tmp_lst) - schedule_activate_check(self, device); + nm_policy_device_recheck_auto_activate_schedule(self, device); - return G_SOURCE_REMOVE; + return G_SOURCE_CONTINUE; } static void -schedule_activate_all(NMPolicy *self) +nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); /* always restart the idle handler. That way, we settle * all other events before restarting to activate them. */ - nm_clear_g_source(&priv->schedule_activate_all_id); - priv->schedule_activate_all_id = g_idle_add(schedule_activate_all_cb, self); + nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source); + + priv->device_recheck_auto_activate_all_idle_source = + nm_g_idle_add_source(_device_recheck_auto_activate_all_cb, self); } +/*****************************************************************************/ + static void connection_added(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF(priv); - schedule_activate_all(self); + unblock_autoconnect_for_ports_for_sett_conn(self, connection); + + nm_policy_device_recheck_auto_activate_all_schedule(self); } static void @@ -2597,6 +2652,8 @@ connection_updated(NMSettings *settings, NMPolicy *self = _PRIV_TO_SELF(priv); NMSettingsConnectionUpdateReason update_reason = update_reason_u; + unblock_autoconnect_for_ports_for_sett_conn(self, connection); + if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) { const CList *tmp_lst; NMDevice *device; @@ -2608,44 +2665,15 @@ connection_updated(NMSettings *settings, } } - schedule_activate_all(self); -} - -static void -_deactivate_if_active(NMPolicy *self, NMSettingsConnection *connection) -{ - NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); - NMActiveConnection *ac; - const CList *tmp_list, *tmp_safe; - GError *error = NULL; - - nm_assert(NM_IS_SETTINGS_CONNECTION(connection)); - - nm_manager_for_each_active_connection_safe (priv->manager, ac, tmp_list, tmp_safe) { - if (nm_active_connection_get_settings_connection(ac) == connection - && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) { - if (!nm_manager_deactivate_connection(priv->manager, - ac, - NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, - &error)) { - _LOGW(LOGD_DEVICE, - "connection '%s' disappeared, but error deactivating it: (%d) %s", - nm_settings_connection_get_id(connection), - error ? error->code : -1, - error ? error->message : "(unknown)"); - g_clear_error(&error); - } - } - } + nm_policy_device_recheck_auto_activate_all_schedule(self); } static void connection_removed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) { NMPolicyPrivate *priv = user_data; - NMPolicy *self = _PRIV_TO_SELF(priv); - _deactivate_if_active(self, connection); + nm_manager_deactivate_ac(priv->manager, connection); } static void @@ -2657,7 +2685,7 @@ connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection, if (NM_FLAGS_HAS(nm_settings_connection_get_flags(connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { if (!nm_settings_connection_autoconnect_is_blocked(connection)) - schedule_activate_all(self); + nm_policy_device_recheck_auto_activate_all_schedule(self); } } @@ -2671,7 +2699,7 @@ secret_agent_registered(NMSettings *settings, NMSecretAgent *agent, gpointer use * connections failed due to missing secrets may re-try auto-connection. */ if (reset_autoconnect_all(self, NULL, TRUE)) - schedule_activate_all(self); + nm_policy_device_recheck_auto_activate_all_schedule(self); } NMActiveConnection * @@ -2765,7 +2793,7 @@ nm_policy_init(NMPolicy *self) NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); gs_free char *hostname_mode = NULL; - c_list_init(&priv->pending_activation_checks); + c_list_init(&priv->policy_auto_activate_lst_head); priv->netns = g_object_ref(nm_netns_get()); @@ -2901,9 +2929,9 @@ dispose(GObject *object) { NMPolicy *self = NM_POLICY(object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); - GHashTableIter h_iter; - NMDevice *device; - ActivateData *data, *data_safe; + + nm_assert(!c_list_is_empty(&priv->policy_auto_activate_lst_head)); + nm_assert(g_hash_table_size(priv->devices) == 0); nm_clear_g_object(&priv->default_ac4); nm_clear_g_object(&priv->default_ac6); @@ -2911,9 +2939,6 @@ dispose(GObject *object) nm_clear_g_object(&priv->activating_ac6); nm_clear_pointer(&priv->pending_active_connections, g_hash_table_unref); - c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst) - activate_data_free(data); - g_slist_free_full(priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free); priv->pending_secondaries = NULL; @@ -2932,20 +2957,14 @@ dispose(GObject *object) g_clear_object(&priv->dns_manager); } - g_hash_table_iter_init(&h_iter, priv->devices); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) { - g_hash_table_iter_remove(&h_iter); - devices_list_unregister(self, device); - } - /* The manager should have disposed of ActiveConnections already, which * will have called active_connection_removed() and thus we don't need * to clean anything up. Assert that this is TRUE. */ nm_assert(c_list_is_empty(nm_manager_get_active_connections(priv->manager))); - nm_clear_g_source(&priv->reset_retries_id); - nm_clear_g_source(&priv->schedule_activate_all_id); + nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source); + nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source); nm_clear_g_free(&priv->orig_hostname); nm_clear_g_free(&priv->cur_hostname); diff --git a/src/core/nm-policy.h b/src/core/nm-policy.h index f2b98dbd..9cfb0b24 100644 --- a/src/core/nm-policy.h +++ b/src/core/nm-policy.h @@ -34,6 +34,8 @@ NMActiveConnection *nm_policy_get_activating_ip6_ac(NMPolicy *policy); void nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self); +void nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device); + /** * NMPolicyHostnameMode * @NM_POLICY_HOSTNAME_MODE_NONE: never update the transient hostname. diff --git a/src/core/nm-test-utils-core.h b/src/core/nm-test-utils-core.h index e341d62f..176f4320 100644 --- a/src/core/nm-test-utils-core.h +++ b/src/core/nm-test-utils-core.h @@ -49,8 +49,8 @@ nmtst_platform_ip4_address_full(const char *address, { NMPlatformIP4Address *addr = nmtst_platform_ip4_address(address, peer_address, plen); - G_STATIC_ASSERT(NMP_IFNAMSIZ == sizeof(addr->label)); - g_assert(!label || strlen(label) < NMP_IFNAMSIZ); + G_STATIC_ASSERT(NM_IFNAMSIZ == sizeof(addr->label)); + g_assert(!label || strlen(label) < NM_IFNAMSIZ); addr->ifindex = ifindex; addr->addr_source = source; diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c index 0c366d82..f6f377d4 100644 --- a/src/core/platform/nm-fake-platform.c +++ b/src/core/platform/nm-fake-platform.c @@ -102,7 +102,9 @@ static gboolean ip6_address_add(NMPlatform *platform, struct in6_addr peer_addr, guint32 lifetime, guint32 preferred, - guint flags); + guint flags, + char **out_extack_msg); + static gboolean ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen); @@ -276,6 +278,24 @@ link_add_pre(NMPlatform *platform, return device; } +static void +link_add_post(NMPlatform *self, NMFakePlatformLink *device) +{ + char path[128]; + + switch (device->obj->link.type) { + case NM_LINK_TYPE_BRIDGE: + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", device->obj->link.name); + sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), "1"); + + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", device->obj->link.name); + sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), "0"); + break; + default: + break; + } +} + static int link_add(NMPlatform *platform, NMLinkType type, @@ -387,6 +407,7 @@ link_add(NMPlatform *platform, *out_link = NMP_OBJECT_CAST_LINK(device->obj); link_changed(platform, device, cache_op, NULL); + link_add_post(platform, device); if (veth_peer) link_changed(platform, device_veth, cache_op_veth, NULL); @@ -439,6 +460,7 @@ link_add_one(NMPlatform *platform, static gboolean link_delete(NMPlatform *platform, int ifindex) { + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform); NMFakePlatformLink *device = link_get(platform, ifindex); nm_auto_nmpobj const NMPObject *obj_old = NULL; nm_auto_nmpobj const NMPObject *obj_old2 = NULL; @@ -449,6 +471,17 @@ link_delete(NMPlatform *platform, int ifindex) obj_old = g_steal_pointer(&device->obj); + if (obj_old->link.type == NM_LINK_TYPE_BRIDGE) { + char path[128]; + + g_hash_table_remove( + priv->options, + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", obj_old->link.name)); + g_hash_table_remove( + priv->options, + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", obj_old->link.name)); + } + cache_op = nmp_cache_remove(nm_platform_get_cache(platform), obj_old, FALSE, FALSE, &obj_old2); g_assert(cache_op == NMP_CACHE_OPS_REMOVED); g_assert(obj_old2); @@ -542,7 +575,7 @@ link_changed(NMPlatform *platform, nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, device->obj); if (!IN6_IS_ADDR_UNSPECIFIED(&device->ip6_lladdr)) { - if (device->obj->link.connected) + if (device->obj->link.connected) { ip6_address_add(platform, device->obj->link.ifindex, device->ip6_lladdr, @@ -550,8 +583,9 @@ link_changed(NMPlatform *platform, in6addr_any, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, - 0); - else + 0, + NULL); + } else ip6_address_delete(platform, device->obj->link.ifindex, device->ip6_lladdr, 64); } @@ -669,8 +703,10 @@ link_supports_sriov(NMPlatform *platform, int ifindex) static gboolean link_change(NMPlatform *platform, int ifindex, + NMPlatformLinkProps *props, NMPortKind port_kind, - const NMPlatformLinkPortData *port_data) + const NMPlatformLinkPortData *port_data, + NMPlatformLinkChangeFlags flags) { NMFakePlatformLink *device = link_get(platform, ifindex); nm_auto_nmpobj NMPObject *obj_tmp = NULL; @@ -747,6 +783,34 @@ link_vlan_change(NMPlatform *platform, return FALSE; } +static gboolean +link_set_bridge_info(NMPlatform *self, + int ifindex, + const NMPlatformLinkSetBridgeInfoData *bridge_info) +{ + NMFakePlatformLink *link; + char path[128]; + char value[128]; + + link = link_get(self, ifindex); + if (!link) + return FALSE; + + if (bridge_info->vlan_default_pvid_has) { + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", link->obj->link.name); + nm_sprintf_buf(value, "%u", bridge_info->vlan_default_pvid_val); + sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), value); + } + + if (bridge_info->vlan_filtering_has) { + nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", link->obj->link.name); + nm_sprintf_buf(value, "%u", bridge_info->vlan_filtering_val); + sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), value); + } + + return TRUE; +} + struct infiniband_add_data { int parent; int p_key; @@ -785,7 +849,7 @@ infiniband_partition_add(NMPlatform *platform, parent_device = link_get(platform, parent); g_return_val_if_fail(parent_device != NULL, FALSE); - nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key); + nm_net_devname_infiniband(name, parent_device->obj->link.name, p_key); link_add_one(platform, name, NM_LINK_TYPE_INFINIBAND, _infiniband_add_prepare, &d, out_link); return TRUE; @@ -800,7 +864,7 @@ infiniband_partition_delete(NMPlatform *platform, int parent, int p_key) parent_device = link_get(platform, parent); g_return_val_if_fail(parent_device != NULL, FALSE); - nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key); + nm_net_devname_infiniband(name, parent_device->obj->link.name, p_key); return link_delete(platform, nm_platform_link_get_ifindex(platform, name)); } @@ -890,7 +954,10 @@ mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) /*****************************************************************************/ static gboolean -ipx_address_add(NMPlatform *platform, int addr_family, const NMPlatformObject *address) +ipx_address_add(NMPlatform *platform, + int addr_family, + const NMPlatformObject *address, + char **out_extack_msg) { nm_auto_nmpobj NMPObject *obj = NULL; NMPCacheOpsType cache_op; @@ -899,6 +966,7 @@ ipx_address_add(NMPlatform *platform, int addr_family, const NMPlatformObject *a NMPCache *cache = nm_platform_get_cache(platform); g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + g_assert(!out_extack_msg || !*out_extack_msg); obj = nmp_object_new(addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, @@ -919,7 +987,8 @@ ip4_address_add(NMPlatform *platform, guint32 lifetime, guint32 preferred, guint32 flags, - const char *label) + const char *label, + char **out_extack_msg) { NMPlatformIP4Address address; @@ -939,7 +1008,7 @@ ip4_address_add(NMPlatform *platform, if (label) g_strlcpy(address.label, label, sizeof(address.label)); - return ipx_address_add(platform, AF_INET, (const NMPlatformObject *) &address); + return ipx_address_add(platform, AF_INET, (const NMPlatformObject *) &address, out_extack_msg); } static gboolean @@ -950,7 +1019,8 @@ ip6_address_add(NMPlatform *platform, struct in6_addr peer_addr, guint32 lifetime, guint32 preferred, - guint32 flags) + guint32 flags, + char **out_extack_msg) { NMPlatformIP6Address address; @@ -967,7 +1037,7 @@ ip6_address_add(NMPlatform *platform, address.preferred = preferred; address.n_ifa_flags = flags; - return ipx_address_add(platform, AF_INET6, (const NMPlatformObject *) &address); + return ipx_address_add(platform, AF_INET6, (const NMPlatformObject *) &address, out_extack_msg); } static gboolean @@ -1117,7 +1187,7 @@ object_delete(NMPlatform *platform, const NMPObject *obj) } static int -ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack) +ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg) { NMDedupMultiIter iter; nm_auto_nmpobj NMPObject *obj = NULL; @@ -1139,6 +1209,7 @@ ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack) g_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_stack), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + g_assert(!out_extack_msg || !*out_extack_msg); addr_family = NMP_OBJECT_GET_ADDR_FAMILY(obj_stack); @@ -1364,6 +1435,8 @@ nm_fake_platform_class_init(NMFakePlatformClass *klass) platform_class->link_vlan_change = link_vlan_change; + platform_class->link_set_bridge_info = link_set_bridge_info; + platform_class->infiniband_partition_add = infiniband_partition_add; platform_class->infiniband_partition_delete = infiniband_partition_delete; diff --git a/src/core/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c index 139a0280..c643e71c 100644 --- a/src/core/platform/tests/test-cleanup.c +++ b/src/core/platform/tests/test-cleanup.c @@ -77,6 +77,7 @@ test_cleanup_internal(void) lifetime, preferred, 0, + NULL, NULL)); g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, ifindex, @@ -85,7 +86,8 @@ test_cleanup_internal(void) in6addr_any, lifetime, preferred, - flags)); + flags, + NULL)); nmtstp_ip4_route_add(NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c index 65bdfeae..fde7dc0d 100644 --- a/src/core/platform/tests/test-common.c +++ b/src/core/platform/tests/test-common.c @@ -1809,7 +1809,8 @@ _ip_address_add(NMPlatform *platform, lifetime, preferred, flags, - label); + label, + NULL); } else { g_assert(label == NULL); success = nm_platform_ip6_address_add(platform, @@ -1819,7 +1820,8 @@ _ip_address_add(NMPlatform *platform, peer_address->addr6, lifetime, preferred, - flags); + flags, + NULL); } g_assert(success); } diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 33858671..8a54ac48 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -36,6 +36,16 @@ #define _ADD_DUMMY(platform, name) \ g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add((platform), (name), NULL))) +#define _sysctl_assert_eq(plat, path, value) \ + G_STMT_START \ + { \ + gs_free char *_val = NULL; \ + \ + _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \ + g_assert_cmpstr(_val, ==, value); \ + } \ + G_STMT_END + static void test_bogus(void) { @@ -212,58 +222,58 @@ test_link_changed_signal_cb(NMPlatform *platform, } static void -test_slave(int master, int type, SignalData *master_changed) +test_port(int controller, int port_type, SignalData *controller_changed) { - int ifindex; + int ifindex_port; SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, SLAVE_NAME); SignalData *link_changed, *link_removed; char *value; - NMLinkType link_type = nm_platform_link_get_type(NM_PLATFORM_GET, master); + NMLinkType controller_type = nm_platform_link_get_type(NM_PLATFORM_GET, controller); gboolean test_link_changed_signal_arg1; gboolean test_link_changed_signal_arg2; - g_assert(NM_IN_SET(link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE)); + g_assert(NM_IN_SET(controller_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE)); - g_assert(software_add(type, SLAVE_NAME)); - ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME); - g_assert(ifindex > 0); + g_assert(software_add(port_type, SLAVE_NAME)); + ifindex_port = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME); + g_assert(ifindex_port > 0); link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, link_callback, - ifindex); + ifindex_port); link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, - ifindex); + ifindex_port); accept_signal(link_added); - /* Set the slave up to see whether master's IFF_LOWER_UP is set correctly. + /* Set the port up to see whether controller's IFF_LOWER_UP is set correctly. * * See https://bugzilla.redhat.com/show_bug.cgi?id=910348 */ - g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); - g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0); - g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port)); + g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, FALSE) >= 0); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port)); ensure_no_signal(link_changed); - /* Enslave */ - link_changed->ifindex = ifindex; - g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); - g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, master); + /* Attach port */ + link_changed->ifindex = ifindex_port; + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, controller, ifindex_port)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex_port), ==, controller); accept_signals(link_changed, 1, 3); - accept_signals(master_changed, 0, 2); + accept_signals(controller_changed, 0, 2); - /* enslaveing brings put the slave */ - if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) - g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + /* Attaching ports brings up the port */ + if (NM_IN_SET(controller_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port)); else - g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port)); - if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND)) { + if (NM_IN_SET(controller_type, NM_LINK_TYPE_BOND)) { NMPlatformLinkBondPort bond_port; gboolean prio_has; gboolean prio_supported; @@ -273,7 +283,7 @@ test_slave(int master, int type, SignalData *master_changed) link = nmtstp_link_get_typed(NM_PLATFORM_GET, 0, SLAVE_NAME, NM_LINK_TYPE_DUMMY); g_assert(link); - lnk = nm_platform_link_get_lnk_bond(NM_PLATFORM_GET, master, NULL); + lnk = nm_platform_link_get_lnk_bond(NM_PLATFORM_GET, controller, NULL); g_assert(lnk); g_assert(NM_IN_SET(lnk->mode, 3, 1)); @@ -286,13 +296,24 @@ test_slave(int master, int type, SignalData *master_changed) .prio = prio_has ? 6 : 0, }; - g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex, &bond_port)); + g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex_port, NULL, &bond_port, 0)); accept_signals(link_changed, 1, 3); - link = nmtstp_link_get(NM_PLATFORM_GET, ifindex, SLAVE_NAME); + link = nmtstp_link_get(NM_PLATFORM_GET, ifindex_port, SLAVE_NAME); g_assert(link); g_assert_cmpint(link->port_data.bond.queue_id, ==, 5); g_assert(link->port_data.bond.prio_has || link->port_data.bond.prio == 0); + } else if (controller_type == NM_LINK_TYPE_BRIDGE) { + /* Skip this part for nm-fake-platform */ + if (nmtstp_is_root_test() && nmtstp_is_sysfs_writable()) { + g_assert(nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET, + ifindex_port, + "priority", + "614")); + value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex_port, "priority"); + g_assert_cmpstr(value, ==, "614"); + g_free(value); + } } test_link_changed_signal_arg1 = FALSE; @@ -306,10 +327,10 @@ test_slave(int master, int type, SignalData *master_changed) G_CALLBACK(test_link_changed_signal_cb), &test_link_changed_signal_arg2); - /* Set master up */ - g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, master, IFF_UP, TRUE) >= 0); - g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, master)); - accept_signals(master_changed, 1, 3); + /* Set controller up */ + g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, controller, IFF_UP, TRUE) >= 0); + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, controller)); + accept_signals(controller_changed, 1, 3); g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET, G_CALLBACK(test_link_changed_signal_cb), @@ -320,28 +341,28 @@ test_slave(int master, int type, SignalData *master_changed) g_assert(test_link_changed_signal_arg1); g_assert(test_link_changed_signal_arg2); - /* Master with a disconnected slave is disconnected + /* Master with a disconnected port is disconnected * - * For some reason, bonding and teaming slaves are automatically set up. We + * For some reason, bonding and teaming ports are automatically set up. We * need to set them back down for this test. */ - switch (nm_platform_link_get_type(NM_PLATFORM_GET, master)) { + switch (nm_platform_link_get_type(NM_PLATFORM_GET, controller)) { case NM_LINK_TYPE_BOND: case NM_LINK_TYPE_TEAM: - g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0); + g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, FALSE) >= 0); accept_signal(link_changed); - accept_signals(master_changed, 0, 3); + accept_signals(controller_changed, 0, 3); break; default: break; } - g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); - g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); - if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, master)) { - if (nm_platform_link_get_type(NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) { - /* Older team versions (e.g. Fedora 17) have a bug that team master stays - * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if - * `ip link` also claims master to be up, accept it. */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex_port)); + if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, controller)) { + if (nm_platform_link_get_type(NM_PLATFORM_GET, controller) == NM_LINK_TYPE_TEAM) { + /* Older team versions (e.g. Fedora 17) have a bug that team controller stays + * IFF_LOWER_UP even if its port is down. Double check it with iproute2 and if + * `ip link` also claims controller to be up, accept it. */ char *stdout_str = NULL; nmtst_spawn_sync(NULL, @@ -352,7 +373,7 @@ test_slave(int master, int type, SignalData *master_changed) "link", "show", "dev", - nm_platform_link_get_name(NM_PLATFORM_GET, master)); + nm_platform_link_get_name(NM_PLATFORM_GET, controller)); g_assert(strstr(stdout_str, "LOWER_UP")); g_free(stdout_str); @@ -360,44 +381,29 @@ test_slave(int master, int type, SignalData *master_changed) g_assert_not_reached(); } - /* Set slave up and see if master gets up too */ - g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0); - g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); - g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, master)); + /* Set port up and see if controller gets up too */ + g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, TRUE) >= 0); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex_port)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, controller)); accept_signals(link_changed, 1, 3); /* NM running, can cause additional change of addrgenmode */ - accept_signals(master_changed, 0, 3); + accept_signals(controller_changed, 0, 3); - /* Enslave again + /* Attach port again * - * Gracefully succeed if already enslaved. + * Gracefully succeed if already attached port. */ ensure_no_signal(link_changed); - g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, controller, ifindex_port)); accept_signals(link_changed, 0, 2); - accept_signals(master_changed, 0, 2); - - /* Set slave option */ - switch (type) { - case NM_LINK_TYPE_BRIDGE: - if (nmtstp_is_sysfs_writable()) { - g_assert( - nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET, ifindex, "priority", "614")); - value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex, "priority"); - g_assert_cmpstr(value, ==, "614"); - g_free(value); - } - break; - default: - break; - } + accept_signals(controller_changed, 0, 2); /* Release */ ensure_no_signal(link_added); ensure_no_signal(link_changed); ensure_no_signal(link_removed); - g_assert(nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); - g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, 0); + g_assert(nm_platform_link_release(NM_PLATFORM_GET, controller, ifindex_port)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex_port), ==, 0); if (link_changed->received_count > 0) { accept_signals(link_added, 0, 1); accept_signals(link_changed, 1, 5); @@ -409,22 +415,22 @@ test_slave(int master, int type, SignalData *master_changed) ensure_no_signal(link_changed); accept_signal(link_removed); } - accept_signals(master_changed, 0, 3); + accept_signals(controller_changed, 0, 3); - ensure_no_signal(master_changed); + ensure_no_signal(controller_changed); /* Release again */ ensure_no_signal(link_changed); - g_assert(!nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); + g_assert(!nm_platform_link_release(NM_PLATFORM_GET, controller, ifindex_port)); - ensure_no_signal(master_changed); + ensure_no_signal(controller_changed); /* Remove */ ensure_no_signal(link_added); ensure_no_signal(link_changed); ensure_no_signal(link_removed); - nmtstp_link_delete(NULL, -1, ifindex, NULL, TRUE); - accept_signals(master_changed, 0, 1); + nmtstp_link_delete(NULL, -1, ifindex_port, NULL, TRUE); + accept_signals(controller_changed, 0, 1); accept_signals(link_changed, 0, 1); accept_signal(link_removed); @@ -528,7 +534,7 @@ test_software(NMLinkType link_type, const char *link_typename) case NM_LINK_TYPE_BOND: case NM_LINK_TYPE_TEAM: link_changed->ifindex = ifindex; - test_slave(ifindex, NM_LINK_TYPE_DUMMY, link_changed); + test_port(ifindex, NM_LINK_TYPE_DUMMY, link_changed); link_changed->ifindex = 0; break; default: @@ -618,10 +624,14 @@ test_vlan(void) static void test_bridge_addr(void) { - char addr[ETH_ALEN]; - NMPlatformLink link; - const NMPlatformLink *plink = NULL; - NMPLinkAddress hw_perm_addr; + char addr[ETH_ALEN]; + NMPlatformLink link; + const NMPlatformLink *plink = NULL; + NMPLinkAddress hw_perm_addr; + gboolean b; + char sbuf[100]; + gs_free char *str = NULL; + NMPlatformLinkSetBridgeInfoData info_data; nm_utils_hwaddr_aton("de:ad:be:ef:00:11", addr, sizeof(addr)); @@ -692,6 +702,42 @@ test_bridge_addr(void) g_assert_cmpint(plink->l_address.len, ==, sizeof(addr)); g_assert(!memcmp(plink->l_address.data, addr, sizeof(addr))); + info_data = (const NMPlatformLinkSetBridgeInfoData){ + .vlan_default_pvid_val = nmtst_rand_select(0, 5, 42, 1048), + .vlan_default_pvid_has = nmtst_get_rand_bool(), + .vlan_filtering_val = nmtst_get_rand_bool(), + .vlan_filtering_has = nmtst_get_rand_bool(), + }; + b = nm_platform_link_set_bridge_info(NM_PLATFORM_GET, link.ifindex, &info_data); + g_assert(b); + + _sysctl_assert_eq(NM_PLATFORM_GET, + "/sys/class/net/" DEVICE_NAME "/bridge/default_pvid", + info_data.vlan_default_pvid_has + ? nm_sprintf_buf(sbuf, "%u", info_data.vlan_default_pvid_val) + : "1"); + + _sysctl_assert_eq(NM_PLATFORM_GET, + "/sys/class/net/" DEVICE_NAME "/bridge/vlan_filtering", + info_data.vlan_filtering_val && info_data.vlan_filtering_has ? "1" : "0"); + + info_data = (const NMPlatformLinkSetBridgeInfoData){ + .vlan_default_pvid_val = 55, + .vlan_default_pvid_has = TRUE, + .vlan_filtering_val = !info_data.vlan_filtering_val, + .vlan_filtering_has = TRUE, + }; + b = nm_platform_link_set_bridge_info(NM_PLATFORM_GET, link.ifindex, &info_data); + g_assert(b); + + _sysctl_assert_eq(NM_PLATFORM_GET, + "/sys/class/net/" DEVICE_NAME "/bridge/default_pvid", + nm_sprintf_buf(sbuf, "%u", info_data.vlan_default_pvid_val)); + + _sysctl_assert_eq(NM_PLATFORM_GET, + "/sys/class/net/" DEVICE_NAME "/bridge/vlan_filtering", + info_data.vlan_filtering_val ? "1" : "0"); + nmtstp_link_delete(NULL, -1, link.ifindex, link.name, TRUE); } @@ -2630,6 +2676,36 @@ test_vlan_set_xgress(void) /*****************************************************************************/ static void +test_link_set_properties(void) +{ + const NMPlatformLink *link; + NMPlatformLinkProps props; + NMPlatformLinkChangeFlags flags; + int ifindex; + + props = (NMPlatformLinkProps){ + .tx_queue_length = 599, + .gso_max_size = 10001, + .gso_max_segments = 512, + }; + flags = NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE + | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS; + + ifindex = nmtstp_link_dummy_add(NM_PLATFORM_GET, FALSE, "dummy1")->ifindex; + g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex, &props, NULL, flags)); + + link = nmtstp_link_get(NM_PLATFORM_GET, ifindex, "dummy1"); + g_assert(link); + g_assert_cmpint(link->link_props.tx_queue_length, ==, 599); + g_assert_cmpint(link->link_props.gso_max_size, ==, 10001); + g_assert_cmpint(link->link_props.gso_max_segments, ==, 512); + + nmtstp_link_delete(NULL, -1, link->ifindex, "dummy1", TRUE); +} + +/*****************************************************************************/ + +static void test_create_many_links_do(guint n_devices) { gint64 time, start_time = nm_utils_get_monotonic_timestamp_nsec(); @@ -2985,16 +3061,6 @@ _check_sysctl_skip(void) /*****************************************************************************/ -#define _sysctl_assert_eq(plat, path, value) \ - G_STMT_START \ - { \ - gs_free char *_val = NULL; \ - \ - _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \ - g_assert_cmpstr(_val, ==, value); \ - } \ - G_STMT_END - static void test_netns_general(gpointer fixture, gconstpointer test_data) { @@ -3533,7 +3599,7 @@ test_sysctl_rename(void) ==, (gint32) nm_platform_sysctl_get_int32( PL, - NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"), + NMP_SYSCTL_PATHID_NETDIR_A(dirfd, s ?: "<unknown>", "ifindex"), -1)); break; } @@ -3605,7 +3671,7 @@ test_sysctl_netns_switch(void) ==, (gint32) nm_platform_sysctl_get_int32( PL, - NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"), + NMP_SYSCTL_PATHID_NETDIR_A(dirfd, s ?: "<unknown>", "ifindex"), -1)); g_assert_cmpint( ifindex, @@ -4022,6 +4088,8 @@ _nmtstp_setup_tests(void) g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress); + g_test_add_func("/link/set-properties", test_link_set_properties); + g_test_add_data_func("/link/create-many-links/20", GUINT_TO_POINTER(20), test_create_many_links); diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index bd8fdc27..9aa21a9a 100644 --- a/src/core/platform/tests/test-route.c +++ b/src/core/platform/tests/test-route.c @@ -421,7 +421,8 @@ test_ip6_route(void) in6addr_any, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, - 0)); + 0, + NULL)); accept_signals(route_added, 0, 3); _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 200, ifindex, 1, &pref_src); @@ -706,7 +707,8 @@ test_ip4_route_options(gconstpointer test_data) a->lifetime, a->preferred, a->n_ifa_flags, - a->label)); + a->label, + NULL)); if (a->peer_address == a->address) _wait_for_ipv4_addr_device_route(NM_PLATFORM_GET, 200, a->ifindex, a->address, a->plen); } @@ -878,7 +880,8 @@ test_ip6_route_options(gconstpointer test_data) addr[i].peer_address, addr[i].lifetime, addr[i].preferred, - addr[i].n_ifa_flags)); + addr[i].n_ifa_flags, + NULL)); } _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6); diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c index a3df4497..bb300345 100644 --- a/src/core/settings/nm-secret-agent.c +++ b/src/core/settings/nm-secret-agent.c @@ -463,7 +463,7 @@ _call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data) * nm_secret_agent_cancel_call: * @self: the #NMSecretAgent instance for the @call_id. * Maybe be %NULL if @call_id is %NULL. - * @call_id: (allow-none): the call id to cancel. May be %NULL for convenience, + * @call_id: (nullable): the call id to cancel. May be %NULL for convenience, * in which case it does nothing. * * It is an error to pass an invalid @call_id or a @call_id for an operation diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index 024c0009..176cc2c2 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -24,13 +24,10 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-audit-manager.h" #include "nm-settings.h" +#include "nm-manager.h" #include "nm-dbus-manager.h" #include "settings/plugins/keyfile/nms-keyfile-storage.h" -#define AUTOCONNECT_RETRIES_UNSET -2 -#define AUTOCONNECT_RETRIES_FOREVER -1 -#define AUTOCONNECT_RESET_RETRIES_TIMER 300 - #define SEEN_BSSIDS_MAX 30 #define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES \ @@ -112,7 +109,11 @@ _seen_bssids_hash_new(void) /*****************************************************************************/ -NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, PROP_UNSAVED, PROP_FLAGS, PROP_FILENAME, ); +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, + PROP_VERSION_ID, + PROP_UNSAVED, + PROP_FLAGS, + PROP_FILENAME, ); enum { UPDATED_INTERNAL, FLAGS_CHANGED, LAST_SIGNAL }; @@ -159,9 +160,7 @@ typedef struct _NMSettingsConnectionPrivate { guint64 last_secret_agent_version_id; - int autoconnect_retries; - - gint32 autoconnect_retries_blocked_until; + guint64 version_id; bool timestamp_set : 1; @@ -227,6 +226,22 @@ static guint _get_seen_bssids(NMSettingsConnection *self, /*****************************************************************************/ +NMSettings * +nm_settings_connection_get_settings(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings; +} + +NMManager * +nm_settings_connection_get_manager(NMSettingsConnection *self) +{ + return nm_settings_get_manager(nm_settings_connection_get_settings(self)); +} + +/*****************************************************************************/ + NMDevice * nm_settings_connection_default_wired_get_device(NMSettingsConnection *self) { @@ -361,6 +376,20 @@ nm_settings_connection_get_connection(NMSettingsConnection *self) return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection; } +gpointer +nm_settings_connection_get_setting(NMSettingsConnection *self, NMMetaSettingType meta_type) +{ + NMConnection *connection; + + nm_assert(NM_IS_SETTINGS_CONNECTION(self)); + + connection = NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection; + + nm_assert(NM_IS_SIMPLE_CONNECTION(connection)); + + return _nm_connection_get_setting_by_metatype_unsafe(connection, meta_type); +} + void _nm_settings_connection_set_connection(NMSettingsConnection *self, NMConnection *new_connection, @@ -1049,7 +1078,7 @@ get_secrets_idle_cb(NMSettingsConnectionCallId *call_id) /** * nm_settings_connection_get_secrets: * @self: the #NMSettingsConnection - * @applied_connection: (allow-none): if provided, only request secrets + * @applied_connection: (nullable): if provided, only request secrets * if @self equals to @applied_connection. Also, update the secrets * in the @applied_connection. * @subject: the #NMAuthSubject originating the request @@ -1414,6 +1443,7 @@ typedef struct { NMSettingsUpdate2Flags flags; char *audit_args; char *plugin_name; + guint64 version_id; bool is_update2 : 1; } UpdateInfo; @@ -1442,53 +1472,7 @@ update_complete(NMSettingsConnection *self, UpdateInfo *info, GError *error) g_clear_object(&info->new_settings); g_free(info->audit_args); g_free(info->plugin_name); - g_slice_free(UpdateInfo, info); -} - -static int -_autoconnect_retries_initial(NMSettingsConnection *self) -{ - NMSettingConnection *s_con; - int retries = -1; - - s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self)); - if (s_con) - retries = nm_setting_connection_get_autoconnect_retries(s_con); - - /* -1 means 'default' */ - if (retries == -1) - retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA); - - /* 0 means 'forever', which is translated to a retry count of -1 */ - if (retries == 0) - retries = AUTOCONNECT_RETRIES_FOREVER; - - nm_assert(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0); - return retries; -} - -static void -_autoconnect_retries_set(NMSettingsConnection *self, int retries, gboolean is_reset) -{ - NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); - - g_return_if_fail(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0); - - if (priv->autoconnect_retries != retries) { - _LOGT("autoconnect: retries set %d%s", retries, is_reset ? " (reset)" : ""); - priv->autoconnect_retries = retries; - } - - if (retries) - priv->autoconnect_retries_blocked_until = 0; - else { - /* NOTE: the blocked time must be identical for all connections, otherwise - * the tracking of resetting the retry count in NMPolicy needs adjustment - * in _connection_autoconnect_retries_set() (as it would need to re-evaluate - * the next-timeout every time a connection gets blocked). */ - priv->autoconnect_retries_blocked_until = - nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER; - } + nm_g_slice_free(info); } static void @@ -1502,14 +1486,23 @@ update_auth_cb(NMSettingsConnection *self, UpdateInfo *info = data; gs_free_error GError *local = NULL; NMSettingsConnectionPersistMode persist_mode; + gs_unref_object NMConnection *for_agent = NULL; - if (error) { - update_complete(self, info, error); - return; - } + if (error) + goto out; priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + if (info->version_id != 0 && info->version_id != priv->version_id) { + g_set_error_literal(&local, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_VERSION_ID_MISMATCH, + "Update failed because profile changed in the meantime and the " + "version-id mismatches"); + error = local; + goto out; + } + if (info->new_settings) { if (!_nm_connection_aggregate(info->new_settings, NM_CONNECTION_AGGREGATE_ANY_SECRETS, @@ -1539,10 +1532,13 @@ update_auth_cb(NMSettingsConnection *self, /* New secrets, allow autoconnection again */ if (nm_settings_connection_autoconnect_blocked_reason_set( self, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS, FALSE) && !nm_settings_connection_autoconnect_blocked_reason_get(self)) - nm_settings_connection_autoconnect_retries_reset(self); + nm_manager_devcon_autoconnect_retries_reset( + nm_settings_connection_get_manager(self), + NULL, + self); } } @@ -1586,10 +1582,9 @@ update_auth_cb(NMSettingsConnection *self, : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE), NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, - NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME - | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY) - ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE - : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL) + (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY) + ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE + : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL) | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET @@ -1599,25 +1594,29 @@ update_auth_cb(NMSettingsConnection *self, "update-from-dbus", &local); - if (!local) { - gs_unref_object NMConnection *for_agent = NULL; - - /* Dupe the connection so we can clear out non-agent-owned secrets, - * as agent-owned secrets are the only ones we send back to be saved. - * Only send secrets to agents of the same UID that called update too. - */ - for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self)); - _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED); - nm_agent_manager_save_secrets(info->agent_mgr, - nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), - for_agent, - info->subject); + if (local) { + error = local; + goto out; } + /* Dupe the connection so we can clear out non-agent-owned secrets, + * as agent-owned secrets are the only ones we send back to be saved. + * Only send secrets to agents of the same UID that called update too. + */ + for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self)); + _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED); + nm_agent_manager_save_secrets(info->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), + for_agent, + info->subject); + /* Reset auto retries back to default since connection was updated */ - nm_settings_connection_autoconnect_retries_reset(self); + nm_manager_devcon_autoconnect_retries_reset(nm_settings_connection_get_manager(self), + NULL, + self); - update_complete(self, info, local); +out: + update_complete(self, info, error); } static const char * @@ -1650,6 +1649,7 @@ settings_connection_update(NMSettingsConnection *self, GDBusMethodInvocation *context, GVariant *new_settings, const char *plugin_name, + guint64 version_id, NMSettingsUpdate2Flags flags) { NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); @@ -1697,14 +1697,17 @@ settings_connection_update(NMSettingsConnection *self, &error)) goto error; - info = g_slice_new0(UpdateInfo); - info->is_update2 = is_update2; - info->context = context; - info->agent_mgr = g_object_ref(priv->agent_mgr); - info->subject = subject; - info->flags = flags; - info->new_settings = tmp; - info->plugin_name = g_strdup(plugin_name); + info = g_slice_new(UpdateInfo); + *info = (UpdateInfo){ + .is_update2 = is_update2, + .context = context, + .agent_mgr = g_object_ref(priv->agent_mgr), + .subject = subject, + .flags = flags, + .new_settings = tmp, + .plugin_name = g_strdup(plugin_name), + .version_id = version_id, + }; permission = get_update_modify_permission(nm_settings_connection_get_connection(self), tmp ?: nm_settings_connection_get_connection(self)); @@ -1738,6 +1741,7 @@ impl_settings_connection_update(NMDBusObject *obj, invocation, settings, NULL, + 0, NM_SETTINGS_UPDATE2_FLAG_TO_DISK); } @@ -1759,6 +1763,7 @@ impl_settings_connection_update_unsaved(NMDBusObject *obj, invocation, settings, NULL, + 0, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY); } @@ -1778,6 +1783,7 @@ impl_settings_connection_save(NMDBusObject *obj, invocation, NULL, NULL, + 0, NM_SETTINGS_UPDATE2_FLAG_TO_DISK); } @@ -1794,6 +1800,7 @@ impl_settings_connection_update2(NMDBusObject *obj, gs_unref_variant GVariant *settings = NULL; gs_unref_variant GVariant *args = NULL; gs_free char *plugin_name = NULL; + guint64 version_id = 0; guint32 flags_u; GError *error = NULL; GVariantIter iter; @@ -1840,6 +1847,11 @@ impl_settings_connection_update2(NMDBusObject *obj, plugin_name = g_variant_dup_string(args_value, NULL); continue; } + if (nm_streq(args_name, "version-id") + && g_variant_is_of_type(args_value, G_VARIANT_TYPE_UINT64)) { + version_id = g_variant_get_uint64(args_value); + continue; + } error = g_error_new(NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_ARGUMENTS, @@ -1849,7 +1861,7 @@ impl_settings_connection_update2(NMDBusObject *obj, return; } - settings_connection_update(self, TRUE, invocation, settings, plugin_name, flags); + settings_connection_update(self, TRUE, invocation, settings, plugin_name, version_id, flags); } static void @@ -2542,56 +2554,6 @@ nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self) /*****************************************************************************/ -/** - * nm_settings_connection_autoconnect_retries_get: - * @self: the settings connection - * - * Returns the number of autoconnect retries left. If the value is - * not yet set, initialize it with the value from the connection or - * with the global default. - */ -int -nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self) -{ - NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); - - if (G_UNLIKELY(priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET)) { - _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE); - } - return priv->autoconnect_retries; -} - -void -nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries) -{ - g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); - g_return_if_fail(retries >= 0); - - _autoconnect_retries_set(self, retries, FALSE); -} - -void -nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self) -{ - g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); - - _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE); -} - -gint32 -nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self) -{ - return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_retries_blocked_until; -} - -static NM_UTILS_FLAGS2STR_DEFINE( - _autoconnect_blocked_reason_to_string, - NMSettingsAutoconnectBlockedReason, - NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE, "none"), - NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"), - NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, "failed"), - NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), ); - NMSettingsAutoconnectBlockedReason nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self) { @@ -2599,25 +2561,41 @@ nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self } gboolean -nm_settings_connection_autoconnect_blocked_reason_set_full(NMSettingsConnection *self, - NMSettingsAutoconnectBlockedReason mask, - NMSettingsAutoconnectBlockedReason value) +nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection *self, + NMSettingsAutoconnectBlockedReason reason, + gboolean set) { NMSettingsAutoconnectBlockedReason v; NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); - char buf[100]; + char buf1[200]; + char buf2[200]; - nm_assert(mask); - nm_assert(!NM_FLAGS_ANY(value, ~mask)); + nm_assert(reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE); + nm_assert(!NM_FLAGS_ANY(reason, + ~(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST + | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS))); v = priv->autoconnect_blocked_reason; - v = (v & ~mask) | (value & mask); + v = NM_FLAGS_ASSIGN(v, reason, set); if (priv->autoconnect_blocked_reason == v) return FALSE; - _LOGT("autoconnect: blocked reason: %s", - _autoconnect_blocked_reason_to_string(v, buf, sizeof(buf))); + if (set) { + _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)", + nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)), + "just blocked", + nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2))); + } else if (v != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) { + _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)", + nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)), + "just unblocked", + nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2))); + } else { + _LOGT("block-autoconnect: profile: not blocked (unblocked %s)", + nm_settings_autoconnect_blocked_reason_to_string(reason, buf1, sizeof(buf1))); + } + priv->autoconnect_blocked_reason = v; return TRUE; } @@ -2632,9 +2610,7 @@ nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self) priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); - if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) - return TRUE; - if (priv->autoconnect_retries == 0) + if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) return TRUE; flags = priv->flags; @@ -2693,6 +2669,23 @@ nm_settings_connection_get_uuid(NMSettingsConnection *self) return uuid; } +guint64 +nm_settings_connection_get_version_id(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id; +} + +void +nm_settings_connection_bump_version_id(NMSettingsConnection *self) +{ + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + + NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id++; + _notify(self, PROP_VERSION_ID); +} + const char * nm_settings_connection_get_connection_type(NMSettingsConnection *self) { @@ -2716,9 +2709,13 @@ _nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self) static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { - NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object); + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object); + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); switch (prop_id) { + case PROP_VERSION_ID: + g_value_set_uint64(value, priv->version_id); + break; case PROP_UNSAVED: g_value_set_boolean(value, nm_settings_connection_get_unsaved(self)); break; @@ -2748,6 +2745,7 @@ nm_settings_connection_init(NMSettingsConnection *self) self->_priv = priv; c_list_init(&self->_connections_lst); + c_list_init(&self->devcon_con_lst_head); c_list_init(&priv->seen_bssids_lst_head); c_list_init(&priv->call_ids_lst_head); c_list_init(&priv->auth_lst_head); @@ -2755,7 +2753,7 @@ nm_settings_connection_init(NMSettingsConnection *self) priv->agent_mgr = g_object_ref(nm_agent_manager_get()); priv->settings = g_object_ref(nm_settings_get()); - priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET; + priv->version_id = 1; } NMSettingsConnection * @@ -2776,6 +2774,7 @@ dispose(GObject *object) nm_assert(!priv->default_wired_device); nm_assert(c_list_is_empty(&self->_connections_lst)); + nm_assert(c_list_is_empty(&self->devcon_con_lst_head)); nm_assert(c_list_is_empty(&priv->auth_lst_head)); /* Cancel in-progress secrets requests */ @@ -2868,7 +2867,10 @@ static const NMDBusInterfaceInfoExtended interface_info_settings_connection = { NM_SETTINGS_CONNECTION_FLAGS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Filename", "s", - NM_SETTINGS_CONNECTION_FILENAME), ), ), + NM_SETTINGS_CONNECTION_FILENAME), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VersionId", + "t", + NM_SETTINGS_CONNECTION_VERSION_ID), ), ), }; static void @@ -2886,6 +2888,15 @@ nm_settings_connection_class_init(NMSettingsConnectionClass *klass) object_class->dispose = dispose; object_class->get_property = get_property; + obj_properties[PROP_VERSION_ID] = + g_param_spec_uint64(NM_SETTINGS_CONNECTION_VERSION_ID, + "", + "", + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_SETTINGS_CONNECTION_UNSAVED, "", "", diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h index fce7497c..835a978e 100644 --- a/src/core/settings/nm-settings-connection.h +++ b/src/core/settings/nm-settings-connection.h @@ -7,8 +7,11 @@ #ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__ #define __NETWORKMANAGER_SETTINGS_CONNECTION_H__ +#include "libnm-core-intern/nm-meta-setting-base.h" + #include "nm-dbus-object.h" #include "nm-connection.h" +#include "NetworkManagerUtils.h" #include "nm-settings-storage.h" @@ -138,9 +141,10 @@ typedef enum { #define NM_SETTINGS_CONNECTION_FLAGS_CHANGED "flags-changed" /* Properties */ -#define NM_SETTINGS_CONNECTION_UNSAVED "unsaved" -#define NM_SETTINGS_CONNECTION_FLAGS "flags" -#define NM_SETTINGS_CONNECTION_FILENAME "filename" +#define NM_SETTINGS_CONNECTION_UNSAVED "unsaved" +#define NM_SETTINGS_CONNECTION_VERSION_ID "version-id" +#define NM_SETTINGS_CONNECTION_FLAGS "flags" +#define NM_SETTINGS_CONNECTION_FILENAME "filename" /** * NMSettingsConnectionIntFlags: @@ -188,19 +192,6 @@ typedef enum _NMSettingsConnectionIntFlags { _NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1, } NMSettingsConnectionIntFlags; -typedef enum { - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE = 0, - - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0), - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED = (1LL << 1), - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS = (1LL << 2), - - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL = - (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST - | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED - | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS), -} NMSettingsAutoconnectBlockedReason; - typedef struct _NMSettingsConnectionCallId NMSettingsConnectionCallId; typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass; @@ -210,6 +201,7 @@ struct _NMSettingsConnectionPrivate; struct _NMSettingsConnection { NMDBusObject parent; CList _connections_lst; + CList devcon_con_lst_head; struct _NMSettingsConnectionPrivate *_priv; }; @@ -217,7 +209,13 @@ GType nm_settings_connection_get_type(void); NMSettingsConnection *nm_settings_connection_new(void); +NMSettings *nm_settings_connection_get_settings(NMSettingsConnection *self); + +NMManager *nm_settings_connection_get_manager(NMSettingsConnection *self); + NMConnection *nm_settings_connection_get_connection(NMSettingsConnection *self); +gpointer nm_settings_connection_get_setting(NMSettingsConnection *self, + NMMetaSettingType meta_type); void _nm_settings_connection_set_connection(NMSettingsConnection *self, NMConnection *new_connection, @@ -234,6 +232,9 @@ const char *nm_settings_connection_get_filename(NMSettingsConnection *self); guint64 nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self); +guint64 nm_settings_connection_get_version_id(NMSettingsConnection *self); +void nm_settings_connection_bump_version_id(NMSettingsConnection *self); + gboolean nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self, NMConnection *applied_connection, @@ -347,31 +348,15 @@ void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const cha guint nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self); -int nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self); -void nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries); -void nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self); - -gint32 nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self); +gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self); NMSettingsAutoconnectBlockedReason - nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self); -gboolean nm_settings_connection_autoconnect_blocked_reason_set_full( - NMSettingsConnection *self, - NMSettingsAutoconnectBlockedReason mask, - NMSettingsAutoconnectBlockedReason value); +nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self); -static inline gboolean +gboolean nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection *self, - NMSettingsAutoconnectBlockedReason mask, - gboolean set) -{ - return nm_settings_connection_autoconnect_blocked_reason_set_full( - self, - mask, - set ? mask : NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE); -} - -gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self); + NMSettingsAutoconnectBlockedReason reason, + gboolean set); const char *nm_settings_connection_get_id(NMSettingsConnection *connection); const char *nm_settings_connection_get_uuid(NMSettingsConnection *connection); diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c index 9995b490..8796de36 100644 --- a/src/core/settings/nm-settings.c +++ b/src/core/settings/nm-settings.c @@ -222,7 +222,7 @@ _sett_conn_entry_get_conn(SettConnEntry *sett_conn_entry) * _sett_conn_entry_storage_find_conflicting_storage: * @sett_conn_entry: the list of settings-storages for the given UUID. * @target_plugin: the settings plugin to check - * @storage_check_including: (allow-none): optionally compare against this storage. + * @storage_check_including: (nullable): optionally compare against this storage. * @plugins: the list of plugins sorted in descending priority. This determines * the priority and whether a storage conflicts. * @@ -451,6 +451,16 @@ static void _startup_complete_check(NMSettings *self, gint64 now_msec); /*****************************************************************************/ +NMManager * +nm_settings_get_manager(NMSettings *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); + + return NM_SETTINGS_GET_PRIVATE(self)->manager; +} + +/*****************************************************************************/ + static void _emit_connection_added(NMSettings *self, NMSettingsConnection *sett_conn) { @@ -525,7 +535,7 @@ _startup_complete_check_is_ready(NMSettings *self, /* Check that device is compatible with the device. We are also happy * with a device compatible but for which the connection is disallowed * by NM configuration. */ - if (!nm_device_check_connection_compatible(device, conn, &error) + if (!nm_device_check_connection_compatible(device, conn, TRUE, &error) && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED)) @@ -1080,11 +1090,13 @@ _connection_changed_update(NMSettings *self, is_new = c_list_is_empty(&sett_conn->_connections_lst); - _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")", + _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT "), " + "new version-id %" G_GUINT64_FORMAT, nm_settings_storage_get_uuid(storage), is_new ? "adding" : "updating", nm_connection_get_id(connection), - NM_SETTINGS_STORAGE_PRINT_ARG(storage)); + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + (nm_settings_connection_get_version_id(sett_conn) + 1u)); _nm_settings_connection_set_storage(sett_conn, storage); @@ -1109,7 +1121,7 @@ _connection_changed_update(NMSettings *self, if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) { nm_settings_connection_autoconnect_blocked_reason_set( sett_conn, - NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, TRUE); } @@ -1156,6 +1168,8 @@ _connection_changed_update(NMSettings *self, path); } + nm_settings_connection_bump_version_id(sett_conn); + if (is_new) { nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), &interface_info_settings, @@ -1237,6 +1251,8 @@ _connection_changed_delete(NMSettings *self, | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, FALSE); + nm_manager_notify_delete_settings_connections(priv->manager, sett_conn); + _emit_connection_removed(self, sett_conn); _nm_settings_connection_cleanup_after_remove(sett_conn); @@ -1453,10 +1469,16 @@ static void _plugin_connections_reload(NMSettings *self) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); - GSList *iter; - - for (iter = priv->plugins; iter; iter = iter->next) { - nm_settings_plugin_reload_connections(iter->data, _plugin_connections_reload_cb, self); + GSList *iter_plugin; + GHashTableIter iter_entry; + SettConnEntry *entry; + gboolean warned = FALSE; + gboolean migrate; + + for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next) { + nm_settings_plugin_reload_connections(iter_plugin->data, + _plugin_connections_reload_cb, + self); } _connection_changed_process_all_dirty( @@ -1469,8 +1491,53 @@ _plugin_connections_reload(NMSettings *self) | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET); - for (iter = priv->plugins; iter; iter = iter->next) - nm_settings_plugin_load_connections_done(iter->data); + for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next) + nm_settings_plugin_load_connections_done(iter_plugin->data); + + migrate = nm_config_data_get_value_boolean(nm_config_get_data(priv->config), + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH, + NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH_BOOL); + + g_hash_table_iter_init(&iter_entry, priv->sce_idx); + while (g_hash_table_iter_next(&iter_entry, (gpointer *) &entry, NULL)) { + const char *plugin; + + plugin = nm_settings_plugin_get_plugin_name(nm_settings_storage_get_plugin(entry->storage)); + + if (nm_streq0(plugin, "ifcfg-rh")) { + if (!warned) { + if (migrate) { + nm_log_warn( + LOGD_SETTINGS, + "Warning: connections were found in ifcfg-rh format and the " + "\"main.migrate-ifcfg-rh\" option is enabled. Those connections will be " + "migrated to keyfile. To convert them back, disable the option and then " + "run \"nmcli connection migrate --plugin ifcfg-rh $UUID\""); + } else { + nm_log_info( + LOGD_SETTINGS, + "Warning: the ifcfg-rh plugin is deprecated, please migrate connections " + "to the keyfile format using \"nmcli connection migrate\""); + } + warned = TRUE; + } + if (migrate) { + _LOGW("migrating connection %s ('%s') from ifcfg-rh to keyfile", + entry->uuid, + nm_settings_connection_get_id(entry->sett_conn)); + nm_settings_connection_update(entry->sett_conn, + "keyfile", + NULL, + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, + "migrate-ifcfg-rh", + NULL); + } + } + } } /*****************************************************************************/ @@ -1728,7 +1795,8 @@ _set_nmmeta_tombstone(NMSettings *self, * @persist_mode: the persist-mode for this profile. * @add_reason: the add-reason flags. * @sett_flags: the settings flags to set. - * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success. + * @out_sett_conn: (out) (optional) (nullable) (transfer none): the added + * settings connection on success. * @error: on return, a location to store any errors that may occur * * Creates a new #NMSettingsConnection for the given source @connection. @@ -2000,9 +2068,10 @@ nm_settings_update_connection(NMSettings *self, gs_unref_object NMConnection *new_connection_cloned = NULL; gs_unref_object NMConnection *new_connection = NULL; NMConnection *new_connection_real; - gs_unref_object NMSettingsStorage *cur_storage = NULL; - gs_unref_object NMSettingsStorage *new_storage = NULL; - NMSettingsStorage *drop_storage = NULL; + gs_unref_object NMSettingsStorage *cur_storage = NULL; + gs_unref_object NMSettingsStorage *new_storage = NULL; + NMSettingsStorage *drop_storage = NULL; + NMSettingsStorage *prev_update_storage = NULL; SettConnEntry *sett_conn_entry; gboolean cur_in_memory; gboolean new_in_memory; @@ -2246,16 +2315,17 @@ nm_settings_update_connection(NMSettings *self, drop_storage, &local); } else { - success = _update_connection_to_plugin(self, - update_storage, - connection, - new_flags, - update_reason, - new_shadowed_storage_filename, - new_shadowed_owned, - &new_storage, - &new_connection, - &local); + success = _update_connection_to_plugin( + self, + update_storage, + connection, + new_flags, + NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME), + new_shadowed_storage_filename, + new_shadowed_owned, + &new_storage, + &new_connection, + &local); } if (!success) { gboolean ignore_failure; @@ -2306,6 +2376,9 @@ nm_settings_update_connection(NMSettings *self, nm_assert_not_reached(); new_connection_real = new_connection; } + + if (update_storage && new_storage != update_storage) + prev_update_storage = update_storage; } } @@ -2314,6 +2387,12 @@ nm_settings_update_connection(NMSettings *self, _connection_changed_track(self, new_storage, new_connection_real, TRUE); + if (prev_update_storage) { + /* The storage was swapped by the update call. The old one needs + * to be dropped, which we do by setting the connection to NULL. */ + _connection_changed_track(self, prev_update_storage, NULL, FALSE); + } + if (drop_storage && drop_storage != new_storage) { gs_free_error GError *local = NULL; @@ -3102,7 +3181,7 @@ error: /** * nm_settings_get_connections: * @self: the #NMSettings - * @out_len: (out) (allow-none): returns the number of returned + * @out_len: (out) (optional): returns the number of returned * connections. * * Returns: (transfer none): a list of NMSettingsConnections. The list is @@ -3205,10 +3284,10 @@ nm_settings_get_connections_sorted_by_autoconnect_priority(NMSettings *self, gui /** * nm_settings_get_connections_clone: * @self: the #NMSetting - * @out_len: (allow-none): optional output argument + * @out_len: (optional): optional output argument * @func: caller-supplied function for filtering connections * @func_data: caller-supplied data passed to @func - * @sort_compare_func: (allow-none): optional function pointer for + * @sort_compare_func: (nullable): optional function pointer for * sorting the returned list. * @sort_data: user data for @sort_compare_func. * @@ -3629,7 +3708,7 @@ have_connection_for_device(NMSettings *self, NMDevice *device) c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) { NMConnection *connection = nm_settings_connection_get_connection(sett_conn); - if (!nm_device_check_connection_compatible(device, connection, NULL)) + if (!nm_device_check_connection_compatible(device, connection, TRUE, NULL)) continue; if (nm_settings_connection_default_wired_get_device(sett_conn)) diff --git a/src/core/settings/nm-settings.h b/src/core/settings/nm-settings.h index aba3c565..020623d0 100644 --- a/src/core/settings/nm-settings.h +++ b/src/core/settings/nm-settings.h @@ -58,6 +58,8 @@ NMSettings *nm_settings_get(void); NMSettings *nm_settings_new(NMManager *manager); +NMManager *nm_settings_get_manager(NMSettings *self); + gboolean nm_settings_start(NMSettings *self, GError **error); typedef void (*NMSettingsAddCallback)(NMSettings *settings, diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c index eb0d733d..0a385247 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c @@ -52,7 +52,6 @@ typedef struct { GHashTable *unmanaged_specs; GHashTable *unrecognized_specs; - } NMSIfcfgRHPluginPrivate; struct _NMSIfcfgRHPlugin { @@ -177,6 +176,7 @@ nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent) static NMSIfcfgRHStorage * _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error) { + NMSIfcfgRHStorage *ret = NULL; gs_unref_object NMConnection *connection = NULL; gs_free_error GError *load_error = NULL; gs_free char *unhandled_spec = NULL; @@ -224,16 +224,16 @@ _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error) nm_assert_not_reached(); return NULL; } - return nms_ifcfg_rh_storage_new_unhandled(self, + + ret = nms_ifcfg_rh_storage_new_unhandled(self, filename, unmanaged_spec, unrecognized_spec); + } else { + ret = nms_ifcfg_rh_storage_new_connection(self, filename, - unmanaged_spec, - unrecognized_spec); + g_steal_pointer(&connection), + &st.st_mtim); } - return nms_ifcfg_rh_storage_new_connection(self, - filename, - g_steal_pointer(&connection), - &st.st_mtim); + return ret; } static void diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index 6cfb5705..84a9479d 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -917,7 +917,7 @@ enum { * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created * route object that is completed with the settings from options. Otherwise, * it shall point to %NULL and a new route is created and returned. - * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance. + * @out_route: (out) (transfer full) (optional): the parsed %NMIPRoute instance. * In case a @options_route is passed in, it returns the input route that was modified * in-place. But the caller must unref the returned route in either case. * @error: the failure description. @@ -2481,6 +2481,11 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL); nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCPV6_PD_HINT", &value); + if (v) + g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_DHCP_PD_HINT, v, NULL); + + nm_clear_g_free(&value); v = svGetValueStr(ifcfg, "DHCPV6_HOSTNAME", &value); /* Use DHCP_HOSTNAME as fallback if it is in FQDN format and ipv6.method is * auto or dhcp: this is required to support old ifcfg files @@ -2591,7 +2596,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea &local)) { PARSE_WARNING("%s", local->message); g_clear_error(&local); - } else if (errno == ENOENT) { + } else if (errno == ENOKEY) { /* The key is not specified. If "v" (IPV6_TOKEN) is set, * we default to EUI64. Otherwise, the connection would not verify. */ if (v) @@ -2683,16 +2688,25 @@ make_hostname_setting(shvarFile *ifcfg) NMTernary from_dns_lookup; NMTernary only_from_default; int priority; + gboolean has_setting = FALSE; priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0); + if (!has_setting && errno != ENOKEY) + has_setting = TRUE; + + from_dhcp = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP"); + if (!has_setting && errno != ENOKEY) + has_setting = TRUE; + + from_dns_lookup = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP"); + if (!has_setting && errno != ENOKEY) + has_setting = TRUE; - from_dhcp = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP"); - from_dns_lookup = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP"); only_from_default = svGetValueTernary(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT"); + if (!has_setting && errno != ENOKEY) + has_setting = TRUE; - /* Create the setting when at least one key is not default*/ - if (priority == 0 && from_dhcp == NM_TERNARY_DEFAULT && from_dns_lookup == NM_TERNARY_DEFAULT - && only_from_default == NM_TERNARY_DEFAULT) + if (!has_setting) return NULL; setting = nm_setting_hostname_new(); diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c index 207b8700..50e352d3 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c @@ -881,6 +881,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = { _KEY_TYPE("DHCPV6_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), _KEY_TYPE("DHCPV6_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), _KEY_TYPE("DHCPV6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_PD_HINT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), _KEY_TYPE("DHCPV6_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), _KEY_TYPE("DHCP_CLIENT_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), _KEY_TYPE("DHCP_FQDN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h index 51b118e3..eb9e418a 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h @@ -33,7 +33,7 @@ typedef struct { NMSIfcfgKeyTypeFlags key_flags; } NMSIfcfgKeyTypeInfo; -extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[263]; +extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[264]; const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx); diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index 97637063..08deaf5a 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -62,6 +62,24 @@ /*****************************************************************************/ static void +set_error_unsupported(GError **error, + NMConnection *connection, + const char *name, + gboolean is_setting) +{ + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_NOT_SUPPORTED_BY_PLUGIN, + "The ifcfg-rh plugin doesn't support %s '%s'. If you are modifying an existing " + "connection profile saved in ifcfg-rh format, please migrate the connection to " + "keyfile using 'nmcli connection migrate %s' or via the Update2() D-Bus API " + "and try again.", + is_setting ? "setting" : "property", + name, + nm_connection_get_uuid(connection)); +}; + +static void save_secret_flags(shvarFile *ifcfg, const char *key, NMSettingSecretFlags flags) { GString *str; @@ -3100,6 +3118,9 @@ write_ip6_setting(NMConnection *connection, shvarFile *ifcfg, GString **out_rout "DHCPV6_DUID", nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6))); svSetValueStr(ifcfg, "DHCPV6_IAID", nm_setting_ip_config_get_dhcp_iaid(s_ip6)); + svSetValueStr(ifcfg, + "DHCPV6_PD_HINT", + nm_setting_ip6_config_get_dhcp_pd_hint(NM_SETTING_IP6_CONFIG(s_ip6))); hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip6); svSetValueStr(ifcfg, "DHCPV6_HOSTNAME", hostname); @@ -3485,6 +3506,11 @@ do_write_construct(NMConnection *connection, write_sriov_setting(connection, ifcfg); write_tc_setting(connection, ifcfg); + if (_nm_connection_get_setting(connection, NM_TYPE_SETTING_LINK)) { + set_error_unsupported(error, connection, "link", TRUE); + return FALSE; + } + route_path_is_svformat = utils_has_route_file_new_syntax(route_path); has_complex_routes_v4 = utils_has_complex_routes(ifcfg_name, AF_INET); diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c index fe8187c3..1ca2ea60 100644 --- a/src/core/settings/plugins/ifcfg-rh/shvar.c +++ b/src/core/settings/plugins/ifcfg-rh/shvar.c @@ -97,23 +97,32 @@ static void _line_link_parse(shvarFile *s, const char *value, gsize len); * in case no valid value is found, the fallback value. Valid values * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0". * + * Always sets errno. Either to zero on success, to ENOKEY for NULL + * or to EINVAL otherwise. + * * Returns: the parsed boolean value or @fallback. */ int svParseBoolean(const char *value, int fallback) { - if (!value) + if (!value) { + errno = ENOKEY; return fallback; + } if (!g_ascii_strcasecmp("yes", value) || !g_ascii_strcasecmp("true", value) || !g_ascii_strcasecmp("t", value) || !g_ascii_strcasecmp("y", value) - || !g_ascii_strcasecmp("1", value)) + || !g_ascii_strcasecmp("1", value)) { + errno = 0; return TRUE; - else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value) - || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value) - || !g_ascii_strcasecmp("0", value)) + } else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value) + || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value) + || !g_ascii_strcasecmp("0", value)) { + errno = 0; return FALSE; + } + errno = EINVAL; return fallback; } @@ -1253,6 +1262,7 @@ svGetValueStr_cp(shvarFile *s, const char *key) * @fallback: the fallback value in any error case * * Reads a value @key and converts it to a boolean using svParseBoolean(). + * This always sets errno, see svParseBoolean(). * * Returns: the parsed boolean value or @fallback. */ @@ -1271,6 +1281,7 @@ svGetValueBoolean(shvarFile *s, const char *key, int fallback) * @key: the name of the key to read * * Reads a value @key and converts it to a NMTernary value. + * This always sets errno, see svParseBoolean(). * * Returns: the parsed NMTernary */ @@ -1328,7 +1339,7 @@ svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GErro if (!svalue) { /* don't touch out_value. The caller is supposed * to initialize it with the default value. */ - errno = ENOENT; + errno = ENOKEY; return TRUE; } diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c index 1d7de8d2..1679cab6 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c @@ -891,6 +891,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *self, gboolean reread_same; const char *uuid; char strbuf[100]; + NMTernary force_rename2; _nm_assert_storage(self, storage, TRUE); nm_assert(NM_IS_CONNECTION(connection)); @@ -910,6 +911,20 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *self, previous_filename = nms_keyfile_storage_get_filename(storage); uuid = nms_keyfile_storage_get_uuid(storage); + if (force_rename) + force_rename2 = NM_TERNARY_TRUE; + else { + /* If the caller does not force a rename, we honor [keyfile].rename + * setting, and (if enabled) we rename by following the preferred name + * as necessary. That's indicated with NM_TERNARY_DEFAULT. */ + force_rename2 = nm_config_data_get_value_boolean(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME, + FALSE) + ? NM_TERNARY_DEFAULT + : NM_TERNARY_FALSE; + } + if (!nms_keyfile_writer_connection( connection, is_nm_generated, @@ -922,7 +937,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *self, _get_plugin_dir(priv), previous_filename, FALSE, - FALSE, + force_rename2, nm_sett_util_allow_filename_cb, NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename), &full_filename, @@ -938,7 +953,8 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *self, return FALSE; } - nm_assert(full_filename && nm_streq(full_filename, previous_filename)); + nm_assert(full_filename); + nm_assert(force_rename2 != NM_TERNARY_FALSE || nm_streq(full_filename, previous_filename)); if (!reread || reread_same) nm_g_object_ref_set(&reread, connection); @@ -957,11 +973,33 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *self, "\")", "")); - storage->u.conn_data.is_nm_generated = is_nm_generated; - storage->u.conn_data.is_volatile = is_volatile; - storage->u.conn_data.is_external = is_external; - storage->u.conn_data.stat_mtime = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime); - storage->u.conn_data.shadowed_owned = shadowed_owned; + nm_sett_util_stat_mtime(full_filename, FALSE, &mtime); + + if (nm_streq(full_filename, previous_filename)) { + storage->u.conn_data.is_nm_generated = is_nm_generated; + storage->u.conn_data.is_volatile = is_volatile; + storage->u.conn_data.is_external = is_external; + storage->u.conn_data.stat_mtime = mtime; + storage->u.conn_data.shadowed_owned = shadowed_owned; + } else { + NMSKeyfileStorage *storage_new; + + /* The filename changed. We cannot modify the filename of an NMSettingsStorage. + * We need to create a new one. */ + storage_new = + nms_keyfile_storage_new_connection(NMS_KEYFILE_PLUGIN(storage->parent._plugin), + g_object_ref(reread), + full_filename, + storage->storage_type, + is_nm_generated, + is_volatile, + is_external, + storage->u.conn_data.shadowed_storage, + shadowed_owned, + &mtime); + nm_sett_util_storages_add_take(&priv->storages, storage_new); + storage = storage_new; + } *out_storage = g_object_ref(NM_SETTINGS_STORAGE(storage)); *out_connection = g_steal_pointer(&reread); @@ -1066,12 +1104,12 @@ delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError * @shadowed_storage: a tombstone can also shadow an existing storage. * In combination with @set and @in_memory, this is allowed to store * the shadowed storage filename. - * @out_storage: (transfer full) (allow-none): the storage element that changes, or - * NULL if nothing changed. Note that the file on disk is already as - * we want to write it, then this still counts as a change. No change only + * @out_storage: (transfer full) (optional) (nullable): the storage element that + * changes, or %NULL if nothing changed. Note that the file on disk is already + * as we want to write it, then this still counts as a change. No change only * means if we try to delete a storage (@set %FALSE) that did not * exist previously. - * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure. + * @out_hard_failure: (optional): on failure, indicate that this is a hard failure. * * The function writes or deletes nmmeta files to/from filesystem. In this case, * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones). diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c index ad6f277c..b1dd2e44 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c @@ -195,7 +195,7 @@ _internal_write_connection(NMConnection *connection, pid_t owner_grp, const char *existing_path, gboolean existing_path_read_only, - gboolean force_rename, + NMTernary force_rename, NMSKeyfileWriterAllowFilenameCb allow_filename_cb, gpointer allow_filename_user_data, char **out_path, @@ -212,6 +212,7 @@ _internal_write_connection(NMConnection *connection, gs_free_error GError *local_err = NULL; int errsv; gboolean rename; + gboolean rename_follow; int i_path; gs_unref_object NMConnection *reread = NULL; gboolean reread_same = FALSE; @@ -223,8 +224,12 @@ _internal_write_connection(NMConnection *connection, nm_assert(!shadowed_owned || shadowed_storage); - rename = force_rename || existing_path_read_only - || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir)); + rename = existing_path_read_only + || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir)) + || force_rename == NM_TERNARY_TRUE; + + /* Follow the connection.id upon change. */ + rename_follow = !rename && existing_path && force_rename == NM_TERNARY_DEFAULT; id = nm_connection_get_id(connection); nm_assert(id && *id); @@ -283,7 +288,7 @@ _internal_write_connection(NMConnection *connection, gboolean is_existing_path; if (i_path == -2) { - if (!existing_path || rename) + if (!existing_path || rename || rename_follow) continue; path_candidate = g_strdup(existing_path); } else if (i_path == -1) { @@ -427,7 +432,7 @@ nms_keyfile_writer_connection(NMConnection *connection, const char *profile_dir, const char *existing_path, gboolean existing_path_read_only, - gboolean force_rename, + NMTernary force_rename, NMSKeyfileWriterAllowFilenameCb allow_filename_cb, gpointer allow_filename_user_data, char **out_path, @@ -458,14 +463,14 @@ nms_keyfile_writer_connection(NMConnection *connection, } gboolean -nms_keyfile_writer_test_connection(NMConnection *connection, - const char *keyfile_dir, - uid_t owner_uid, - pid_t owner_grp, - char **out_path, - NMConnection **out_reread, - gboolean *out_reread_same, - GError **error) +nmtst_keyfile_writer_test_connection(NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char **out_path, + NMConnection **out_reread, + gboolean *out_reread_same, + GError **error) { return _internal_write_connection(connection, FALSE, diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h index 62aaa19d..850d5522 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h @@ -22,7 +22,7 @@ gboolean nms_keyfile_writer_connection(NMConnection *connectio const char *profile_dir, const char *existing_path, gboolean existing_path_read_only, - gboolean force_rename, + NMTernary force_rename, NMSKeyfileWriterAllowFilenameCb allow_filename_cb, gpointer allow_filename_user_data, char **out_path, @@ -30,13 +30,13 @@ gboolean nms_keyfile_writer_connection(NMConnection *connectio gboolean *out_reread_same, GError **error); -gboolean nms_keyfile_writer_test_connection(NMConnection *connection, - const char *keyfile_dir, - uid_t owner_uid, - pid_t owner_grp, - char **out_path, - NMConnection **out_reread, - gboolean *out_reread_same, - GError **error); +gboolean nmtst_keyfile_writer_test_connection(NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char **out_path, + NMConnection **out_reread, + gboolean *out_reread_same, + GError **error); #endif /* __NMS_KEYFILE_WRITER_H__ */ diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c index 83019bab..866b1ffd 100644 --- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c @@ -138,14 +138,14 @@ write_test_connection_reread(NMConnection *connection, connection_normalized = nmtst_connection_duplicate_and_normalize(connection); - success = nms_keyfile_writer_test_connection(connection_normalized, - TEST_SCRATCH_DIR, - owner_uid, - owner_grp, - testfile, - out_reread, - out_reread_same, - p_error); + success = nmtst_keyfile_writer_test_connection(connection_normalized, + TEST_SCRATCH_DIR, + owner_uid, + owner_grp, + testfile, + out_reread, + out_reread_same, + p_error); g_assert_no_error(error); g_assert(success); g_assert(*testfile && (*testfile)[0]); diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c index f6927500..3b805693 100644 --- a/src/core/supplicant/nm-supplicant-manager.c +++ b/src/core/supplicant/nm-supplicant-manager.c @@ -447,7 +447,7 @@ _create_iface_dbus_call_get_interface_cb(GObject *source, GAsyncResult *result, nm_assert(handle->name_owner == priv->name_owner); if (!res) { - char ifname[NMP_IFNAMSIZ]; + char ifname[NM_IFNAMSIZ]; if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX && nm_dbus_error_is(error, NM_WPAS_ERROR_UNKNOWN_IFACE) @@ -489,7 +489,7 @@ _create_iface_dbus_call_create_interface_cb(GObject *source, gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; const char *iface_path_str; - char ifname[NMP_IFNAMSIZ]; + char ifname[NM_IFNAMSIZ]; res = g_dbus_connection_call_finish(dbus_connection, result, &error); @@ -619,7 +619,7 @@ static void _create_iface_dbus_start(NMSupplicantManager *self, NMSupplMgrCreateIfaceHandle *handle) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); - char ifname[NMP_IFNAMSIZ]; + char ifname[NM_IFNAMSIZ]; nm_assert(priv->name_owner); nm_assert(!handle->cancellable); diff --git a/src/core/tests/test-core-with-expect.c b/src/core/tests/test-core-with-expect.c index b05144ac..01510126 100644 --- a/src/core/tests/test-core-with-expect.c +++ b/src/core/tests/test-core-with-expect.c @@ -333,9 +333,8 @@ do_test_nm_utils_kill_child(void) /* pid3s should not be a valid process, hence the call should fail. Note, that there * is a race here. */ - NMTST_EXPECT_NM_ERROR( - "kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid " - "(No child process*, 10) after sending no signal (0)"); + NMTST_EXPECT_NM_ERROR("kill child process 'test-s-3-2' (*): unexpected error while waitpid: No " + "child process* (10)"); test_nm_utils_kill_child_sync_do("test-s-3-2", pid3s, 0, 0, FALSE, NULL); NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): waiting up to 50 milliseconds for " @@ -396,9 +395,8 @@ do_test_nm_utils_kill_child(void) /* pid3a should not be a valid process, hence the call should fail. Note, that there * is a race here. */ - NMTST_EXPECT_NM_ERROR( - "kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid " - "(No child process*, 10) after sending no signal (0)"); + NMTST_EXPECT_NM_ERROR("kill child process 'test-a-3-2' (*): unexpected error while " + "waitpid: No child process* (10)"); NMTST_EXPECT_NM_DEBUG( "kill child process 'test-a-3-2' (*): invoke callback: killing child failed"); test_nm_utils_kill_child_async_do("test-a-3-2", pid3a, 0, 0, FALSE, NULL); diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c index 887803bf..8296d745 100644 --- a/src/core/tests/test-core.c +++ b/src/core/tests/test-core.c @@ -18,6 +18,7 @@ #include "dns/nm-dns-manager.h" #include "nm-connectivity.h" +#include "nm-firewall-utils.h" #include "nm-test-utils-core.h" @@ -1243,13 +1244,9 @@ _test_match_spec_device(const GSList *specs, const char *match_str) { if (match_str && g_str_has_prefix(match_str, MATCH_S390)) return nm_match_spec_device(specs, - NULL, - NULL, - NULL, - NULL, - NULL, - &match_str[NM_STRLEN(MATCH_S390)], - NULL); + &((const NMMatchSpecDeviceData){ + .s390_subchannels = &match_str[NM_STRLEN(MATCH_S390)], + })); if (match_str && g_str_has_prefix(match_str, MATCH_DRIVER)) { gs_free char *s = g_strdup(&match_str[NM_STRLEN(MATCH_DRIVER)]); char *t; @@ -1259,9 +1256,16 @@ _test_match_spec_device(const GSList *specs, const char *match_str) t[0] = '\0'; t++; } - return nm_match_spec_device(specs, NULL, NULL, s, t, NULL, NULL, NULL); + return nm_match_spec_device(specs, + &((const NMMatchSpecDeviceData){ + .driver = s, + .driver_version = t, + })); } - return nm_match_spec_device(specs, match_str, NULL, NULL, NULL, NULL, NULL, NULL); + return nm_match_spec_device(specs, + &((const NMMatchSpecDeviceData){ + .interface_name = match_str, + })); } static void @@ -2109,7 +2113,7 @@ do_test_stable_id_parse(const char *stable_id, g_assert(!expected_generated); if (expected_stable_type == NM_UTILS_STABLE_TYPE_UUID) - g_assert(!stable_id); + g_assert(NM_IN_STRSET(stable_id, NULL, "default${CONNECTION}")); else g_assert(stable_id); @@ -2137,6 +2141,7 @@ test_stable_id_parse(void) #define _parse_random(stable_id) \ do_test_stable_id_parse("" stable_id "", NM_UTILS_STABLE_TYPE_RANDOM, NULL) do_test_stable_id_parse(NULL, NM_UTILS_STABLE_TYPE_UUID, NULL); + do_test_stable_id_parse("default${CONNECTION}", NM_UTILS_STABLE_TYPE_UUID, NULL); _parse_stable_id(""); _parse_stable_id("a"); _parse_stable_id("a$"); @@ -2151,6 +2156,7 @@ test_stable_id_parse(void) _parse_stable_id("a$${CONNECTION}"); _parse_stable_id("a$${CONNECTION}x"); _parse_generated("${CONNECTION}", "${CONNECTION}=11{_CONNECTION}"); + _parse_generated(" ${CONNECTION}", " ${CONNECTION}=11{_CONNECTION}"); _parse_generated("${${CONNECTION}", "${${CONNECTION}=11{_CONNECTION}"); _parse_generated("${CONNECTION}x", "${CONNECTION}=11{_CONNECTION}x"); _parse_generated("x${CONNECTION}", "x${CONNECTION}=11{_CONNECTION}"); @@ -2580,6 +2586,125 @@ test_connectivity_state_cmp(void) /*****************************************************************************/ +static void +test_nm_firewall_nft_stdio_mlag(void) +{ +#define _T(up, \ + bond_ifname, \ + bond_ifnames_down, \ + active_members, \ + previous_members, \ + with_counters, \ + expected) \ + G_STMT_START \ + { \ + gs_unref_bytes GBytes *_b = NULL; \ + \ + _b = nm_firewall_nft_stdio_mlag((up), \ + (bond_ifname), \ + (bond_ifnames_down), \ + (active_members), \ + (previous_members), \ + (with_counters)); \ + \ + g_assert(_b); \ + nmtst_assert_cmpmem(expected, \ + NM_STRLEN(expected), \ + g_bytes_get_data(_b, NULL), \ + g_bytes_get_size(_b)); \ + } \ + G_STMT_END + + _T(TRUE, + "bond0", + NM_MAKE_STRV("eth0"), + NM_MAKE_STRV("eth1"), + NM_MAKE_STRV("eth2"), + TRUE, + "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev " + "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth1 { type filter hook ingress device eth1 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth1\012add set netdev nm-mlag-bond0 " + "macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; }\012add set netdev " + "nm-mlag-bond0 macset-untagged { typeof ether saddr; flags dynamic,timeout; }\012add chain " + "netdev nm-mlag-bond0 tx-snoop-source-mac { type filter hook egress device bond0 priority " + "filter; }\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr . " + "vlan id timeout 5s @macset-tagged counter return\012add rule netdev nm-mlag-bond0 " + "tx-snoop-source-mac set update ether saddr timeout 5s @macset-untagged counter\012add " + "chain netdev nm-mlag-bond0 rx-drop-looped-packets { type filter hook ingress device bond0 " + "priority filter; }\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr . " + "vlan id @macset-tagged counter drop\012add rule netdev nm-mlag-bond0 " + "rx-drop-looped-packets ether type vlan counter return\012add rule netdev nm-mlag-bond0 " + "rx-drop-looped-packets ether saddr @macset-untagged counter drop\012"); + + _T(TRUE, + "bond0", + NM_MAKE_STRV("eth0"), + NM_MAKE_STRV("eth1"), + NM_MAKE_STRV("eth2"), + FALSE, + "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev " + "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth1 { type filter hook ingress device eth1 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth1\012add set netdev nm-mlag-bond0 " + "macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; }\012add set netdev " + "nm-mlag-bond0 macset-untagged { typeof ether saddr; flags dynamic,timeout; }\012add chain " + "netdev nm-mlag-bond0 tx-snoop-source-mac { type filter hook egress device bond0 priority " + "filter; }\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr . " + "vlan id timeout 5s @macset-tagged return\012add rule netdev nm-mlag-bond0 " + "tx-snoop-source-mac set update ether saddr timeout 5s @macset-untagged\012add chain netdev " + "nm-mlag-bond0 rx-drop-looped-packets { type filter hook ingress device bond0 priority " + "filter; }\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr . vlan id " + "@macset-tagged drop\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether type " + "vlan return\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr " + "@macset-untagged drop\012"); + + _T(TRUE, + "bond0", + NM_MAKE_STRV("eth0", "eth1"), + NM_MAKE_STRV("eth2", "eth3"), + NM_MAKE_STRV("eth4", "eth5"), + FALSE, + "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev " + "nm-mlag-eth1\012delete table netdev nm-mlag-eth1\012add table netdev " + "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth4 { type filter hook ingress device eth4 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth4\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth5 { type filter hook ingress device eth5 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth5\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete " + "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 " + "rx-drop-bc-mc-eth3 { type filter hook ingress device eth3 priority filter; }\012add rule " + "netdev nm-mlag-bond0 rx-drop-bc-mc-eth3 pkttype { broadcast, multicast } drop\012add set " + "netdev nm-mlag-bond0 macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; " + "}\012add set netdev nm-mlag-bond0 macset-untagged { typeof ether saddr; flags " + "dynamic,timeout; }\012add chain netdev nm-mlag-bond0 tx-snoop-source-mac { type filter " + "hook egress device bond0 priority filter; }\012add rule netdev nm-mlag-bond0 " + "tx-snoop-source-mac set update ether saddr . vlan id timeout 5s @macset-tagged " + "return\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr timeout " + "5s @macset-untagged\012add chain netdev nm-mlag-bond0 rx-drop-looped-packets { type filter " + "hook ingress device bond0 priority filter; }\012add rule netdev nm-mlag-bond0 " + "rx-drop-looped-packets ether saddr . vlan id @macset-tagged drop\012add rule netdev " + "nm-mlag-bond0 rx-drop-looped-packets ether type vlan return\012add rule netdev " + "nm-mlag-bond0 rx-drop-looped-packets ether saddr @macset-untagged drop\012"); + + _T(FALSE, + "bond0", + NM_MAKE_STRV("eth0", "eth1"), + NM_MAKE_STRV("eth2", "eth3"), + NM_MAKE_STRV("eth4", "eth5"), + FALSE, + "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev " + "nm-mlag-eth1\012delete table netdev nm-mlag-eth1\012add table netdev " + "nm-mlag-bond0\012delete table netdev nm-mlag-bond0\012"); +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -2654,5 +2779,7 @@ main(int argc, char **argv) g_test_add_func("/core/general/test_kernel_cmdline_match_check", test_kernel_cmdline_match_check); + g_test_add_func("/core/test_nm_firewall_nft_stdio_mlag", test_nm_firewall_nft_stdio_mlag); + return g_test_run(); } |