/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2004 - 2013 Red Hat, Inc. * Copyright (C) 2007 - 2008 Novell, Inc. */ #include "nm-default.h" #include "nm-policy.h" #include #include #include #include #include "NetworkManagerUtils.h" #include "nm-act-request.h" #include "devices/nm-device.h" #include "nm-setting-ip4-config.h" #include "nm-setting-connection.h" #include "platform/nm-platform.h" #include "dns/nm-dns-manager.h" #include "vpn/nm-vpn-manager.h" #include "nm-auth-utils.h" #include "nm-firewall-manager.h" #include "nm-dispatcher.h" #include "nm-utils.h" #include "nm-core-internal.h" #include "nm-manager.h" #include "settings/nm-settings.h" #include "settings/nm-settings-connection.h" #include "nm-dhcp4-config.h" #include "nm-dhcp6-config.h" #include "nm-config.h" #include "nm-netns.h" #include "nm-hostname-manager.h" /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMPolicy, PROP_MANAGER, PROP_SETTINGS, PROP_DEFAULT_IP4_DEVICE, PROP_DEFAULT_IP6_DEVICE, PROP_ACTIVATING_IP4_DEVICE, PROP_ACTIVATING_IP6_DEVICE, ); typedef struct { NMManager *manager; NMNetns *netns; NMFirewallManager *firewall_manager; GSList *pending_activation_checks; GHashTable *devices; GHashTable *pending_active_connections; GSList *pending_secondaries; NMSettings *settings; NMHostnameManager *hostname_manager; NMDevice *default_device4, *activating_device4; NMDevice *default_device6, *activating_device6; struct { GInetAddress *addr; GResolver *resolver; GCancellable *cancellable; } lookup; 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 */ char *last_hostname; /* last hostname NM set (to detect if someone else changed it in the meanwhile) */ gboolean changing_hostname; /* hostname set operation still in progress */ gboolean dhcp_hostname; /* current hostname was set from dhcp */ GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */ } NMPolicyPrivate; struct _NMPolicy { GObject parent; NMPolicyPrivate _priv; }; struct _NMPolicyClass { GObjectClass parent; }; G_DEFINE_TYPE (NMPolicy, nm_policy, G_TYPE_OBJECT) #define NM_POLICY_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMPolicy, NM_IS_POLICY) static NMPolicy * _PRIV_TO_SELF (NMPolicyPrivate *priv) { NMPolicy *self; nm_assert (priv); self = (NMPolicy *) (((char *) priv) - G_STRUCT_OFFSET (NMPolicy, _priv)); nm_assert (NM_IS_POLICY (self)); return self; } /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "policy" #define _NMLOG(level, domain, ...) \ G_STMT_START { \ nm_log ((level), (domain), NULL, NULL, \ "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ _NMLOG_PREFIX_NAME": " \ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } G_STMT_END /*****************************************************************************/ static void schedule_activate_all (NMPolicy *self); static void schedule_activate_check (NMPolicy *self, NMDevice *device); /*****************************************************************************/ typedef struct { NMPlatformIP6Address prefix; NMDevice *device; /* The requesting ("uplink") device */ guint64 next_subnet; /* Cache of the next subnet number to be * assigned from this prefix */ GHashTable *subnets; /* ifindex -> NMPlatformIP6Address */ } IP6PrefixDelegation; static void _clear_ip6_subnet (gpointer key, gpointer value, gpointer user_data) { NMPlatformIP6Address *subnet = value; NMDevice *device = nm_manager_get_device_by_ifindex (nm_manager_get (), GPOINTER_TO_INT (key)); if (device) { /* We can not remove a subnet we already started announcing. * Just un-prefer it. */ subnet->preferred = 0; nm_device_use_ip6_subnet (device, subnet); } g_slice_free (NMPlatformIP6Address, subnet); } static void clear_ip6_prefix_delegation (gpointer data) { IP6PrefixDelegation *delegation = data; _LOGD (LOGD_IP6, "ipv6-pd: undelegating prefix %s/%d", nm_utils_inet6_ntop (&delegation->prefix.address, NULL), delegation->prefix.plen); g_hash_table_foreach (delegation->subnets, _clear_ip6_subnet, NULL); g_hash_table_destroy (delegation->subnets); } static void expire_ip6_delegations (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); guint32 now = nm_utils_get_monotonic_timestamp_s (); IP6PrefixDelegation *delegation = NULL; guint i; for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i); if (delegation->prefix.timestamp + delegation->prefix.lifetime < now) g_array_remove_index_fast (priv->ip6_prefix_delegations, i); } } /* * Try to obtain a new subnet for a particular active connection from given * delegated prefix, possibly reusing the existing subnet. * Return value of FALSE indicates no more subnets are available from * this prefix (and other prefix should be used -- and requested if necessary). */ static gboolean ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device) { NMPlatformIP6Address *subnet; int ifindex = nm_device_get_ifindex (device); subnet = g_hash_table_lookup (delegation->subnets, GINT_TO_POINTER (ifindex)); if (!subnet) { /* Check for out-of-prefixes condition. */ if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) { _LOGD (LOGD_IP6, "ipv6-pd: no more prefixes in %s/%d", nm_utils_inet6_ntop (&delegation->prefix.address, NULL), delegation->prefix.plen); return FALSE; } /* Allocate a new subnet. */ subnet = g_slice_new0 (NMPlatformIP6Address); g_hash_table_insert (delegation->subnets, GINT_TO_POINTER (ifindex), subnet); subnet->plen = 64; subnet->address.s6_addr32[0] = delegation->prefix.address.s6_addr32[0] | htonl (delegation->next_subnet >> 32); subnet->address.s6_addr32[1] = delegation->prefix.address.s6_addr32[1] | htonl (delegation->next_subnet); /* Out subnet pool management is pretty unsophisticated. We only add * the subnets and index them by ifindex. That keeps the implementation * simple and the dead entries make it easy to reuse the same subnet on * subsequent activations. On the other hand they may waste the subnet * space. */ delegation->next_subnet++; } subnet->timestamp = delegation->prefix.timestamp; subnet->lifetime = delegation->prefix.lifetime; subnet->preferred = delegation->prefix.preferred; _LOGD (LOGD_IP6, "ipv6-pd: %s allocated from a /%d prefix on %s", nm_utils_inet6_ntop (&subnet->address, NULL), delegation->prefix.plen, nm_device_get_iface (device)); nm_device_use_ip6_subnet (device, subnet); return TRUE; } /* * Try to obtain a subnet from each prefix delegated to given requesting * ("uplink") device and assign it to the downlink device. * Requests a new prefix if no subnet could be found. */ static void ip6_subnet_from_device (NMPolicy *self, NMDevice *from_device, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); IP6PrefixDelegation *delegation = NULL; gboolean got_subnet = FALSE; guint have_prefixes = 0; guint i; expire_ip6_delegations (self); for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i); if (delegation->device != from_device) continue; if (ip6_subnet_from_delegation (delegation, device)) got_subnet = TRUE; have_prefixes++; } if (!got_subnet) { _LOGI (LOGD_IP6, "ipv6-pd: none of %u prefixes of %s can be shared on %s", have_prefixes, nm_device_get_iface (from_device), nm_device_get_iface (device)); nm_device_request_ip6_prefixes (from_device, have_prefixes + 1); } } static void ip6_remove_device_prefix_delegations (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); IP6PrefixDelegation *delegation = NULL; guint i; for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i); if (delegation->device == device) g_array_remove_index_fast (priv->ip6_prefix_delegations, i); } } static void device_ip6_prefix_delegated (NMDevice *device, NMPlatformIP6Address *prefix, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); IP6PrefixDelegation *delegation = NULL; const GSList *connections, *iter; guint i; _LOGI (LOGD_IP6, "ipv6-pd: received a prefix %s/%d from %s", nm_utils_inet6_ntop (&prefix->address, NULL), prefix->plen, nm_device_get_iface (device)); expire_ip6_delegations (self); for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { /* Look for an already known prefix to update. */ delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i); if (IN6_ARE_ADDR_EQUAL (&delegation->prefix.address, &prefix->address)) break; } if (i == priv->ip6_prefix_delegations->len) { /* Allocate a delegation delegation for new prefix. */ g_array_set_size (priv->ip6_prefix_delegations, i + 1); delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i); delegation->subnets = g_hash_table_new (NULL, NULL); delegation->next_subnet = 0; } delegation->device = device; delegation->prefix = *prefix; /* The newly activated connections are added to the list beginning, * so traversing it from the beginning makes it likely for newly * activated connections that have no subnet assigned to be served * first. That is a simple yet fair policy, which is good. */ connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { NMDevice *to_device = nm_active_connection_get_device (iter->data); if (nm_device_needs_ip6_subnet (to_device)) ip6_subnet_from_delegation (delegation, to_device); } } static void device_ip6_subnet_needed (NMDevice *device, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); _LOGD (LOGD_IP6, "ipv6-pd: %s needs a subnet", nm_device_get_iface (device)); if (!priv->default_device6) { /* We request the prefixes when the default IPv6 device is set. */ _LOGI (LOGD_IP6, "ipv6-pd: no device to obtain a subnet to share on %s from", nm_device_get_iface (device)); return; } ip6_subnet_from_device (self, priv->default_device6, device); nm_device_copy_ip6_dns_config (device, priv->default_device6); } /*****************************************************************************/ static NMDevice * get_best_ip_device (NMPolicy *self, int addr_family, gboolean fully_activated) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *iter; NMDevice *best_device; NMDevice *prev_device; guint32 best_metric = G_MAXUINT32; gboolean best_is_fully_activated = FALSE; nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6)); /* we prefer the current device in case of identical metric. * Hence, try that one first.*/ best_device = NULL; prev_device = addr_family == AF_INET ? (fully_activated ? priv->default_device4 : priv->activating_device4) : (fully_activated ? priv->default_device6 : priv->activating_device6); for (iter = nm_manager_get_devices (priv->manager); iter; iter = iter->next) { NMDevice *device = NM_DEVICE (iter->data); NMDeviceState state; const NMPObject *r; NMConnection *connection; guint32 metric; gboolean is_fully_activated; state = nm_device_get_state (device); if ( state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING) continue; if (nm_device_sys_iface_state_is_external (device)) continue; r = nm_device_get_best_default_route (device, addr_family); if (r) { /* XXX: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN, * which means it was injected by a VPN, not added by device. * * In this case, is it really the best device? Why do we even need the best * device?? */ metric = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (r)->metric); is_fully_activated = TRUE; } else if ( !fully_activated && (connection = nm_device_get_applied_connection (device)) && nm_utils_connection_has_default_route (connection, addr_family, NULL)) { metric = nm_utils_ip_route_metric_normalize (addr_family, nm_device_get_route_metric (device, addr_family)); is_fully_activated = FALSE; } else continue; if ( !best_device || (!best_is_fully_activated && is_fully_activated) || ( metric < best_metric || (metric == best_metric && device == prev_device))) { best_device = device; best_metric = metric; best_is_fully_activated = is_fully_activated; } } if ( !fully_activated && best_device && best_is_fully_activated) { /* There's only a best activating device if the best device * among all activating and already-activated devices is a * still-activating one. */ return NULL; } return best_device; } static gboolean all_devices_not_active (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *iter = nm_manager_get_devices (priv->manager); while (iter != NULL) { NMDeviceState state; state = nm_device_get_state (NM_DEVICE (iter->data)); if ( state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING) { iter = g_slist_next (iter); continue; } return FALSE; } return TRUE; } #define FALLBACK_HOSTNAME4 "localhost.localdomain" static void settings_set_hostname_cb (const char *hostname, gboolean result, gpointer user_data) { NMPolicy *self = NM_POLICY (user_data); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); int ret = 0; if (!result) { _LOGT (LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\""); ret = sethostname (hostname, strlen (hostname)); if (ret != 0) { int errsv = errno; _LOGW (LOGD_DNS, "set-hostname: couldn't set the system hostname to '%s': (%d) %s", hostname, errsv, strerror (errsv)); if (errsv == EPERM) _LOGW (LOGD_DNS, "set-hostname: you should use hostnamed when systemd hardening is in effect!"); } } priv->changing_hostname = FALSE; if (!ret) nm_dispatcher_call_hostname (NULL, NULL, NULL); g_object_unref (self); } #define HOST_NAME_BUFSIZE (HOST_NAME_MAX + 2) static char * _get_hostname (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); char *hostname = NULL; /* If there is an in-progress hostname change, return * the last hostname set as would be set soon... */ if (priv->changing_hostname) { _LOGT (LOGD_DNS, "get-hostname: \"%s\" (last on set)", priv->last_hostname); return g_strdup (priv->last_hostname); } /* try to get the hostname via dbus... */ if (nm_hostname_manager_get_transient_hostname (priv->hostname_manager, &hostname)) { _LOGT (LOGD_DNS, "get-hostname: \"%s\" (from dbus)", hostname); return hostname; } /* ...or retrieve it by yourself */ hostname = g_malloc (HOST_NAME_BUFSIZE); if (gethostname (hostname, HOST_NAME_BUFSIZE -1) != 0) { int errsv = errno; _LOGT (LOGD_DNS, "get-hostname: couldn't get the system hostname: (%d) %s", errsv, g_strerror (errsv)); g_free (hostname); return NULL; } /* the name may be truncated... */ hostname[HOST_NAME_BUFSIZE - 1] = '\0'; if (strlen (hostname) >= HOST_NAME_BUFSIZE -1) { _LOGT (LOGD_DNS, "get-hostname: system hostname too long: \"%s\"", hostname); g_free (hostname); return NULL; } _LOGT (LOGD_DNS, "get-hostname: \"%s\"", hostname); return hostname; } static void _set_hostname (NMPolicy *self, const char *new_hostname, const char *msg) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); gs_free char *old_hostname = NULL; const char *name; /* The incoming hostname *can* be NULL, which will get translated to * 'localhost.localdomain' or such in the hostname policy code, but we * keep cur_hostname = NULL in the case because we need to know that * there was no valid hostname to start with. */ /* Clear lookup addresses if we have a hostname, so that we don't * restart the reverse lookup thread later. */ if (new_hostname) g_clear_object (&priv->lookup.addr); /* Update the DNS only if the hostname is actually * going to change. */ if (!nm_streq0 (priv->cur_hostname, new_hostname)) { g_free (priv->cur_hostname); priv->cur_hostname = g_strdup (new_hostname); /* Notify the DNS manager of the hostname change so that the domain part, if * present, can be added to the search list. */ nm_dns_manager_set_hostname (priv->dns_manager, priv->cur_hostname, all_devices_not_active (self)); } /* Finally, set kernel hostname */ if (!new_hostname) name = FALLBACK_HOSTNAME4; else if (!new_hostname[0]) { g_warn_if_reached (); name = FALLBACK_HOSTNAME4; } else name = new_hostname; /* Don't set the hostname if it isn't actually changing */ if ( (old_hostname = _get_hostname (self)) && (nm_streq (name, old_hostname))) { _LOGT (LOGD_DNS, "set-hostname: hostname already set to '%s' (%s)", name, msg); return; } /* Keep track of the last set hostname */ g_free (priv->last_hostname); priv->last_hostname = g_strdup (name); priv->changing_hostname = TRUE; _LOGI (LOGD_DNS, "set-hostname: set hostname to '%s' (%s)", name, msg); /* Ask NMSettings to update the transient hostname using its * systemd-hostnamed proxy */ nm_hostname_manager_set_transient_hostname (priv->hostname_manager, name, settings_set_hostname_cb, g_object_ref (self)); } static void lookup_callback (GObject *source, GAsyncResult *result, gpointer user_data) { NMPolicy *self; NMPolicyPrivate *priv; gs_free char *hostname = NULL; gs_free_error GError *error = NULL; hostname = g_resolver_lookup_by_address_finish (G_RESOLVER (source), result, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; self = user_data; priv = NM_POLICY_GET_PRIVATE (self); g_clear_object (&priv->lookup.cancellable); if (hostname) _set_hostname (self, hostname, "from address lookup"); else _set_hostname (self, NULL, error->message); } static void lookup_by_address (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); nm_clear_g_cancellable (&priv->lookup.cancellable); priv->lookup.cancellable = g_cancellable_new (); g_resolver_lookup_by_address_async (priv->lookup.resolver, priv->lookup.addr, priv->lookup.cancellable, lookup_callback, self); } static void update_system_hostname (NMPolicy *self, const char *msg) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const char *configured_hostname; gs_free char *temp_hostname = NULL; const char *dhcp_hostname, *p; NMIP4Config *ip4_config; NMIP6Config *ip6_config; gboolean external_hostname = FALSE; const NMPlatformIP4Address *addr4; const NMPlatformIP6Address *addr6; g_return_if_fail (self != NULL); if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_NONE) { _LOGT (LOGD_DNS, "set-hostname: hostname is unmanaged"); return; } _LOGT (LOGD_DNS, "set-hostname: updating hostname (%s)", msg); nm_clear_g_cancellable (&priv->lookup.cancellable); /* Check if the hostname was set externally to NM, so that in that case * we can avoid to fallback to the one we got when we started. * Consider "not specific" hostnames as equal. */ if ( (temp_hostname = _get_hostname (self)) && !nm_streq0 (temp_hostname, priv->last_hostname) && ( nm_utils_is_specific_hostname (temp_hostname) || nm_utils_is_specific_hostname (priv->last_hostname))) { external_hostname = TRUE; _LOGI (LOGD_DNS, "set-hostname: current hostname was changed outside NetworkManager: '%s'", temp_hostname); priv->dhcp_hostname = FALSE; if (!nm_utils_is_specific_hostname (temp_hostname)) nm_clear_g_free (&temp_hostname); if (!nm_streq0 (temp_hostname, priv->orig_hostname)) { /* Update original (fallback) hostname */ g_free (priv->orig_hostname); priv->orig_hostname = g_steal_pointer (&temp_hostname); _LOGT (LOGD_DNS, "hostname-original: update to %s%s%s", NM_PRINT_FMT_QUOTE_STRING (priv->orig_hostname)); } } /* Hostname precedence order: * * 1) a configured hostname (from settings) * 2) automatic hostname from the default device's config (DHCP, VPN, etc) * 3) the last hostname set outside NM * 4) reverse-DNS of the best device's IPv4 address * */ /* Try a persistent hostname first */ configured_hostname = nm_hostname_manager_get_hostname (priv->hostname_manager); if (configured_hostname && nm_utils_is_specific_hostname (configured_hostname)) { _set_hostname (self, configured_hostname, "from system configuration"); priv->dhcp_hostname = FALSE; return; } if (priv->default_device4) { NMDhcp4Config *dhcp4_config; /* Grab a hostname out of the device's DHCP4 config */ dhcp4_config = nm_device_get_dhcp4_config (priv->default_device4); if (dhcp4_config) { dhcp_hostname = nm_dhcp4_config_get_option (dhcp4_config, "host_name"); if (dhcp_hostname && dhcp_hostname[0]) { p = nm_str_skip_leading_spaces (dhcp_hostname); if (p[0]) { _set_hostname (self, p, "from DHCPv4"); priv->dhcp_hostname = TRUE; return; } _LOGW (LOGD_DNS, "set-hostname: DHCPv4-provided hostname '%s' looks invalid; ignoring it", dhcp_hostname); } } } if (priv->default_device6) { NMDhcp6Config *dhcp6_config; /* Grab a hostname out of the device's DHCP6 config */ dhcp6_config = nm_device_get_dhcp6_config (priv->default_device6); if (dhcp6_config) { dhcp_hostname = nm_dhcp6_config_get_option (dhcp6_config, "host_name"); if (dhcp_hostname && dhcp_hostname[0]) { p = nm_str_skip_leading_spaces (dhcp_hostname); if (p[0]) { _set_hostname (self, p, "from DHCPv6"); priv->dhcp_hostname = TRUE; return; } _LOGW (LOGD_DNS, "set-hostname: DHCPv6-provided hostname '%s' looks invalid; ignoring it", dhcp_hostname); } } } /* If an hostname was set outside NetworkManager keep it */ if (external_hostname) return; if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_DHCP) { /* In dhcp hostname-mode, the hostname is updated only if it comes from * a DHCP host-name option: if last set was from a host-name option and * we are here than that connection is gone (with its host-name option), * so reset the hostname to the previous value */ if (priv->dhcp_hostname) { _set_hostname (self, priv->orig_hostname, "reset dhcp hostname"); priv->dhcp_hostname = FALSE; } return; } priv->dhcp_hostname = FALSE; if (!priv->default_device4 && !priv->default_device6) { /* No best device; fall back to the last hostname set externally * to NM or if there wasn't one, 'localhost.localdomain' */ _set_hostname (self, priv->orig_hostname, "no default device"); return; } /* If no automatically-configured hostname, try using the last hostname * set externally to NM */ if (priv->orig_hostname) { _set_hostname (self, priv->orig_hostname, "from system startup"); return; } /* No configured hostname, no automatically determined hostname, and no * bootup hostname. Start reverse DNS of the current IPv4 or IPv6 address. */ ip4_config = priv->default_device4 ? nm_device_get_ip4_config (priv->default_device4) : NULL; ip6_config = priv->default_device6 ? nm_device_get_ip6_config (priv->default_device6) : NULL; if ( ip4_config && (addr4 = nm_ip4_config_get_first_address (ip4_config))) { g_clear_object (&priv->lookup.addr); priv->lookup.addr = g_inet_address_new_from_bytes ((guint8 *) &addr4->address, G_SOCKET_FAMILY_IPV4); } else if ( ip6_config && (addr6 = nm_ip6_config_get_first_address (ip6_config))) { g_clear_object (&priv->lookup.addr); priv->lookup.addr = g_inet_address_new_from_bytes ((guint8 *) &addr6->address, G_SOCKET_FAMILY_IPV6); } else { /* No valid IP config; fall back to localhost.localdomain */ _set_hostname (self, NULL, "no IP config"); return; } lookup_by_address (self); } static void update_default_ac (NMPolicy *self, NMActiveConnection *best, void (*set_active_func)(NMActiveConnection*, gboolean)) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *connections, *iter; /* Clear the 'default[6]' flag on all active connections that aren't the new * default active connection. We'll set the new default after; this ensures * we don't ever have two marked 'default[6]' simultaneously. */ connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { if (NM_ACTIVE_CONNECTION (iter->data) != best) set_active_func (NM_ACTIVE_CONNECTION (iter->data), FALSE); } /* Mark new default active connection */ if (best) set_active_func (best, TRUE); } static gpointer get_best_ip_config (NMPolicy *self, int addr_family, const char **out_ip_iface, NMActiveConnection **out_ac, NMDevice **out_device, NMVpnConnection **out_vpn) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMDevice *device; gpointer conf; const GSList *iter; nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6)); for (iter = nm_manager_get_active_connections (priv->manager); iter; iter = iter->next) { NMActiveConnection *active = NM_ACTIVE_CONNECTION (iter->data); NMVpnConnection *candidate; NMVpnConnectionState vpn_state; if (!NM_IS_VPN_CONNECTION (active)) continue; candidate = NM_VPN_CONNECTION (active); vpn_state = nm_vpn_connection_get_vpn_state (candidate); if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED) continue; if (addr_family == AF_INET) conf = nm_vpn_connection_get_ip4_config (candidate); else conf = nm_vpn_connection_get_ip6_config (candidate); if (!conf) continue; if (addr_family == AF_INET) { if (!nm_ip4_config_best_default_route_get (conf)) continue; } else { if (!nm_ip6_config_best_default_route_get (conf)) continue; } /* FIXME: in case of multiple VPN candidates, choose the one with the * best metric. */ NM_SET_OUT (out_device, NULL); NM_SET_OUT (out_vpn, candidate); NM_SET_OUT (out_ac, active); NM_SET_OUT (out_ip_iface, nm_vpn_connection_get_ip_iface (candidate, TRUE)); return conf; } device = get_best_ip_device (self, addr_family, TRUE); if (device) { NMActRequest *req; if (addr_family == AF_INET) conf = nm_device_get_ip4_config (device); else conf = nm_device_get_ip6_config (device); req = nm_device_get_act_request (device); if (conf && req) { NM_SET_OUT (out_device, device); NM_SET_OUT (out_vpn, NULL); NM_SET_OUT (out_ac, NM_ACTIVE_CONNECTION (req)); NM_SET_OUT (out_ip_iface, nm_device_get_ip_iface (device)); return conf; } } NM_SET_OUT (out_device, NULL); NM_SET_OUT (out_vpn, NULL); NM_SET_OUT (out_ac, NULL); NM_SET_OUT (out_ip_iface, NULL); return NULL; } static void update_ip4_dns (NMPolicy *self, NMDnsManager *dns_mgr) { NMIP4Config *ip4_config; const char *ip_iface = NULL; NMVpnConnection *vpn = NULL; NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE; ip4_config = get_best_ip_config (self, AF_INET, &ip_iface, NULL, NULL, &vpn); if (ip4_config) { if (vpn) dns_type = NM_DNS_IP_CONFIG_TYPE_VPN; /* Tell the DNS manager this config is preferred by re-adding it with * a different IP config type. */ nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, ip4_config, dns_type); } } static void update_ip4_routing (NMPolicy *self, gboolean force_update) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMDevice *best = NULL; NMVpnConnection *vpn = NULL; NMActiveConnection *best_ac = NULL; const char *ip_iface = NULL; /* Note that we might have an IPv4 VPN tunneled over an IPv6-only device, * so we can get (vpn != NULL && best == NULL). */ if (!get_best_ip_config (self, AF_INET, &ip_iface, &best_ac, &best, &vpn)) { if (nm_clear_g_object (&priv->default_device4)) { _LOGt (LOGD_DNS, "set-default-device-4: %p", NULL); _notify (self, PROP_DEFAULT_IP4_DEVICE); } return; } g_assert ((best || vpn) && best_ac); if ( !force_update && best && best == priv->default_device4) return; if (best) { const GSList *connections, *iter; connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { NMActiveConnection *active = iter->data; if ( NM_IS_VPN_CONNECTION (active) && nm_vpn_connection_get_ip4_config (NM_VPN_CONNECTION (active)) && !nm_active_connection_get_device (active)) nm_active_connection_set_device (active, best); } } if (vpn) best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn)); update_default_ac (self, best_ac, nm_active_connection_set_default); if (!nm_g_object_ref_set (&priv->default_device4, best)) return; _LOGt (LOGD_DNS, "set-default-device-4: %p", priv->default_device4); _LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv4 routing and DNS", nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)), ip_iface); _notify (self, PROP_DEFAULT_IP4_DEVICE); } static void update_ip6_dns_delegation (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *connections, *iter; connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { NMDevice *device = nm_active_connection_get_device (iter->data); if (device && nm_device_needs_ip6_subnet (device)) nm_device_copy_ip6_dns_config (device, priv->default_device6); } } static void update_ip6_dns (NMPolicy *self, NMDnsManager *dns_mgr) { NMIP6Config *ip6_config; const char *ip_iface = NULL; NMVpnConnection *vpn = NULL; NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE; ip6_config = get_best_ip_config (self, AF_INET6, &ip_iface, NULL, NULL, &vpn); if (ip6_config) { if (vpn) dns_type = NM_DNS_IP_CONFIG_TYPE_VPN; /* Tell the DNS manager this config is preferred by re-adding it with * a different IP config type. */ nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, dns_type); } update_ip6_dns_delegation (self); } static void update_ip6_prefix_delegation (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *connections, *iter; /* There's new default IPv6 connection, try to get a prefix for everyone. */ connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { NMDevice *device = nm_active_connection_get_device (iter->data); if (device && nm_device_needs_ip6_subnet (device)) ip6_subnet_from_device (self, priv->default_device6, device); } } static void update_ip6_routing (NMPolicy *self, gboolean force_update) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMDevice *best = NULL; NMVpnConnection *vpn = NULL; NMActiveConnection *best_ac = NULL; const char *ip_iface = NULL; /* Note that we might have an IPv6 VPN tunneled over an IPv4-only device, * so we can get (vpn != NULL && best == NULL). */ if (!get_best_ip_config (self, AF_INET6, &ip_iface, &best_ac, &best, &vpn)) { if (nm_clear_g_object (&priv->default_device6)) { _LOGt (LOGD_DNS, "set-default-device-6: %p", NULL); _notify (self, PROP_DEFAULT_IP6_DEVICE); } return; } g_assert ((best || vpn) && best_ac); if ( !force_update && best && best == priv->default_device6) return; if (best) { const GSList *connections, *iter; connections = nm_manager_get_active_connections (priv->manager); for (iter = connections; iter; iter = g_slist_next (iter)) { NMActiveConnection *active = iter->data; if ( NM_IS_VPN_CONNECTION (active) && nm_vpn_connection_get_ip6_config (NM_VPN_CONNECTION (active)) && !nm_active_connection_get_device (active)) nm_active_connection_set_device (active, best); } } if (vpn) best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn)); update_default_ac (self, best_ac, nm_active_connection_set_default6); if (!nm_g_object_ref_set (&priv->default_device6, best)) return; _LOGt (LOGD_DNS, "set-default-device-6: %p", priv->default_device6); update_ip6_prefix_delegation (self); _LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv6 routing and DNS", nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)), ip_iface); _notify (self, PROP_DEFAULT_IP6_DEVICE); } static void update_routing_and_dns (NMPolicy *self, gboolean force_update) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); nm_dns_manager_begin_updates (priv->dns_manager, __func__); update_ip4_dns (self, priv->dns_manager); update_ip6_dns (self, priv->dns_manager); update_ip4_routing (self, force_update); update_ip6_routing (self, force_update); /* Update the system hostname */ update_system_hostname (self, "routing and dns"); nm_dns_manager_end_updates (priv->dns_manager, __func__); } static void check_activating_devices (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMDevice *best4, *best6 = NULL; best4 = get_best_ip_device (self, AF_INET, FALSE); best6 = get_best_ip_device (self, AF_INET6, FALSE); g_object_freeze_notify (G_OBJECT (self)); if (nm_g_object_ref_set (&priv->activating_device4, best4)) { _LOGt (LOGD_DNS, "set-activating-device-4: %p", priv->activating_device4); _notify (self, PROP_ACTIVATING_IP4_DEVICE); } if (nm_g_object_ref_set (&priv->activating_device6, best6)) { _LOGt (LOGD_DNS, "set-activating-device-6: %p", priv->activating_device6); _notify (self, PROP_ACTIVATING_IP6_DEVICE); } g_object_thaw_notify (G_OBJECT (self)); } typedef struct { NMPolicy *policy; NMDevice *device; guint autoactivate_id; } ActivateData; static void activate_data_free (ActivateData *data) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (data->policy); nm_device_remove_pending_action (data->device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); priv->pending_activation_checks = g_slist_remove (priv->pending_activation_checks, data); if (data->autoactivate_id) g_source_remove (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) { NMPolicy *self = NM_POLICY (data); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); /* Active connections should reach the DEACTIVATED state * before disappearing. */ nm_assert_not_reached(); if (g_hash_table_remove (priv->pending_active_connections, where_the_object_was)) g_object_unref (self); } static void pending_ac_state_changed (NMActiveConnection *ac, guint state, guint reason, NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMSettingsConnection *con; if (state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { /* The AC is being deactivated before the device had a chance * to move to PREPARE. Schedule a new auto-activation on the * device, but block the current connection to avoid an activation * loop. */ con = nm_active_connection_get_settings_connection (ac); nm_settings_connection_set_autoconnect_blocked_reason (con, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED); schedule_activate_check (self, nm_active_connection_get_device (ac)); /* Cleanup */ g_signal_handlers_disconnect_by_func (ac, pending_ac_state_changed, self); if (!g_hash_table_remove (priv->pending_active_connections, ac)) nm_assert_not_reached (); g_object_weak_unref (G_OBJECT (ac), pending_ac_gone, self); g_object_unref (self); } } static void auto_activate_device (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv; NMSettingsConnection *best_connection; gs_free char *specific_object = NULL; gs_free NMSettingsConnection **connections = NULL; guint i, len; nm_assert (NM_IS_POLICY (self)); nm_assert (NM_IS_DEVICE (device)); priv = NM_POLICY_GET_PRIVATE (self); // FIXME: if a device is already activating (or activated) with a connection // but another connection now overrides the current one for that device, // deactivate the device and activate the new connection instead of just // bailing if the device is already active if (nm_device_get_act_request (device)) return; connections = nm_manager_get_activatable_connections (priv->manager, &len, TRUE); if (!connections[0]) return; /* Find the first connection that should be auto-activated */ best_connection = NULL; for (i = 0; i < len; i++) { NMSettingsConnection *candidate = NM_SETTINGS_CONNECTION (connections[i]); if (!nm_settings_connection_can_autoconnect (candidate)) continue; if (nm_device_can_auto_connect (device, (NMConnection *) candidate, &specific_object)) { best_connection = candidate; break; } } if (best_connection) { GError *error = NULL; NMAuthSubject *subject; NMActiveConnection *ac; _LOGI (LOGD_DEVICE, "auto-activating connection '%s'", nm_settings_connection_get_id (best_connection)); subject = nm_auth_subject_new_internal (); ac = nm_manager_activate_connection (priv->manager, best_connection, NULL, specific_object, device, subject, NM_ACTIVATION_TYPE_MANAGED, &error); if (!ac) { _LOGI (LOGD_DEVICE, "connection '%s' auto-activation failed: (%d) %s", nm_settings_connection_get_id (best_connection), error->code, error->message); g_error_free (error); nm_settings_connection_set_autoconnect_blocked_reason (best_connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED); schedule_activate_check (self, device); return; } /* Subscribe to AC state-changed signal to detect when the * activation fails in early stages without changing device * state. */ if (nm_g_hash_table_add (priv->pending_active_connections, ac)) { g_signal_connect (ac, NM_ACTIVE_CONNECTION_STATE_CHANGED, G_CALLBACK (pending_ac_state_changed), g_object_ref (self)); g_object_weak_ref (G_OBJECT (ac), (GWeakNotify) pending_ac_gone, self); } g_object_unref (subject); } } static gboolean auto_activate_device_cb (gpointer user_data) { ActivateData *data = user_data; g_assert (data); g_assert (NM_IS_POLICY (data->policy)); g_assert (NM_IS_DEVICE (data->device)); data->autoactivate_id = 0; auto_activate_device (data->policy, data->device); activate_data_free (data); return G_SOURCE_REMOVE; } static ActivateData * find_pending_activation (GSList *list, NMDevice *device) { GSList *iter; for (iter = list; iter; iter = g_slist_next (iter)) { if (((ActivateData *) iter->data)->device == device) return iter->data; } return NULL; } /*****************************************************************************/ typedef struct { NMDevice *device; GSList *secondaries; } PendingSecondaryData; static PendingSecondaryData * pending_secondary_data_new (NMDevice *device, GSList *secondaries) { PendingSecondaryData *data; data = g_slice_new (PendingSecondaryData); data->device = g_object_ref (device); data->secondaries = secondaries; return data; } static void pending_secondary_data_free (PendingSecondaryData *data) { g_object_unref (data->device); g_slist_free_full (data->secondaries, g_object_unref); g_slice_free (PendingSecondaryData, data); } static void process_secondaries (NMPolicy *self, NMActiveConnection *active, gboolean connected) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); GSList *iter, *iter2, *next, *next2; /* Loop through devices waiting for secondary connections to activate */ for (iter = priv->pending_secondaries; iter; iter = next) { PendingSecondaryData *secondary_data = (PendingSecondaryData *) iter->data; NMDevice *item_device = secondary_data->device; next = g_slist_next (iter); /* Look for 'active' in each device's secondary connections list */ for (iter2 = secondary_data->secondaries; iter2; iter2 = next2) { NMActiveConnection *secondary_active = NM_ACTIVE_CONNECTION (iter2->data); next2 = g_slist_next (iter2); if (active != secondary_active) continue; if (connected) { _LOGD (LOGD_DEVICE, "secondary connection '%s' succeeded; active path '%s'", nm_active_connection_get_settings_connection_id (active), nm_exported_object_get_path (NM_EXPORTED_OBJECT (active))); /* Secondary connection activated */ secondary_data->secondaries = g_slist_remove (secondary_data->secondaries, secondary_active); g_object_unref (secondary_active); if (!secondary_data->secondaries) { /* No secondary UUID remained -> remove the secondary data item */ priv->pending_secondaries = g_slist_remove (priv->pending_secondaries, secondary_data); pending_secondary_data_free (secondary_data); if (nm_device_get_state (item_device) == NM_DEVICE_STATE_SECONDARIES) nm_device_state_changed (item_device, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE); break; } } else { _LOGD (LOGD_DEVICE, "secondary connection '%s' failed; active path '%s'", nm_active_connection_get_settings_connection_id (active), nm_exported_object_get_path (NM_EXPORTED_OBJECT (active))); /* Secondary connection failed -> do not watch other connections */ priv->pending_secondaries = g_slist_remove (priv->pending_secondaries, secondary_data); pending_secondary_data_free (secondary_data); if ( nm_device_get_state (item_device) == NM_DEVICE_STATE_SECONDARIES || nm_device_get_state (item_device) == NM_DEVICE_STATE_ACTIVATED) nm_device_state_changed (item_device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED); break; } } } } static void hostname_changed (NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); update_system_hostname (self, "hostname changed"); } static void reset_autoconnect_all (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; if (device) { _LOGD (LOGD_DEVICE, "re-enabling autoconnect for all connections on %s", nm_device_get_iface (device)); } else _LOGD (LOGD_DEVICE, "re-enabling autoconnect for all connections"); connections = nm_settings_get_connections_sorted (priv->settings, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *connection = connections[i]; if (!device || nm_device_check_connection_compatible (device, NM_CONNECTION (connection))) { nm_settings_connection_reset_autoconnect_retries (connection); nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE); } } } static void reset_autoconnect_for_failed_secrets (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; _LOGD (LOGD_DEVICE, "re-enabling autoconnect for all connections with failed secrets"); connections = nm_settings_get_connections_sorted (priv->settings, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *connection = connections[i]; if (nm_settings_connection_get_autoconnect_blocked_reason (connection) == NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS) { nm_settings_connection_reset_autoconnect_retries (connection); nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE); } } } static void block_autoconnect_for_device (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; _LOGD (LOGD_DEVICE, "blocking autoconnect for all connections on %s", nm_device_get_iface (device)); /* NMDevice keeps its own autoconnect-able-ness state; we only need to * explicitly block connections for software devices, where the NMDevice * might be destroyed and recreated later. */ if (!nm_device_is_software (device)) return; connections = nm_settings_get_connections_sorted (priv->settings, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *connection = connections[i]; if (nm_device_check_connection_compatible (device, NM_CONNECTION (connection))) { nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST); } } } static void sleeping_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); gboolean sleeping = FALSE, enabled = FALSE; g_object_get (G_OBJECT (manager), NM_MANAGER_SLEEPING, &sleeping, NULL); g_object_get (G_OBJECT (manager), NM_MANAGER_NETWORKING_ENABLED, &enabled, NULL); /* Reset retries on all connections so they'll checked on wakeup */ if (sleeping || !enabled) reset_autoconnect_all (self, NULL); } static void schedule_activate_check (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); ActivateData *data; const GSList *active_connections, *iter; if (nm_manager_get_state (priv->manager) == NM_STATE_ASLEEP) return; if (!nm_device_get_enabled (device)) return; if (!nm_device_autoconnect_allowed (device)) return; if (find_pending_activation (priv->pending_activation_checks, device)) return; active_connections = nm_manager_get_active_connections (priv->manager); for (iter = active_connections; iter; iter = iter->next) { if (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (iter->data)) == device) return; } 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); priv->pending_activation_checks = g_slist_append (priv->pending_activation_checks, data); } static void clear_pending_activate_check (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); ActivateData *data; data = find_pending_activation (priv->pending_activation_checks, device); if (data && data->autoactivate_id) activate_data_free (data); } static gboolean reset_connections_retries (gpointer user_data) { NMPolicy *self = (NMPolicy *) user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; gint32 con_stamp, min_stamp, now; gboolean changed = FALSE; priv->reset_retries_id = 0; min_stamp = 0; now = nm_utils_get_monotonic_timestamp_s (); connections = nm_settings_get_connections_sorted (priv->settings, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *connection = connections[i]; con_stamp = nm_settings_connection_get_autoconnect_retry_time (connection); if (con_stamp == 0) continue; if (con_stamp <= now) { nm_settings_connection_reset_autoconnect_retries (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 anything changed, try to activate the newly re-enabled connections */ if (changed) schedule_activate_all (self); return FALSE; } static void activate_slave_connections (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const char *master_device, *master_uuid_settings = NULL, *master_uuid_applied = NULL; guint i; NMActRequest *req; gboolean internal_activation = FALSE; gs_free NMSettingsConnection **connections = NULL; master_device = nm_device_get_iface (device); g_assert (master_device); req = nm_device_get_act_request (device); if (req) { NMConnection *con; NMAuthSubject *subject; con = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (req)); if (con) master_uuid_applied = nm_connection_get_uuid (con); con = NM_CONNECTION (nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (req))); if (con) { master_uuid_settings = nm_connection_get_uuid (con); if (!g_strcmp0 (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_is_internal (subject); } connections = nm_settings_get_connections_sorted (priv->settings, NULL); for (i = 0; connections[i]; i++) { NMConnection *slave; NMSettingConnection *s_slave_con; const char *slave_master; slave = NM_CONNECTION (connections[i]); s_slave_con = nm_connection_get_setting_connection (slave); g_assert (s_slave_con); slave_master = nm_setting_connection_get_master (s_slave_con); if (!slave_master) continue; if ( nm_streq0 (slave_master, master_device) || nm_streq0 (slave_master, master_uuid_applied) || nm_streq0 (slave_master, master_uuid_settings)) { NMSettingsConnection *settings = NM_SETTINGS_CONNECTION (slave); NMSettingsAutoconnectBlockedReason reason; if (!internal_activation) nm_settings_connection_reset_autoconnect_retries (settings); reason = nm_settings_connection_get_autoconnect_blocked_reason (settings); if (reason == NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED) { reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE; nm_settings_connection_set_autoconnect_blocked_reason (settings, reason); } } } schedule_activate_all (self); } static gboolean activate_secondary_connections (NMPolicy *self, NMConnection *connection, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMSettingConnection *s_con; NMSettingsConnection *settings_con; NMActiveConnection *ac; PendingSecondaryData *secondary_data; GSList *secondary_ac_list = NULL; GError *error = NULL; guint32 i; gboolean success = TRUE; s_con = nm_connection_get_setting_connection (connection); g_assert (s_con); for (i = 0; i < nm_setting_connection_get_num_secondaries (s_con); i++) { const char *sec_uuid = nm_setting_connection_get_secondary (s_con, i); NMActRequest *req; settings_con = nm_settings_get_connection_by_uuid (priv->settings, sec_uuid); if (!settings_con) { _LOGW (LOGD_DEVICE, "secondary connection '%s' auto-activation failed: The connection doesn't exist.", sec_uuid); success = FALSE; break; } if (!nm_connection_is_type (NM_CONNECTION (settings_con), NM_SETTING_VPN_SETTING_NAME)) { _LOGW (LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: The connection is not a VPN.", nm_settings_connection_get_id (settings_con), sec_uuid); success = FALSE; break; } req = nm_device_get_act_request (device); g_assert (req); _LOGD (LOGD_DEVICE, "activating secondary connection '%s (%s)' for base connection '%s (%s)'", nm_settings_connection_get_id (settings_con), sec_uuid, nm_connection_get_id (connection), nm_connection_get_uuid (connection)); ac = nm_manager_activate_connection (priv->manager, settings_con, NULL, nm_exported_object_get_path (NM_EXPORTED_OBJECT (req)), device, nm_active_connection_get_subject (NM_ACTIVE_CONNECTION (req)), NM_ACTIVATION_TYPE_MANAGED, &error); if (ac) secondary_ac_list = g_slist_append (secondary_ac_list, g_object_ref (ac)); else { _LOGW (LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: (%d) %s", nm_settings_connection_get_id (settings_con), sec_uuid, error->code, error->message); g_clear_error (&error); success = FALSE; break; } } if (success && secondary_ac_list != NULL) { secondary_data = pending_secondary_data_new (device, secondary_ac_list); priv->pending_secondaries = g_slist_append (priv->pending_secondaries, secondary_data); } else g_slist_free_full (secondary_ac_list, g_object_unref); return success; } static void device_state_changed (NMDevice *device, NMDeviceState new_state, NMDeviceState old_state, NMDeviceStateReason reason, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); NMActiveConnection *ac; NMSettingsConnection *connection = nm_device_get_settings_connection (device); const char *ip_iface = nm_device_get_ip_iface (device); NMIP4Config *ip4_config; NMIP6Config *ip6_config; NMSettingConnection *s_con = NULL; switch (new_state) { case NM_DEVICE_STATE_FAILED: /* Mark the connection invalid if it failed during activation so that * it doesn't get automatically chosen over and over and over again. */ if ( connection && old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_ACTIVATED) { int tries; tries = nm_settings_connection_get_autoconnect_retries (connection); if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) { _LOGD (LOGD_DEVICE, "connection '%s' now blocked from autoconnect due to no secrets", nm_settings_connection_get_id (connection)); nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS); } else if (tries != 0) { if (tries > 0) { _LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; %d tries left", nm_settings_connection_get_id (connection), tries); nm_settings_connection_set_autoconnect_retries (connection, --tries); } else { _LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; infinite tries left", nm_settings_connection_get_id (connection)); } } if (nm_settings_connection_get_autoconnect_retries (connection) == 0) { _LOGI (LOGD_DEVICE, "disabling autoconnect for connection '%s'.", nm_settings_connection_get_id (connection)); /* Schedule a handler to reset retries count */ if (!priv->reset_retries_id) { gint32 retry_time = nm_settings_connection_get_autoconnect_retry_time (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_s ()), reset_connections_retries, self); } } nm_connection_clear_secrets (NM_CONNECTION (connection)); } break; case NM_DEVICE_STATE_ACTIVATED: if (connection) { /* Reset auto retries back to default since connection was successful */ nm_settings_connection_reset_autoconnect_retries (connection); /* And clear secrets so they will always be requested from the * settings service when the next connection is made. */ nm_connection_clear_secrets (NM_CONNECTION (connection)); } /* Add device's new IPv4 and IPv6 configs to DNS */ nm_dns_manager_begin_updates (priv->dns_manager, __func__); ip4_config = nm_device_get_ip4_config (device); if (ip4_config) nm_dns_manager_add_ip4_config (priv->dns_manager, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT); ip6_config = nm_device_get_ip6_config (device); if (ip6_config) nm_dns_manager_add_ip6_config (priv->dns_manager, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT); update_routing_and_dns (self, FALSE); nm_dns_manager_end_updates (priv->dns_manager, __func__); break; case NM_DEVICE_STATE_UNMANAGED: case NM_DEVICE_STATE_UNAVAILABLE: if (old_state > NM_DEVICE_STATE_DISCONNECTED) update_routing_and_dns (self, FALSE); break; case NM_DEVICE_STATE_DEACTIVATING: if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_USER_REQUESTED) { if (!nm_device_get_autoconnect (device)) { /* The device was disconnected; block all connections on it */ block_autoconnect_for_device (self, device); } else { if (connection) { /* The connection was deactivated, so block just this connection */ _LOGD (LOGD_DEVICE, "blocking autoconnect of connection '%s' by user request", nm_settings_connection_get_id (connection)); nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST); } } } ip6_remove_device_prefix_delegations (self, device); break; case NM_DEVICE_STATE_DISCONNECTED: /* Reset retry counts for a device's connections when carrier on; if cable * was unplugged and plugged in again, we should try to reconnect. */ if ( nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CARRIER && old_state == NM_DEVICE_STATE_UNAVAILABLE) reset_autoconnect_all (self, device); if (old_state > NM_DEVICE_STATE_DISCONNECTED) update_routing_and_dns (self, FALSE); /* Device is now available for auto-activation */ schedule_activate_check (self, device); break; case NM_DEVICE_STATE_PREPARE: /* Reset auto-connect retries of all slaves and schedule them for * activation. */ activate_slave_connections (self, device); /* Now that the device state is progressing, we don't care * anymore for the AC state. */ ac = (NMActiveConnection *) nm_device_get_act_request (device); if (ac && g_hash_table_remove (priv->pending_active_connections, ac)) { g_signal_handlers_disconnect_by_func (ac, pending_ac_state_changed, self); g_object_weak_unref (G_OBJECT (ac), pending_ac_gone, self); g_object_unref (self); } break; case NM_DEVICE_STATE_IP_CONFIG: /* We must have secrets if we got here. */ if (connection) nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE); break; case NM_DEVICE_STATE_SECONDARIES: if (connection) s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection)); if (s_con && nm_setting_connection_get_num_secondaries (s_con) > 0) { /* Make routes and DNS up-to-date before activating dependent connections */ update_routing_and_dns (self, FALSE); /* Activate secondary (VPN) connections */ if (!activate_secondary_connections (self, NM_CONNECTION (connection), device)) nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED); } else nm_device_queue_state (device, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE); break; default: break; } check_activating_devices (self); } static void device_ip4_config_changed (NMDevice *device, NMIP4Config *new_config, NMIP4Config *old_config, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); const char *ip_iface = nm_device_get_ip_iface (device); nm_dns_manager_begin_updates (priv->dns_manager, __func__); /* We catch already all the IP events registering on the device state changes but * the ones where the IP changes but the device state keep stable (i.e., activated): * ignore IP config changes but when the device is in activated state. * Prevents unecessary changes to DNS information. */ if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) { if (old_config != new_config) { if (old_config) nm_dns_manager_remove_ip4_config (priv->dns_manager, old_config); if (new_config) nm_dns_manager_add_ip4_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT); } update_ip4_dns (self, priv->dns_manager); update_ip4_routing (self, TRUE); update_system_hostname (self, "ip4 conf"); } else { /* Old configs get removed immediately */ if (old_config) nm_dns_manager_remove_ip4_config (priv->dns_manager, old_config); } nm_dns_manager_end_updates (priv->dns_manager, __func__); } static void device_ip6_config_changed (NMDevice *device, NMIP6Config *new_config, NMIP6Config *old_config, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); const char *ip_iface = nm_device_get_ip_iface (device); nm_dns_manager_begin_updates (priv->dns_manager, __func__); /* We catch already all the IP events registering on the device state changes but * the ones where the IP changes but the device state keep stable (i.e., activated): * ignore IP config changes but when the device is in activated state. * Prevents unecessary changes to DNS information. */ if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) { if (old_config != new_config) { if (old_config) nm_dns_manager_remove_ip6_config (priv->dns_manager, old_config); if (new_config) nm_dns_manager_add_ip6_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT); } update_ip6_dns (self, priv->dns_manager); update_ip6_routing (self, TRUE); update_system_hostname (self, "ip6 conf"); } else { /* Old configs get removed immediately */ if (old_config) nm_dns_manager_remove_ip6_config (priv->dns_manager, old_config); } nm_dns_manager_end_updates (priv->dns_manager, __func__); } /*****************************************************************************/ static void device_autoconnect_changed (NMDevice *device, GParamSpec *pspec, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); if (nm_device_autoconnect_allowed (device)) 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); } static void devices_list_unregister (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); g_signal_handlers_disconnect_by_data ((GObject *) device, priv); } static void devices_list_register (NMPolicy *self, NMDevice *device) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); /* Connect state-changed with _after, so that the handler is invoked after other handlers. */ g_signal_connect_after (device, NM_DEVICE_STATE_CHANGED, (GCallback) device_state_changed, priv); g_signal_connect (device, NM_DEVICE_IP4_CONFIG_CHANGED, (GCallback) device_ip4_config_changed, priv); g_signal_connect (device, NM_DEVICE_IP6_CONFIG_CHANGED, (GCallback) device_ip6_config_changed, priv); g_signal_connect (device, NM_DEVICE_IP6_PREFIX_DELEGATED, (GCallback) device_ip6_prefix_delegated, priv); g_signal_connect (device, NM_DEVICE_IP6_SUBNET_NEEDED, (GCallback) device_ip6_subnet_needed, priv); g_signal_connect (device, "notify::" NM_DEVICE_AUTOCONNECT, (GCallback) device_autoconnect_changed, priv); g_signal_connect (device, NM_DEVICE_RECHECK_AUTO_ACTIVATE, (GCallback) device_recheck_auto_activate, priv); } static void device_added (NMManager *manager, NMDevice *device, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); g_return_if_fail (NM_IS_POLICY (self)); priv = NM_POLICY_GET_PRIVATE (self); if (!nm_g_hash_table_add (priv->devices, device)) g_return_if_reached (); devices_list_register (self, device); } static void device_removed (NMManager *manager, NMDevice *device, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); /* XXX 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 */ clear_pending_activate_check (self, device); if (g_hash_table_remove (priv->devices, device)) devices_list_unregister (self, device); /* Don't update routing and DNS here as we've already handled that * for devices that need it when the device's state changed to UNMANAGED. */ } /*****************************************************************************/ static void vpn_connection_activated (NMPolicy *self, NMVpnConnection *vpn) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMIP4Config *ip4_config; NMIP6Config *ip6_config; const char *ip_iface; nm_dns_manager_begin_updates (priv->dns_manager, __func__); ip_iface = nm_vpn_connection_get_ip_iface (vpn, TRUE); /* Add the VPN connection's IP configs from DNS */ ip4_config = nm_vpn_connection_get_ip4_config (vpn); if (ip4_config) nm_dns_manager_add_ip4_config (priv->dns_manager, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_VPN); ip6_config = nm_vpn_connection_get_ip6_config (vpn); if (ip6_config) nm_dns_manager_add_ip6_config (priv->dns_manager, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_VPN); update_routing_and_dns (self, TRUE); nm_dns_manager_end_updates (priv->dns_manager, __func__); } static void vpn_connection_deactivated (NMPolicy *self, NMVpnConnection *vpn) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMIP4Config *ip4_config; NMIP6Config *ip6_config; nm_dns_manager_begin_updates (priv->dns_manager, __func__); ip4_config = nm_vpn_connection_get_ip4_config (vpn); if (ip4_config) { /* Remove the VPN connection's IP4 config from DNS */ nm_dns_manager_remove_ip4_config (priv->dns_manager, ip4_config); } ip6_config = nm_vpn_connection_get_ip6_config (vpn); if (ip6_config) { /* Remove the VPN connection's IP6 config from DNS */ nm_dns_manager_remove_ip6_config (priv->dns_manager, ip6_config); } update_routing_and_dns (self, TRUE); nm_dns_manager_end_updates (priv->dns_manager, __func__); } static void vpn_connection_state_changed (NMVpnConnection *vpn, NMVpnConnectionState new_state, NMVpnConnectionState old_state, NMActiveConnectionStateReason reason, NMPolicy *self) { if (new_state == NM_VPN_CONNECTION_STATE_ACTIVATED) vpn_connection_activated (self, vpn); else if (new_state >= NM_VPN_CONNECTION_STATE_FAILED) { /* Only clean up IP/DNS if the connection ever got past IP_CONFIG */ if (old_state >= NM_VPN_CONNECTION_STATE_IP_CONFIG_GET && old_state <= NM_VPN_CONNECTION_STATE_ACTIVATED) vpn_connection_deactivated (self, vpn); } } static void vpn_connection_retry_after_failure (NMVpnConnection *vpn, NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); NMActiveConnection *ac = NM_ACTIVE_CONNECTION (vpn); NMSettingsConnection *connection = nm_active_connection_get_settings_connection (ac); GError *error = NULL; /* Attempt to reconnect VPN connections that failed after being connected */ if (!nm_manager_activate_connection (priv->manager, connection, NULL, NULL, NULL, nm_active_connection_get_subject (ac), NM_ACTIVATION_TYPE_MANAGED, &error)) { _LOGW (LOGD_DEVICE, "VPN '%s' reconnect failed: %s", nm_settings_connection_get_id (connection), error->message ? error->message : "unknown"); g_clear_error (&error); } } static void active_connection_state_changed (NMActiveConnection *active, GParamSpec *pspec, NMPolicy *self) { NMActiveConnectionState state = nm_active_connection_get_state (active); if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) process_secondaries (self, active, TRUE); else if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) process_secondaries (self, active, FALSE); } static void active_connection_added (NMManager *manager, NMActiveConnection *active, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); if (NM_IS_VPN_CONNECTION (active)) { g_signal_connect (active, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED, G_CALLBACK (vpn_connection_state_changed), self); g_signal_connect (active, NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE, G_CALLBACK (vpn_connection_retry_after_failure), self); } g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_STATE, G_CALLBACK (active_connection_state_changed), self); } static void active_connection_removed (NMManager *manager, NMActiveConnection *active, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); g_signal_handlers_disconnect_by_func (active, vpn_connection_state_changed, self); g_signal_handlers_disconnect_by_func (active, vpn_connection_retry_after_failure, self); g_signal_handlers_disconnect_by_func (active, active_connection_state_changed, self); } /*****************************************************************************/ static gboolean schedule_activate_all_cb (gpointer user_data) { NMPolicy *self = user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *iter; priv->schedule_activate_all_id = 0; for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter)) schedule_activate_check (self, iter->data); return G_SOURCE_REMOVE; } static void schedule_activate_all (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); } 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); } static void firewall_state_changed (NMFirewallManager *manager, gboolean initialized_now, gpointer user_data) { NMPolicy *self = (NMPolicy *) user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *iter; if (initialized_now) { /* the firewall manager was initializing, but all requests * so fare were queued and are already sent. No need to * re-update the firewall zone of the devices. */ return; } if (!nm_firewall_manager_get_running (manager)) return; /* add interface of each device to correct zone */ for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter)) nm_device_update_firewall_zone (iter->data); } static void dns_config_changed (NMDnsManager *dns_manager, gpointer user_data) { NMPolicy *self = (NMPolicy *) user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); /* Restart a thread for reverse-DNS lookup after we are signalled that * DNS changed. Because the result from a previous run may not be right * (race in updating DNS and doing the reverse lookup). */ nm_clear_g_cancellable (&priv->lookup.cancellable); /* Re-start the hostname lookup thread if we don't have hostname yet. */ if (priv->lookup.addr) { char *str = NULL; gs_free char *hostname = NULL; /* Check if the hostname was externally set */ if ( (hostname = _get_hostname (self)) && nm_utils_is_specific_hostname (hostname) && !nm_streq0 (hostname, priv->last_hostname)) { g_clear_object (&priv->lookup.addr); return; } _LOGD (LOGD_DNS, "restarting reverse-lookup thread for address %s", (str = g_inet_address_to_string (priv->lookup.addr))); g_free (str); lookup_by_address (self); } } static void connection_updated (NMSettings *settings, NMSettingsConnection *connection, gboolean by_user, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); const GSList *iter; NMDevice *device = NULL; if (by_user) { /* find device with given connection */ for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter)) { NMDevice *dev = NM_DEVICE (iter->data); if (nm_device_get_settings_connection (dev) == connection) { device = dev; break; } } if (device) nm_device_reapply_settings_immediately (device); /* Reset auto retries back to default since connection was updated */ nm_settings_connection_reset_autoconnect_retries (connection); } schedule_activate_all (self); } static void _deactivate_if_active (NMManager *manager, NMSettingsConnection *connection) { const GSList *active, *iter; active = nm_manager_get_active_connections (manager); for (iter = active; iter; iter = g_slist_next (iter)) { NMActiveConnection *ac = iter->data; NMActiveConnectionState state = nm_active_connection_get_state (ac); GError *error = NULL; if (nm_active_connection_get_settings_connection (ac) == connection && (state <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) { if (!nm_manager_deactivate_connection (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); } } } } static void connection_removed (NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) { NMPolicyPrivate *priv = user_data; _deactivate_if_active (priv->manager, connection); } static void connection_visibility_changed (NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); if (nm_settings_connection_is_visible (connection)) schedule_activate_all (self); else _deactivate_if_active (priv->manager, connection); } static void secret_agent_registered (NMSettings *settings, NMSecretAgent *agent, gpointer user_data) { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); /* The registered secret agent may provide some missing secrets. Thus we * reset retries count here and schedule activation, so that the * connections failed due to missing secrets may re-try auto-connection. */ reset_autoconnect_for_failed_secrets (self); schedule_activate_all (self); } NMDevice * nm_policy_get_default_ip4_device (NMPolicy *self) { return NM_POLICY_GET_PRIVATE (self)->default_device4; } NMDevice * nm_policy_get_default_ip6_device (NMPolicy *self) { return NM_POLICY_GET_PRIVATE (self)->default_device6; } NMDevice * nm_policy_get_activating_ip4_device (NMPolicy *self) { return NM_POLICY_GET_PRIVATE (self)->activating_device4; } NMDevice * nm_policy_get_activating_ip6_device (NMPolicy *self) { return NM_POLICY_GET_PRIVATE (self)->activating_device6; } /*****************************************************************************/ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_hostname_mode_to_string, NMPolicyHostnameMode, NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"), NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_NONE, "none"), NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_DHCP, "dhcp"), NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_FULL, "full"), ); /*****************************************************************************/ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMPolicy *self = NM_POLICY (object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); switch (prop_id) { case PROP_DEFAULT_IP4_DEVICE: g_value_set_object (value, priv->default_device4); break; case PROP_DEFAULT_IP6_DEVICE: g_value_set_object (value, priv->default_device6); break; case PROP_ACTIVATING_IP4_DEVICE: g_value_set_object (value, priv->activating_device4); break; case PROP_ACTIVATING_IP6_DEVICE: g_value_set_object (value, priv->activating_device6); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMPolicy *self = NM_POLICY (object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); switch (prop_id) { case PROP_MANAGER: /* construct-only */ priv->manager = g_value_get_object (value); g_return_if_fail (NM_IS_MANAGER (priv->manager)); break; case PROP_SETTINGS: /* construct-only */ priv->settings = g_value_dup_object (value); g_return_if_fail (NM_IS_SETTINGS (priv->settings)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_policy_init (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const char *hostname_mode; priv->netns = g_object_ref (nm_netns_get ()); priv->hostname_manager = g_object_ref (nm_hostname_manager_get ()); hostname_mode = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG, NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE, NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); if (nm_streq0 (hostname_mode, "none")) priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_NONE; else if (nm_streq0 (hostname_mode, "dhcp")) priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_DHCP; else /* default - full mode */ priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_FULL; priv->devices = g_hash_table_new (NULL, NULL); priv->pending_active_connections = g_hash_table_new (NULL, NULL); priv->ip6_prefix_delegations = g_array_new (FALSE, FALSE, sizeof (IP6PrefixDelegation)); g_array_set_clear_func (priv->ip6_prefix_delegations, clear_ip6_prefix_delegation); } static void constructed (GObject *object) { NMPolicy *self = NM_POLICY (object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); char *hostname = NULL; /* Grab hostname on startup and use that if nothing provides one */ if ((hostname = _get_hostname (self))) { /* init last_hostname */ priv->last_hostname = hostname; /* only cache it if it's a valid hostname */ if (nm_utils_is_specific_hostname (hostname)) priv->orig_hostname = g_strdup (hostname); } _LOGT (LOGD_DNS, "hostname-original: set to %s%s%s", NM_PRINT_FMT_QUOTE_STRING (priv->orig_hostname)); priv->firewall_manager = g_object_ref (nm_firewall_manager_get ()); g_signal_connect (priv->firewall_manager, NM_FIREWALL_MANAGER_STATE_CHANGED, G_CALLBACK (firewall_state_changed), self); priv->dns_manager = g_object_ref (nm_dns_manager_get ()); nm_dns_manager_set_initial_hostname (priv->dns_manager, priv->orig_hostname); priv->config_changed_id = g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (dns_config_changed), self); priv->lookup.resolver = g_resolver_get_default (); g_signal_connect (priv->hostname_manager, "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, (GCallback) hostname_changed, priv); g_signal_connect (priv->manager, "notify::" NM_MANAGER_SLEEPING, (GCallback) sleeping_changed, priv); g_signal_connect (priv->manager, "notify::" NM_MANAGER_NETWORKING_ENABLED, (GCallback) sleeping_changed, priv); g_signal_connect (priv->manager, NM_MANAGER_INTERNAL_DEVICE_ADDED, (GCallback) device_added, priv); g_signal_connect (priv->manager, NM_MANAGER_INTERNAL_DEVICE_REMOVED, (GCallback) device_removed, priv); g_signal_connect (priv->manager, NM_MANAGER_ACTIVE_CONNECTION_ADDED, (GCallback) active_connection_added, priv); g_signal_connect (priv->manager, NM_MANAGER_ACTIVE_CONNECTION_REMOVED, (GCallback) active_connection_removed, priv); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, (GCallback) connection_added, priv); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, (GCallback) connection_updated, priv); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, (GCallback) connection_removed, priv); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_VISIBILITY_CHANGED, (GCallback) connection_visibility_changed, priv); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_AGENT_REGISTERED, (GCallback) secret_agent_registered, priv); G_OBJECT_CLASS (nm_policy_parent_class)->constructed (object); _LOGD (LOGD_DNS, "hostname-mode: %s", _hostname_mode_to_string (priv->hostname_mode)); } NMPolicy * nm_policy_new (NMManager *manager, NMSettings *settings) { g_return_val_if_fail (NM_IS_MANAGER (manager), NULL); g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL); return g_object_new (NM_TYPE_POLICY, NM_POLICY_MANAGER, manager, NM_POLICY_SETTINGS, settings, NULL); } static void dispose (GObject *object) { NMPolicy *self = NM_POLICY (object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); const GSList *connections; GHashTableIter h_iter; NMDevice *device; nm_clear_g_cancellable (&priv->lookup.cancellable); g_clear_object (&priv->lookup.addr); g_clear_object (&priv->lookup.resolver); nm_clear_g_object (&priv->default_device4); nm_clear_g_object (&priv->default_device6); nm_clear_g_object (&priv->activating_device4); nm_clear_g_object (&priv->activating_device6); g_clear_pointer (&priv->pending_active_connections, g_hash_table_unref); while (priv->pending_activation_checks) activate_data_free (priv->pending_activation_checks->data); g_slist_free_full (priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free); priv->pending_secondaries = NULL; if (priv->firewall_manager) { g_signal_handlers_disconnect_by_func (priv->firewall_manager, firewall_state_changed, self); g_clear_object (&priv->firewall_manager); } if (priv->dns_manager) { nm_clear_g_signal_handler (priv->dns_manager, &priv->config_changed_id); g_clear_object (&priv->dns_manager); } g_hash_table_iter_init (&h_iter, priv->devices); if (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. */ connections = nm_manager_get_active_connections (priv->manager); g_assert (connections == NULL); nm_clear_g_source (&priv->reset_retries_id); nm_clear_g_source (&priv->schedule_activate_all_id); g_clear_pointer (&priv->orig_hostname, g_free); g_clear_pointer (&priv->cur_hostname, g_free); g_clear_pointer (&priv->last_hostname, g_free); if (priv->hostname_manager) { g_signal_handlers_disconnect_by_data (priv->hostname_manager, priv); g_clear_object (&priv->hostname_manager); } if (priv->settings) { g_signal_handlers_disconnect_by_data (priv->settings, priv); g_clear_object (&priv->settings); /* we don't clear priv->manager as we don't own a reference to it, * that is, NMManager must outlive NMPolicy anyway. * * Hence, we unsubscribe the signals here together with the signals * for settings. */ g_signal_handlers_disconnect_by_data (priv->manager, priv); } if (priv->ip6_prefix_delegations) { g_array_free (priv->ip6_prefix_delegations, TRUE); priv->ip6_prefix_delegations = NULL; } nm_assert (NM_IS_MANAGER (priv->manager)); G_OBJECT_CLASS (nm_policy_parent_class)->dispose (object); } static void finalize (GObject *object) { NMPolicy *self = NM_POLICY (object); NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); g_hash_table_unref (priv->devices); G_OBJECT_CLASS (nm_policy_parent_class)->finalize (object); g_object_unref (priv->netns); } static void nm_policy_class_init (NMPolicyClass *policy_class) { GObjectClass *object_class = G_OBJECT_CLASS (policy_class); object_class->get_property = get_property; object_class->set_property = set_property; object_class->constructed = constructed; object_class->dispose = dispose; object_class->finalize = finalize; obj_properties[PROP_MANAGER] = g_param_spec_object (NM_POLICY_MANAGER, "", "", NM_TYPE_MANAGER, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SETTINGS] = g_param_spec_object (NM_POLICY_SETTINGS, "", "", NM_TYPE_SETTINGS, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DEFAULT_IP4_DEVICE] = g_param_spec_object (NM_POLICY_DEFAULT_IP4_DEVICE, "", "", NM_TYPE_DEVICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DEFAULT_IP6_DEVICE] = g_param_spec_object (NM_POLICY_DEFAULT_IP6_DEVICE, "", "", NM_TYPE_DEVICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACTIVATING_IP4_DEVICE] = g_param_spec_object (NM_POLICY_ACTIVATING_IP4_DEVICE, "", "", NM_TYPE_DEVICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACTIVATING_IP6_DEVICE] = g_param_spec_object (NM_POLICY_ACTIVATING_IP6_DEVICE, "", "", NM_TYPE_DEVICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); }