NetworkManager/src/nm-policy.c
Aleksander Morgado eebaf181d5 policy: don't block connection if device is gone
If the active connection is deactivated because the device is gone,
don't block autoconnection. Otherwise, whenever the device comes
back (e.g. maybe it was reset in the middle of a connection attempt),
the autoconnection logic won't be triggered, as the settings are still
blocked.

I'm able to reproduce this by performing a WWAN modem reset in the
middle of a connection attempt.

https://github.com/NetworkManager/NetworkManager/pull/121
(cherry picked from commit d97eab6c5a)
2018-09-13 15:16:02 +02:00

2743 lines
90 KiB
C

/* -*- 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 <string.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#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 "settings/nm-agent-manager.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;
CList pending_activation_checks;
NMAgentManager *agent_mgr;
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;
guint i;
const CList *tmp_list;
NMActiveConnection *ac;
_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. */
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
NMDevice *to_device;
to_device = nm_active_connection_get_device (ac);
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,
int addr_family,
NMActiveConnection *best)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
const CList *tmp_list;
NMActiveConnection *ac;
/* 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.
*/
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
if (ac != best)
nm_active_connection_set_default (ac, addr_family, FALSE);
}
/* Mark new default active connection */
if (best)
nm_active_connection_set_default (best, addr_family, 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 CList *tmp_list;
NMActiveConnection *ac;
nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
NMVpnConnection *candidate;
NMVpnConnectionState vpn_state;
if (!NM_IS_VPN_CONNECTION (ac))
continue;
candidate = NM_VPN_CONNECTION (ac);
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, ac);
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_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;
const CList *tmp_list;
NMActiveConnection *ac;
/* 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) {
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
if ( NM_IS_VPN_CONNECTION (ac)
&& nm_vpn_connection_get_ip4_config (NM_VPN_CONNECTION (ac))
&& !nm_active_connection_get_device (ac))
nm_active_connection_set_device (ac, best);
}
}
if (vpn)
best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
update_default_ac (self, AF_INET, best_ac);
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);
NMDevice *device;
NMActiveConnection *ac;
const CList *tmp_list;
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
device = nm_active_connection_get_device (ac);
if (device && nm_device_needs_ip6_subnet (device))
nm_device_copy_ip6_dns_config (device, priv->default_device6);
}
}
static void
update_ip6_prefix_delegation (NMPolicy *self)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
NMDevice *device;
NMActiveConnection *ac;
const CList *tmp_list;
/* There's new default IPv6 connection, try to get a prefix for everyone. */
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
device = nm_active_connection_get_device (ac);
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;
NMActiveConnection *ac;
const CList *tmp_list;
/* 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) {
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
if ( NM_IS_VPN_CONNECTION (ac)
&& nm_vpn_connection_get_ip6_config (NM_VPN_CONNECTION (ac))
&& !nm_active_connection_get_device (ac))
nm_active_connection_set_device (ac, best);
}
}
if (vpn)
best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
update_default_ac (self, AF_INET6, best_ac);
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_ip_dns (NMPolicy *self, int addr_family)
{
gpointer ip_config;
const char *ip_iface = NULL;
NMVpnConnection *vpn = NULL;
NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
nm_assert_addr_family (addr_family);
ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, NULL, &vpn);
if (ip_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_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
ip_iface,
ip_config,
dns_type);
}
if (addr_family == AF_INET6)
update_ip6_dns_delegation (self);
}
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_ip_dns (self, AF_INET);
update_ip_dns (self, AF_INET6);
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 {
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)
{
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.
*/
if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED) {
con = nm_active_connection_get_settings_connection (ac);
nm_settings_connection_autoconnect_blocked_reason_set (con, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, TRUE);
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;
gs_free_error GError *error = NULL;
NMAuthSubject *subject;
NMActiveConnection *ac;
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;
if (!nm_device_autoconnect_allowed (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]);
NMSettingConnection *s_con;
const char *permission;
if (nm_settings_connection_autoconnect_is_blocked (candidate))
continue;
s_con = nm_connection_get_setting_connection (NM_CONNECTION (candidate));
if (!nm_setting_connection_get_autoconnect (s_con))
continue;
permission = nm_utils_get_shared_wifi_permission (NM_CONNECTION (candidate));
if ( permission
&& !nm_settings_connection_check_permission (candidate, permission))
continue;
if (nm_device_can_auto_connect (device, (NMConnection *) candidate, &specific_object)) {
best_connection = candidate;
break;
}
}
if (!best_connection)
return;
_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,
NM_ACTIVATION_REASON_AUTOCONNECT,
&error);
if (!ac) {
_LOGI (LOGD_DEVICE, "connection '%s' auto-activation failed: %s",
nm_settings_connection_get_id (best_connection),
error->message);
nm_settings_connection_autoconnect_blocked_reason_set (best_connection,
NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
TRUE);
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 (NMPolicy *self, NMDevice *device)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
ActivateData *data;
c_list_for_each_entry (data, &priv->pending_activation_checks, pending_lst) {
if (data->device == device)
return 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 gboolean
reset_autoconnect_all (NMPolicy *self,
NMDevice *device, /* if present, only reset connections compatible with @device */
gboolean only_no_secrets)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
NMSettingsConnection *const*connections = NULL;
guint i;
gboolean changed = FALSE;
_LOGD (LOGD_DEVICE, "re-enabling autoconnect for all connections%s%s%s",
device ? " on " : "",
device ? nm_device_get_iface (device) : "",
only_no_secrets ? " (only clear no-secrets flag)" : "");
connections = nm_settings_get_connections (priv->settings, NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *connection = connections[i];
if ( device
&& !nm_device_check_connection_compatible (device, NM_CONNECTION (connection)))
continue;
if (only_no_secrets) {
/* we only reset the no-secrets blocked flag. */
if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
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 (connection))
changed = TRUE;
}
} else {
/* we reset the tries-count and any blocked-reason */
if (nm_settings_connection_autoconnect_retries_get (connection) == 0)
changed = TRUE;
nm_settings_connection_autoconnect_retries_reset (connection);
if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL
& ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
FALSE)) {
if (!nm_settings_connection_autoconnect_is_blocked (connection))
changed = TRUE;
}
}
}
return changed;
}
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, FALSE);
}
static void
schedule_activate_check (NMPolicy *self, NMDevice *device)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
ActivateData *data;
NMActiveConnection *ac;
const CList *tmp_list;
if (nm_manager_get_state (priv->manager) == NM_STATE_ASLEEP)
return;
if (!nm_device_autoconnect_allowed (device))
return;
if (find_pending_activation (self, device))
return;
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
if (nm_active_connection_get_device (ac) == 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);
c_list_link_tail (&priv->pending_activation_checks, &data->pending_lst);
}
static gboolean
reset_connections_retries (gpointer user_data)
{
NMPolicy *self = (NMPolicy *) user_data;
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
NMSettingsConnection *const*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 (priv->settings, NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *connection = connections[i];
con_stamp = nm_settings_connection_autoconnect_retries_blocked_until (connection);
if (con_stamp == 0)
continue;
if (con_stamp <= now) {
nm_settings_connection_autoconnect_retries_reset (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
_connection_autoconnect_retries_set (NMPolicy *self,
NMSettingsConnection *connection,
int 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);
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_s ()), reset_connections_retries, self);
}
}
}
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;
NMSettingsConnection *const*connections;
gboolean changed;
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);
}
changed = FALSE;
connections = nm_settings_get_connections (priv->settings, NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *connection = connections[i];
NMSettingConnection *s_slave_con;
const char *slave_master;
s_slave_con = nm_connection_get_setting_connection (NM_CONNECTION (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 (connection) == 0)
changed = TRUE;
nm_settings_connection_autoconnect_retries_reset (connection);
}
if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
FALSE)) {
if (!nm_settings_connection_autoconnect_is_blocked (connection))
changed = TRUE;
}
}
if (changed)
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,
nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (req)),
&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 (nm_device_state_reason_check (reason)) {
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
/* Block autoconnect of the just-failed connection for situations
* where a retry attempt would just fail again.
*/
if (connection) {
nm_settings_connection_autoconnect_blocked_reason_set (connection,
NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
TRUE);
}
break;
default:
break;
}
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) {
gboolean block_no_secrets = FALSE;
int tries;
guint64 con_v;
if (nm_device_state_reason_check (reason) == 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
* been consulted, and it may be able to provide the secrets.
*
* We detect this by using a version-id of the agent-manager, which increments
* whenever new agents register. Note that the agent-manager's version-id is
* never zero and strictly increasing.
*
* A connection's version-id of zero means that the connection never tried to request secrets.
* That can happen when nm_settings_connection_get_secrets() fails early without actually
* consulting any agents.
*/
con_v = nm_settings_connection_get_last_secret_agent_version_id (connection);
if ( con_v == 0
|| con_v == nm_agent_manager_get_agent_version_id (priv->agent_mgr))
block_no_secrets = TRUE;
}
if (block_no_secrets) {
_LOGD (LOGD_DEVICE, "connection '%s' now blocked from autoconnect due to no secrets",
nm_settings_connection_get_id (connection));
nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, TRUE);
} else {
tries = nm_settings_connection_autoconnect_retries_get (connection);
if (tries > 0) {
_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; %d tries left",
nm_settings_connection_get_id (connection), tries - 1);
_connection_autoconnect_retries_set (self, connection, tries - 1);
} else if (tries != 0) {
_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; infinite tries left",
nm_settings_connection_get_id (connection));
}
}
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_autoconnect_retries_reset (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_ip_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_ip_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 (connection) {
NMSettingsAutoconnectBlockedReason blocked_reason = NM_SETTINGS_AUTO_CONNECT_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;
break;
case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED;
break;
default:
break;
}
if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) {
_LOGD (LOGD_DEVICE, "blocking autoconnect of connection '%s': %s",
nm_settings_connection_get_id (connection),
NM_UTILS_LOOKUP_STR (nm_device_state_reason_to_str,
nm_device_state_reason_check (reason)));
nm_settings_connection_autoconnect_blocked_reason_set (connection, blocked_reason, TRUE);
}
}
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, FALSE);
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_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL, FALSE);
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_ip_config (priv->dns_manager, old_config);
if (new_config)
nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
}
update_ip_dns (self, AF_INET);
update_ip4_routing (self, TRUE);
update_system_hostname (self, "ip4 conf");
} else {
/* Old configs get removed immediately */
if (old_config)
nm_dns_manager_remove_ip_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_ip_config (priv->dns_manager, old_config);
if (new_config)
nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
}
update_ip_dns (self, AF_INET6);
update_ip6_routing (self, TRUE);
update_system_hostname (self, "ip6 conf");
} else {
/* Old configs get removed immediately */
if (old_config)
nm_dns_manager_remove_ip_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);
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);
ActivateData *data;
/* 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 */
data = find_pending_activation (self, device);
if (data && data->autoactivate_id)
activate_data_free (data);
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_ip_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_ip_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_ip_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_ip_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,
nm_active_connection_get_activation_reason (ac),
&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_autoconnect_retries_reset (connection);
}
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;
GError *error = NULL;
nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
NMActiveConnectionState state = nm_active_connection_get_state (ac);
if ( nm_active_connection_get_settings_connection (ac) == connection
&& (state <= 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);
}
}
}
}
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);
}
static void
connection_flags_changed (NMSettings *settings,
NMSettingsConnection *connection,
gpointer user_data)
{
NMPolicyPrivate *priv = user_data;
NMPolicy *self = _PRIV_TO_SELF (priv);
if (NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
NM_SETTINGS_CONNECTION_FLAGS_VISIBLE)) {
if (!nm_settings_connection_autoconnect_is_blocked (connection))
schedule_activate_all (self);
} else
_deactivate_if_active (self, connection);
}
static void
secret_agent_registered (NMSettings *settings,
NMSecretAgent *agent,
gpointer user_data)
{
NMPolicy *self = NM_POLICY (user_data);
/* 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.
*/
if (reset_autoconnect_all (self, NULL, TRUE))
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;
c_list_init (&priv->pending_activation_checks);
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->agent_mgr = g_object_ref (nm_agent_manager_get ());
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_FLAGS_CHANGED, (GCallback) connection_flags_changed, priv);
g_signal_connect (priv->agent_mgr, NM_AGENT_MANAGER_AGENT_REGISTERED, G_CALLBACK (secret_agent_registered), self);
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);
GHashTableIter h_iter;
NMDevice *device;
ActivateData *data, *data_safe;
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);
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;
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->agent_mgr) {
g_signal_handlers_disconnect_by_func (priv->agent_mgr, secret_agent_registered, self);
g_clear_object (&priv->agent_mgr);
}
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.
*/
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);
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);
}