mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-03-17 18:20:33 +01:00
We are currently asserting that the list of devices waiting for auto-activation in NMPolicy is not empty. This condition is always false because: - NMDevice holds a reference to NMManager - NMManager holds a reference to NMPolicy - on dispose, NMDevice asserts that it's not in NMPolicy's auto-activate list Therefore if there is any NMDevice alive, NMPolicy must be alive as well. Instead, if there is no NMDevice alive the list must be empty. The assertion could fail only when the NMPolicy instance gets disposed, which usually doesn't happen because it's still referenced at shutdown. Fixes:aede228974('core: assert that devices are not registered when disposing NMPolicy') (cherry picked from commit27b646cfa1) (cherry picked from commit1b51404703)
3248 lines
120 KiB
C
3248 lines
120 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2004 - 2013 Red Hat, Inc.
|
|
* Copyright (C) 2007 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include "src/core/nm-default-daemon.h"
|
|
|
|
#include "nm-policy.h"
|
|
|
|
#include <unistd.h>
|
|
#include <netdb.h>
|
|
|
|
#include "libnm-core-intern/nm-core-internal.h"
|
|
#include "libnm-platform/nm-platform.h"
|
|
#include "libnm-platform/nmp-object.h"
|
|
|
|
#include "NetworkManagerUtils.h"
|
|
#include "devices/nm-device.h"
|
|
#include "devices/nm-device-factory.h"
|
|
#include "dns/nm-dns-manager.h"
|
|
#include "nm-act-request.h"
|
|
#include "nm-auth-utils.h"
|
|
#include "nm-config.h"
|
|
#include "nm-dhcp-config.h"
|
|
#include "nm-dispatcher.h"
|
|
#include "nm-firewalld-manager.h"
|
|
#include "nm-hostname-manager.h"
|
|
#include "nm-keep-alive.h"
|
|
#include "nm-l3-config-data.h"
|
|
#include "nm-manager.h"
|
|
#include "nm-netns.h"
|
|
#include "nm-setting-connection.h"
|
|
#include "nm-setting-ip4-config.h"
|
|
#include "nm-utils.h"
|
|
#include "settings/nm-agent-manager.h"
|
|
#include "settings/nm-settings-connection.h"
|
|
#include "settings/nm-settings.h"
|
|
#include "vpn/nm-vpn-manager.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE(NMPolicy,
|
|
PROP_MANAGER,
|
|
PROP_SETTINGS,
|
|
PROP_DEFAULT_IP4_AC,
|
|
PROP_DEFAULT_IP6_AC,
|
|
PROP_ACTIVATING_IP4_AC,
|
|
PROP_ACTIVATING_IP6_AC, );
|
|
|
|
#define HOSTNAME_RETRY_INTERVAL_MIN 30U
|
|
#define HOSTNAME_RETRY_INTERVAL_MAX (60U * 60 * 12) /* 12 hours */
|
|
#define HOSTNAME_RETRY_INTERVAL_MULTIPLIER 8U
|
|
|
|
typedef struct {
|
|
NMManager *manager;
|
|
NMNetns *netns;
|
|
NMFirewalldManager *firewalld_manager;
|
|
CList policy_auto_activate_lst_head;
|
|
|
|
NMAgentManager *agent_mgr;
|
|
|
|
GHashTable *devices;
|
|
GHashTable *pending_active_connections;
|
|
|
|
GSList *pending_secondaries;
|
|
|
|
NMSettings *settings;
|
|
|
|
GSource *device_recheck_auto_activate_all_idle_source;
|
|
|
|
GSource *reset_connections_retries_idle_source;
|
|
|
|
NMHostnameManager *hostname_manager;
|
|
|
|
NMActiveConnection *default_ac4, *activating_ac4;
|
|
NMActiveConnection *default_ac6, *activating_ac6;
|
|
|
|
NMDnsManager *dns_manager;
|
|
gulong config_changed_id;
|
|
|
|
NMPolicyHostnameMode hostname_mode;
|
|
char *orig_hostname; /* hostname at NM start time */
|
|
char *cur_hostname; /* hostname we want to assign */
|
|
char *cur_hostname_full; /* similar to @last_hostname, but before shortening */
|
|
char *last_hostname; /* last hostname NM set (to detect if someone else
|
|
* changed it in the meanwhile) */
|
|
struct {
|
|
GSource *source;
|
|
guint interval_sec;
|
|
gboolean do_restart; /* when something changes, set this to TRUE so that the next retry
|
|
* will restart from the lowest timeout. */
|
|
} hostname_retry;
|
|
|
|
bool changing_hostname : 1; /* hostname set operation in progress */
|
|
bool dhcp_hostname : 1; /* current hostname was set from dhcp */
|
|
bool updating_dns : 1;
|
|
|
|
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 = NM_CAST_ALIGN(NMPolicy, (((char *) priv) - G_STRUCT_OFFSET(NMPolicy, _priv)));
|
|
|
|
nm_assert(NM_IS_POLICY(self));
|
|
|
|
return self;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define _NMLOG_PREFIX_NAME "policy"
|
|
#undef _NMLOG_ENABLED
|
|
#define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain)))
|
|
#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 update_system_hostname(NMPolicy *self, const char *msg, gboolean reset_retry_interval);
|
|
static void nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self);
|
|
static NMDevice *get_default_device(NMPolicy *self, int addr_family);
|
|
static gboolean hostname_retry_cb(gpointer user_data);
|
|
|
|
/*****************************************************************************/
|
|
|
|
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;
|
|
char sbuf[NM_INET_ADDRSTRLEN];
|
|
|
|
_LOGD(LOGD_IP6,
|
|
"ipv6-pd: undelegating prefix %s/%d",
|
|
nm_inet6_ntop(&delegation->prefix.address, sbuf),
|
|
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_sec();
|
|
IP6PrefixDelegation *delegation = NULL;
|
|
guint i;
|
|
|
|
for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
|
|
delegation = &nm_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);
|
|
char sbuf[NM_INET_ADDRSTRLEN];
|
|
|
|
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_inet6_ntop(&delegation->prefix.address, sbuf),
|
|
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_inet6_ntop(&subnet->address, sbuf),
|
|
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 = &nm_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 = &nm_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,
|
|
const 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;
|
|
char sbuf[NM_INET_ADDRSTRLEN];
|
|
|
|
_LOGI(LOGD_IP6,
|
|
"ipv6-pd: received a prefix %s/%d from %s",
|
|
nm_inet6_ntop(&prefix->address, sbuf),
|
|
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 = &nm_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 for new prefix. */
|
|
delegation = nm_g_array_append_new(priv->ip6_prefix_delegations, IP6PrefixDelegation);
|
|
delegation->subnets = g_hash_table_new(nm_direct_hash, NULL);
|
|
delegation->next_subnet = 0;
|
|
}
|
|
|
|
delegation->device = device;
|
|
delegation->prefix = *prefix;
|
|
|
|
/* The newly activated connections are added to the end of the list,
|
|
* so traversing it from the end 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_prev (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_ac6) {
|
|
/* 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, get_default_device(self, AF_INET6), device);
|
|
nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static NMDevice *
|
|
get_default_device(NMPolicy *self, int addr_family)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMActiveConnection *ac;
|
|
|
|
nm_assert_addr_family(addr_family);
|
|
|
|
ac = (addr_family == AF_INET) ? priv->default_ac4 : priv->default_ac6;
|
|
|
|
return ac ? nm_active_connection_get_device(ac) : NULL;
|
|
}
|
|
|
|
static NMActiveConnection *
|
|
get_best_active_connection(NMPolicy *self, int addr_family, gboolean fully_activated)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
guint32 best_metric = G_MAXUINT32;
|
|
gboolean best_is_fully_activated = FALSE;
|
|
NMActiveConnection *best_ac, *prev_ac;
|
|
|
|
nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
|
|
|
|
/* we prefer the current AC in case of identical metric.
|
|
* Hence, try that one first. */
|
|
prev_ac = addr_family == AF_INET ? (fully_activated ? priv->default_ac4 : priv->activating_ac4)
|
|
: (fully_activated ? priv->default_ac6 : priv->activating_ac6);
|
|
best_ac = NULL;
|
|
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
NMDeviceState state;
|
|
const NMPObject *r;
|
|
NMActiveConnection *ac;
|
|
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) {
|
|
/* NOTE: 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 = 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_device_get_route_metric(device, addr_family);
|
|
is_fully_activated = FALSE;
|
|
} else
|
|
continue;
|
|
|
|
ac = (NMActiveConnection *) nm_device_get_act_request(device);
|
|
nm_assert(ac);
|
|
|
|
if (!best_ac || (!best_is_fully_activated && is_fully_activated)
|
|
|| (metric < best_metric || (metric == best_metric && ac == prev_ac))) {
|
|
best_ac = ac;
|
|
best_metric = metric;
|
|
best_is_fully_activated = is_fully_activated;
|
|
}
|
|
}
|
|
|
|
if (!fully_activated && best_ac && best_is_fully_activated) {
|
|
/* There's a best activating AC only if the best device
|
|
* among all activating and already-activated devices is a
|
|
* still-activating one. */
|
|
return NULL;
|
|
}
|
|
|
|
return best_ac;
|
|
}
|
|
|
|
static gboolean
|
|
any_devices_active(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
NMDeviceState state;
|
|
|
|
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;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#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;
|
|
int errsv;
|
|
|
|
if (!result) {
|
|
_LOGT(LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\"");
|
|
ret = sethostname(hostname, strlen(hostname));
|
|
if (ret != 0) {
|
|
errsv = errno;
|
|
_LOGW(LOGD_DNS,
|
|
"set-hostname: couldn't set the system hostname to '%s': (%d) %s",
|
|
hostname,
|
|
errsv,
|
|
nm_strerror_native(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;
|
|
int errsv;
|
|
|
|
/* 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) {
|
|
errsv = errno;
|
|
_LOGT(LOGD_DNS,
|
|
"get-hostname: couldn't get the system hostname: (%d) %s",
|
|
errsv,
|
|
nm_strerror_native(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
|
|
hostname_retry_schedule(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
if (priv->hostname_retry.source && !priv->hostname_retry.do_restart)
|
|
return;
|
|
|
|
nm_clear_g_source_inst(&priv->hostname_retry.source);
|
|
|
|
if (priv->hostname_retry.do_restart)
|
|
priv->hostname_retry.interval_sec = 0;
|
|
|
|
priv->hostname_retry.interval_sec *= HOSTNAME_RETRY_INTERVAL_MULTIPLIER;
|
|
priv->hostname_retry.interval_sec = NM_CLAMP(priv->hostname_retry.interval_sec,
|
|
HOSTNAME_RETRY_INTERVAL_MIN,
|
|
HOSTNAME_RETRY_INTERVAL_MAX);
|
|
|
|
_LOGT(LOGD_DNS,
|
|
"hostname-retry: schedule in %u seconds%s",
|
|
priv->hostname_retry.interval_sec,
|
|
priv->hostname_retry.do_restart ? " (restarted)" : "");
|
|
priv->hostname_retry.source =
|
|
nm_g_timeout_add_seconds_source(priv->hostname_retry.interval_sec, hostname_retry_cb, self);
|
|
|
|
priv->hostname_retry.do_restart = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
hostname_retry_cb(gpointer user_data)
|
|
{
|
|
NMPolicy *self = NM_POLICY(user_data);
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
|
|
_LOGT(LOGD_DNS, "hostname-retry: timeout");
|
|
|
|
nm_clear_g_source_inst(&priv->hostname_retry.source);
|
|
|
|
/* Clear any cached DNS results before retrying */
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
nm_device_clear_dns_lookup_data(device, "hostname retry timeout");
|
|
}
|
|
update_system_hostname(self, "hostname retry timeout", FALSE);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
_set_hostname(NMPolicy *self, const char *new_hostname, const char *msg, gboolean do_retry)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
gs_free char *old_hostname = NULL;
|
|
gboolean cur_hostname_full_changed;
|
|
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.
|
|
*/
|
|
|
|
if (nm_strdup_reset(&priv->cur_hostname_full, new_hostname)) {
|
|
gs_free char *shortened = NULL;
|
|
|
|
cur_hostname_full_changed = TRUE;
|
|
|
|
if (priv->cur_hostname_full
|
|
&& !nm_utils_shorten_hostname(priv->cur_hostname_full, &shortened)) {
|
|
_LOGW(LOGD_DNS,
|
|
"set-hostname: hostname '%s' %s is invalid",
|
|
priv->cur_hostname_full,
|
|
msg);
|
|
return;
|
|
}
|
|
|
|
if (shortened) {
|
|
_LOGI(LOGD_DNS,
|
|
"set-hostname: shortened hostname %s from '%s' to '%s'",
|
|
msg,
|
|
priv->cur_hostname_full,
|
|
shortened);
|
|
nm_strdup_reset_take(&priv->cur_hostname, g_steal_pointer(&shortened));
|
|
} else
|
|
nm_strdup_reset(&priv->cur_hostname, priv->cur_hostname_full);
|
|
} else
|
|
cur_hostname_full_changed = FALSE;
|
|
|
|
/* Update the DNS only if the hostname is actually
|
|
* going to change.
|
|
*/
|
|
if (cur_hostname_full_changed) {
|
|
/* Notify the DNS manager of the hostname change so that the domain part, if
|
|
* present, can be added to the search list. Set the @updating_dns flag
|
|
* so that dns_config_changed() doesn't try again to restart DNS lookup.
|
|
*/
|
|
priv->updating_dns = TRUE;
|
|
nm_dns_manager_set_hostname(priv->dns_manager,
|
|
priv->cur_hostname_full,
|
|
!any_devices_active(self));
|
|
priv->updating_dns = FALSE;
|
|
}
|
|
|
|
if (!do_retry) {
|
|
_LOGT(LOGD_DNS, "hostname-retry: clear");
|
|
nm_clear_g_source_inst(&priv->hostname_retry.source);
|
|
priv->hostname_retry.interval_sec = 0;
|
|
priv->hostname_retry.do_restart = FALSE;
|
|
} else if (!priv->hostname_retry.source) {
|
|
hostname_retry_schedule(self);
|
|
}
|
|
|
|
/* Finally, set kernel hostname */
|
|
nm_assert(!priv->cur_hostname || priv->cur_hostname[0]);
|
|
name = priv->cur_hostname ?: FALLBACK_HOSTNAME4;
|
|
|
|
/* 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 */
|
|
nm_strdup_reset(&priv->last_hostname, 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.
|
|
*
|
|
* FIXME(shutdown): an async request must be cancellable, so we are guaranteed
|
|
* to tear down in a reasonable time.*/
|
|
nm_hostname_manager_set_transient_hostname(priv->hostname_manager,
|
|
name,
|
|
settings_set_hostname_cb,
|
|
g_object_ref(self));
|
|
}
|
|
|
|
typedef struct {
|
|
NMDevice *device;
|
|
int priority;
|
|
bool from_dhcp : 1;
|
|
bool from_dns : 1;
|
|
bool IS_IPv4 : 1;
|
|
bool is_default : 1;
|
|
} DeviceHostnameInfo;
|
|
|
|
static int
|
|
device_hostname_info_compare(gconstpointer a, gconstpointer b)
|
|
{
|
|
const DeviceHostnameInfo *info1 = a;
|
|
const DeviceHostnameInfo *info2 = b;
|
|
|
|
NM_CMP_FIELD(info1, info2, priority);
|
|
NM_CMP_FIELD_UNSAFE(info2, info1, is_default);
|
|
NM_CMP_FIELD_UNSAFE(info2, info1, IS_IPv4);
|
|
|
|
return 0;
|
|
}
|
|
|
|
NM_CON_DEFAULT_NOP("hostname.from-dhcp");
|
|
NM_CON_DEFAULT_NOP("hostname.from-dns-lookup");
|
|
NM_CON_DEFAULT_NOP("hostname.only-from-default");
|
|
|
|
static gboolean
|
|
device_get_hostname_property_boolean(NMDevice *device, const char *name)
|
|
{
|
|
NMSettingHostname *s_hostname;
|
|
char buf[128];
|
|
int value;
|
|
NMTernary default_value;
|
|
|
|
nm_assert(NM_IN_STRSET(name,
|
|
NM_SETTING_HOSTNAME_FROM_DHCP,
|
|
NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP,
|
|
NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT));
|
|
|
|
s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
|
|
|
|
if (s_hostname) {
|
|
g_object_get(s_hostname, name, &value, NULL);
|
|
if (NM_IN_SET(value, NM_TERNARY_FALSE, NM_TERNARY_TRUE))
|
|
return value;
|
|
}
|
|
|
|
if (nm_streq(name, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT))
|
|
default_value = NM_TERNARY_FALSE;
|
|
else
|
|
default_value = NM_TERNARY_TRUE;
|
|
|
|
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
|
|
nm_sprintf_buf(buf, "hostname.%s", name),
|
|
device,
|
|
NM_TERNARY_FALSE,
|
|
NM_TERNARY_TRUE,
|
|
default_value);
|
|
}
|
|
|
|
static int
|
|
device_get_hostname_priority(NMDevice *device)
|
|
{
|
|
NMSettingHostname *s_hostname;
|
|
int priority;
|
|
|
|
s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
|
|
if (s_hostname) {
|
|
priority = nm_setting_hostname_get_priority(s_hostname);
|
|
if (priority != 0)
|
|
return priority;
|
|
}
|
|
|
|
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
|
|
NM_CON_DEFAULT("hostname.priority"),
|
|
device,
|
|
G_MININT,
|
|
G_MAXINT,
|
|
100);
|
|
}
|
|
|
|
static GArray *
|
|
build_device_hostname_infos(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_clist;
|
|
NMActiveConnection *ac;
|
|
GArray *array = NULL;
|
|
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_clist) {
|
|
DeviceHostnameInfo *info;
|
|
NMDevice *device;
|
|
gboolean only_from_default;
|
|
gboolean is_default;
|
|
int IS_IPv4;
|
|
|
|
device = nm_active_connection_get_device(ac);
|
|
if (!device)
|
|
continue;
|
|
|
|
if (nm_device_sys_iface_state_is_external(device))
|
|
continue;
|
|
|
|
only_from_default =
|
|
device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT);
|
|
|
|
for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
|
|
is_default = (ac == (IS_IPv4 ? priv->default_ac4 : priv->default_ac6));
|
|
if (only_from_default && !is_default)
|
|
continue;
|
|
|
|
if (!array)
|
|
array = g_array_sized_new(FALSE, FALSE, sizeof(DeviceHostnameInfo), 4);
|
|
|
|
info = nm_g_array_append_new(array, DeviceHostnameInfo);
|
|
*info = (DeviceHostnameInfo){
|
|
.device = device,
|
|
.priority = device_get_hostname_priority(device),
|
|
.from_dhcp =
|
|
device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_FROM_DHCP),
|
|
.from_dns =
|
|
device_get_hostname_property_boolean(device,
|
|
NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP),
|
|
.IS_IPv4 = IS_IPv4,
|
|
.is_default = is_default,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (array && array->len > 1) {
|
|
const DeviceHostnameInfo *info0;
|
|
guint i;
|
|
|
|
g_array_sort(array, device_hostname_info_compare);
|
|
|
|
info0 = &nm_g_array_first(array, DeviceHostnameInfo);
|
|
if (info0->priority < 0) {
|
|
for (i = 1; i < array->len; i++) {
|
|
const DeviceHostnameInfo *info = &nm_g_array_index(array, DeviceHostnameInfo, i);
|
|
|
|
if (info->priority > info0->priority) {
|
|
g_array_set_size(array, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
static void
|
|
device_dns_lookup_done(NMDevice *device, gpointer user_data)
|
|
{
|
|
NMPolicy *self = user_data;
|
|
|
|
g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
|
|
|
|
update_system_hostname(self, "lookup finished", FALSE);
|
|
}
|
|
|
|
static void
|
|
device_carrier_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
gs_free char *msg = NULL;
|
|
|
|
if (nm_device_has_carrier(device)) {
|
|
g_signal_handlers_disconnect_by_func(device, device_carrier_changed, priv);
|
|
msg = g_strdup_printf("device '%s' got carrier", nm_device_get_iface(device));
|
|
update_system_hostname(self, msg, TRUE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function evaluates different sources (static configuration, DHCP, DNS, ...)
|
|
* to set the system hostname.
|
|
*
|
|
* When the function needs to perform a blocking action like a DNS resolution, it
|
|
* subscribes to a signal for the completion event, registering a callback that
|
|
* invokes this function again. In the new invocation, any previous DNS result is
|
|
* cached and doesn't need a new resolution.
|
|
*
|
|
* In case no hostname is found when after sources have been evaluated, it schedules
|
|
* a timer to retry later with an interval that is increased at each attempt. When
|
|
* this function is called after something changed (for example, carrier went up, a
|
|
* new address was added), @reset_retry_interval should be set to TRUE so that the
|
|
* next retry will use the smallest interval. In this way, it can quickly adapt to
|
|
* temporary misconfigurations at boot or when the network environment changes.
|
|
*/
|
|
static void
|
|
update_system_hostname(NMPolicy *self, const char *msg, gboolean reset_retry_interval)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const char *configured_hostname;
|
|
gs_free char *temp_hostname = NULL;
|
|
const char *dhcp_hostname, *p;
|
|
gboolean external_hostname = FALSE;
|
|
NMDhcpConfig *dhcp_config;
|
|
gs_unref_array GArray *infos = NULL;
|
|
DeviceHostnameInfo *info;
|
|
guint i;
|
|
int addr_family;
|
|
|
|
g_return_if_fail(self != NULL);
|
|
|
|
if (reset_retry_interval)
|
|
priv->hostname_retry.do_restart = TRUE;
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
_LOGD(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 DHCP of eligible interfaces
|
|
* 3) reverse-DNS lookup of the first address on eligible interfaces
|
|
* 4) the last hostname set outside NM
|
|
*/
|
|
|
|
/* Try a persistent hostname first */
|
|
configured_hostname = nm_hostname_manager_get_static_hostname(priv->hostname_manager);
|
|
if (configured_hostname && nm_utils_is_specific_hostname(configured_hostname)) {
|
|
_set_hostname(self, configured_hostname, "from system configuration", FALSE);
|
|
priv->dhcp_hostname = FALSE;
|
|
return;
|
|
}
|
|
|
|
infos = build_device_hostname_infos(self);
|
|
|
|
if (infos && _LOGT_ENABLED(LOGD_DNS)) {
|
|
_LOGT(LOGD_DNS, "device hostname info:");
|
|
for (i = 0; i < infos->len; i++) {
|
|
info = &nm_g_array_index(infos, DeviceHostnameInfo, i);
|
|
_LOGT(LOGD_DNS,
|
|
" - prio:%5d ipv%c%s %s %s dev:%s",
|
|
info->priority,
|
|
info->IS_IPv4 ? '4' : '6',
|
|
info->is_default ? " (def)" : " ",
|
|
info->from_dhcp ? "dhcp " : " ",
|
|
info->from_dns ? "dns " : " ",
|
|
nm_device_get_iface(info->device));
|
|
}
|
|
}
|
|
|
|
for (i = 0; infos && i < infos->len; i++) {
|
|
info = &nm_g_array_index(infos, DeviceHostnameInfo, i);
|
|
addr_family = info->IS_IPv4 ? AF_INET : AF_INET6;
|
|
g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self);
|
|
g_signal_handlers_disconnect_by_func(info->device, device_carrier_changed, priv);
|
|
|
|
if (info->from_dhcp) {
|
|
dhcp_config = nm_device_get_dhcp_config(info->device, addr_family);
|
|
if (dhcp_config) {
|
|
dhcp_hostname =
|
|
nm_dhcp_config_get_option(dhcp_config,
|
|
info->IS_IPv4 ? "host_name" : "fqdn_fqdn");
|
|
if (dhcp_hostname && dhcp_hostname[0]) {
|
|
p = nm_str_skip_leading_spaces(dhcp_hostname);
|
|
if (p[0]) {
|
|
_set_hostname(self,
|
|
p,
|
|
info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6",
|
|
FALSE);
|
|
priv->dhcp_hostname = TRUE;
|
|
return;
|
|
}
|
|
_LOGW(LOGD_DNS,
|
|
"set-hostname: DHCPv%c-provided hostname '%s' looks invalid; "
|
|
"ignoring it",
|
|
nm_utils_addr_family_to_char(addr_family),
|
|
dhcp_hostname);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (priv->hostname_mode != NM_POLICY_HOSTNAME_MODE_DHCP) {
|
|
if (info->from_dns) {
|
|
const char *result = NULL;
|
|
gboolean wait = FALSE;
|
|
|
|
if (nm_device_has_carrier(info->device)) {
|
|
result =
|
|
nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
|
|
} else {
|
|
g_signal_connect(info->device,
|
|
"notify::" NM_DEVICE_CARRIER,
|
|
G_CALLBACK(device_carrier_changed),
|
|
priv);
|
|
}
|
|
if (result) {
|
|
_set_hostname(self, result, "from address lookup", FALSE);
|
|
return;
|
|
}
|
|
if (wait) {
|
|
g_signal_connect(info->device,
|
|
NM_DEVICE_DNS_LOOKUP_DONE,
|
|
G_CALLBACK(device_dns_lookup_done),
|
|
self);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If an hostname was set outside NetworkManager keep it */
|
|
if (external_hostname) {
|
|
hostname_retry_schedule(self);
|
|
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", TRUE);
|
|
priv->dhcp_hostname = FALSE;
|
|
}
|
|
return;
|
|
}
|
|
|
|
priv->dhcp_hostname = FALSE;
|
|
|
|
/* 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", TRUE);
|
|
return;
|
|
}
|
|
|
|
_set_hostname(self, NULL, "no hostname found", TRUE);
|
|
}
|
|
|
|
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 const NML3ConfigData *
|
|
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);
|
|
const NML3ConfigData *l3cd_best = NULL;
|
|
const CList *tmp_list;
|
|
NMActiveConnection *ac;
|
|
guint64 best_metric = G_MAXUINT64;
|
|
NMVpnConnection *best_vpn = NULL;
|
|
|
|
nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
|
|
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
|
|
const NML3ConfigData *l3cd;
|
|
NMVpnConnection *candidate;
|
|
NMVpnConnectionState vpn_state;
|
|
const NMPObject *obj;
|
|
guint32 metric;
|
|
|
|
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;
|
|
|
|
l3cd = nm_vpn_connection_get_l3cd(candidate);
|
|
if (!l3cd)
|
|
continue;
|
|
|
|
obj = nm_l3_config_data_get_best_default_route(l3cd, addr_family);
|
|
if (!obj)
|
|
continue;
|
|
|
|
metric = NMP_OBJECT_CAST_IPX_ROUTE(obj)->rx.metric;
|
|
if (metric <= best_metric) {
|
|
best_metric = metric;
|
|
l3cd_best = l3cd;
|
|
best_vpn = candidate;
|
|
}
|
|
}
|
|
|
|
if (best_metric != G_MAXUINT64) {
|
|
NM_SET_OUT(out_device, NULL);
|
|
NM_SET_OUT(out_vpn, best_vpn);
|
|
NM_SET_OUT(out_ac, NM_ACTIVE_CONNECTION(best_vpn));
|
|
NM_SET_OUT(out_ip_iface, nm_vpn_connection_get_ip_iface(best_vpn, TRUE));
|
|
return l3cd_best;
|
|
}
|
|
|
|
ac = get_best_active_connection(self, addr_family, TRUE);
|
|
if (ac) {
|
|
NMDevice *device = nm_active_connection_get_device(ac);
|
|
|
|
nm_assert(NM_IS_DEVICE(device));
|
|
|
|
NM_SET_OUT(out_device, device);
|
|
NM_SET_OUT(out_vpn, NULL);
|
|
NM_SET_OUT(out_ac, ac);
|
|
NM_SET_OUT(out_ip_iface, nm_device_get_ip_iface(device));
|
|
return nm_device_get_l3cd(device, TRUE);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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_ac4)) {
|
|
_LOGt(LOGD_DNS, "set-default-ac-4: %p", NULL);
|
|
_notify(self, PROP_DEFAULT_IP4_AC);
|
|
}
|
|
return;
|
|
}
|
|
g_assert((best || vpn) && best_ac);
|
|
|
|
if (!force_update && best_ac && best_ac == priv->default_ac4)
|
|
return;
|
|
|
|
if (best) {
|
|
const CList *tmp_list;
|
|
NMActiveConnection *ac;
|
|
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
|
|
if (NM_IS_VPN_CONNECTION(ac) && !nm_active_connection_get_device(ac))
|
|
nm_active_connection_set_device(ac, best);
|
|
}
|
|
}
|
|
|
|
update_default_ac(self, AF_INET, best_ac);
|
|
|
|
if (!nm_g_object_ref_set(&priv->default_ac4, best_ac))
|
|
return;
|
|
_LOGt(LOGD_DNS, "set-default-ac-4: %p", priv->default_ac4);
|
|
|
|
_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_AC);
|
|
}
|
|
|
|
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, get_default_device(self, AF_INET6), 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_ac6)) {
|
|
_LOGt(LOGD_DNS, "set-default-ac-6: %p", NULL);
|
|
_notify(self, PROP_DEFAULT_IP6_AC);
|
|
}
|
|
return;
|
|
}
|
|
g_assert((best || vpn) && best_ac);
|
|
|
|
if (!force_update && best_ac && best_ac == priv->default_ac6)
|
|
return;
|
|
|
|
if (best) {
|
|
const CList *tmp_list;
|
|
NMActiveConnection *ac;
|
|
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
|
|
if (NM_IS_VPN_CONNECTION(ac) && !nm_active_connection_get_device(ac))
|
|
nm_active_connection_set_device(ac, best);
|
|
}
|
|
}
|
|
|
|
update_default_ac(self, AF_INET6, best_ac);
|
|
|
|
if (!nm_g_object_ref_set(&priv->default_ac6, best_ac))
|
|
return;
|
|
_LOGt(LOGD_DNS, "set-default-ac-6: %p", priv->default_ac6);
|
|
|
|
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_AC);
|
|
}
|
|
|
|
static void
|
|
update_ip_dns(NMPolicy *self, int addr_family, NMDevice *changed_device)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const NML3ConfigData *l3cd;
|
|
const char *ip_iface = NULL;
|
|
NMVpnConnection *vpn = NULL;
|
|
NMDevice *device = NULL;
|
|
|
|
nm_assert_addr_family(addr_family);
|
|
|
|
l3cd = get_best_ip_config(self, addr_family, &ip_iface, NULL, &device, &vpn);
|
|
if (l3cd) {
|
|
NMDnsIPConfigType ip_config_type;
|
|
|
|
nm_assert(!device || NM_IS_DEVICE(device));
|
|
nm_assert(!vpn || NM_IS_VPN_CONNECTION(vpn));
|
|
nm_assert((!!device) != (!!vpn));
|
|
|
|
/* Tell the DNS manager this config is preferred by re-adding it with
|
|
* a different IP config type.
|
|
*/
|
|
if (device && nm_device_sys_iface_state_is_external(device))
|
|
ip_config_type = NM_DNS_IP_CONFIG_TYPE_REMOVED;
|
|
else if (vpn || (device && nm_device_is_vpn(device)))
|
|
ip_config_type = NM_DNS_IP_CONFIG_TYPE_VPN;
|
|
else
|
|
ip_config_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
|
|
|
|
nm_dns_manager_set_ip_config(NM_POLICY_GET_PRIVATE(self)->dns_manager,
|
|
addr_family,
|
|
((gconstpointer) device) ?: ((gconstpointer) vpn),
|
|
l3cd,
|
|
ip_config_type,
|
|
TRUE);
|
|
}
|
|
|
|
if (addr_family == AF_INET6) {
|
|
NMActiveConnection *ac;
|
|
const CList *tmp_list;
|
|
|
|
/* Tell devices needing a subnet about the new DNS configuration */
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
|
|
device = nm_active_connection_get_device(ac);
|
|
if (device && device != changed_device && nm_device_needs_ip6_subnet(device))
|
|
nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_routing_and_dns(NMPolicy *self, gboolean force_update, NMDevice *changed_device)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
nm_dns_manager_begin_updates(priv->dns_manager, __func__);
|
|
|
|
update_ip_dns(self, AF_INET, changed_device);
|
|
update_ip_dns(self, AF_INET6, changed_device);
|
|
|
|
update_ip4_routing(self, force_update);
|
|
update_ip6_routing(self, force_update);
|
|
|
|
/* Update the system hostname */
|
|
update_system_hostname(self, "routing and dns", FALSE);
|
|
|
|
nm_dns_manager_end_updates(priv->dns_manager, __func__);
|
|
}
|
|
|
|
static void
|
|
check_activating_active_connections(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMActiveConnection *best4, *best6 = NULL;
|
|
|
|
best4 = get_best_active_connection(self, AF_INET, FALSE);
|
|
best6 = get_best_active_connection(self, AF_INET6, FALSE);
|
|
|
|
g_object_freeze_notify(G_OBJECT(self));
|
|
|
|
if (nm_g_object_ref_set(&priv->activating_ac4, best4)) {
|
|
_LOGt(LOGD_DNS, "set-activating-ac-4: %p", priv->activating_ac4);
|
|
_notify(self, PROP_ACTIVATING_IP4_AC);
|
|
}
|
|
if (nm_g_object_ref_set(&priv->activating_ac6, best6)) {
|
|
_LOGt(LOGD_DNS, "set-activating-ac-6: %p", priv->activating_ac6);
|
|
_notify(self, PROP_ACTIVATING_IP6_AC);
|
|
}
|
|
|
|
g_object_thaw_notify(G_OBJECT(self));
|
|
}
|
|
|
|
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
|
|
&& reason != NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED) {
|
|
con = nm_active_connection_get_settings_connection(ac);
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
nm_active_connection_get_device(ac),
|
|
con,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
TRUE);
|
|
nm_policy_device_recheck_auto_activate_schedule(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;
|
|
gs_unref_object NMAuthSubject *subject = NULL;
|
|
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)) {
|
|
if (nm_device_sys_iface_state_is_external(device)
|
|
&& nm_device_get_allow_autoconnect_on_external(device)) {
|
|
/* this is an external activation, and we allow autoconnecting on
|
|
* top of that.
|
|
*
|
|
* pass. */
|
|
} else
|
|
return;
|
|
}
|
|
|
|
if (!nm_device_autoconnect_allowed(device))
|
|
return;
|
|
|
|
connections = nm_manager_get_activatable_connections(priv->manager, TRUE, TRUE, &len);
|
|
if (!connections[0])
|
|
return;
|
|
|
|
/* Find the first connection that should be auto-activated */
|
|
best_connection = NULL;
|
|
for (i = 0; i < len; i++) {
|
|
NMSettingsConnection *candidate = connections[i];
|
|
NMConnection *cand_conn;
|
|
NMSettingConnection *s_con;
|
|
const char *permission;
|
|
|
|
if (nm_manager_devcon_autoconnect_is_blocked(priv->manager, device, candidate))
|
|
continue;
|
|
|
|
cand_conn = nm_settings_connection_get_connection(candidate);
|
|
|
|
s_con = nm_connection_get_setting_connection(cand_conn);
|
|
if (!nm_setting_connection_get_autoconnect(s_con))
|
|
continue;
|
|
|
|
permission = nm_utils_get_shared_wifi_permission(cand_conn);
|
|
if (permission && !nm_settings_connection_check_permission(candidate, permission))
|
|
continue;
|
|
|
|
if (nm_device_can_auto_connect(device, candidate, &specific_object)) {
|
|
best_connection = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!best_connection)
|
|
return;
|
|
|
|
_LOGI(LOGD_DEVICE,
|
|
"auto-activating connection '%s' (%s)",
|
|
nm_settings_connection_get_id(best_connection),
|
|
nm_settings_connection_get_uuid(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,
|
|
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
|
|
&error);
|
|
if (!ac) {
|
|
_LOGI(LOGD_DEVICE,
|
|
"connection '%s' auto-activation failed: %s",
|
|
nm_settings_connection_get_id(best_connection),
|
|
error->message);
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
device,
|
|
best_connection,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
TRUE);
|
|
nm_policy_device_recheck_auto_activate_schedule(self, device);
|
|
return;
|
|
}
|
|
|
|
/* Subscribe to AC state-changed signal to detect when the
|
|
* activation fails in early stages without changing device
|
|
* state.
|
|
*/
|
|
if (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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_auto_activate_device_clear(NMPolicy *self, NMDevice *device, gboolean do_activate)
|
|
{
|
|
nm_assert(NM_IS_DEVICE(device));
|
|
nm_assert(NM_IS_POLICY(self));
|
|
nm_assert(c_list_is_linked(&device->policy_auto_activate_lst));
|
|
nm_assert(c_list_contains(&NM_POLICY_GET_PRIVATE(self)->policy_auto_activate_lst_head,
|
|
&device->policy_auto_activate_lst));
|
|
|
|
c_list_unlink(&device->policy_auto_activate_lst);
|
|
nm_clear_g_source_inst(&device->policy_auto_activate_idle_source);
|
|
|
|
if (do_activate)
|
|
_auto_activate_device(self, device);
|
|
|
|
nm_device_remove_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
_auto_activate_idle_cb(gpointer user_data)
|
|
{
|
|
NMDevice *device = user_data;
|
|
|
|
nm_assert(NM_IS_DEVICE(device));
|
|
|
|
_auto_activate_device_clear(nm_manager_get_policy(nm_device_get_manager(device)), device, TRUE);
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_dbus_object_get_path(NM_DBUS_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_dbus_object_get_path(NM_DBUS_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
|
|
_static_hostname_changed_cb(NMHostnameManager *hostname_manager,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
|
|
update_system_hostname(self, "hostname changed", FALSE);
|
|
}
|
|
|
|
void
|
|
nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMSettingsConnection *const *connections = NULL;
|
|
guint i;
|
|
|
|
_LOGT(LOGD_DEVICE, "unblocking failed OVS interfaces");
|
|
|
|
connections = nm_settings_get_connections(priv->settings, NULL);
|
|
for (i = 0; connections[i]; i++) {
|
|
NMSettingsConnection *sett_conn = connections[i];
|
|
NMConnection *connection = nm_settings_connection_get_connection(sett_conn);
|
|
|
|
if (nm_connection_get_setting_ovs_interface(connection)) {
|
|
nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn);
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
NULL,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 *sett_conn = connections[i];
|
|
|
|
if (device
|
|
&& !nm_device_check_connection_compatible(
|
|
device,
|
|
nm_settings_connection_get_connection(sett_conn),
|
|
TRUE,
|
|
NULL))
|
|
continue;
|
|
|
|
if (nm_manager_devcon_autoconnect_reset_reconnect_all(priv->manager,
|
|
device,
|
|
sett_conn,
|
|
only_no_secrets))
|
|
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);
|
|
}
|
|
|
|
void
|
|
nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device)
|
|
{
|
|
NMPolicyPrivate *priv;
|
|
NMActiveConnection *ac;
|
|
const CList *tmp_list;
|
|
|
|
g_return_if_fail(NM_IS_POLICY(self));
|
|
g_return_if_fail(NM_IS_DEVICE(device));
|
|
nm_assert(g_signal_handler_find(device,
|
|
G_SIGNAL_MATCH_DATA,
|
|
0,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NM_POLICY_GET_PRIVATE(self))
|
|
!= 0);
|
|
|
|
if (!c_list_is_empty(&device->policy_auto_activate_lst)) {
|
|
/* already queued. Return. */
|
|
return;
|
|
}
|
|
|
|
priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP)
|
|
return;
|
|
|
|
if (!nm_device_autoconnect_allowed(device))
|
|
return;
|
|
|
|
nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
|
|
if (nm_active_connection_get_device(ac) == device) {
|
|
if (nm_device_sys_iface_state_is_external(device)
|
|
&& nm_device_get_allow_autoconnect_on_external(device)) {
|
|
/* pass */
|
|
} else
|
|
return;
|
|
}
|
|
}
|
|
|
|
nm_device_add_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
|
|
|
|
c_list_link_tail(&priv->policy_auto_activate_lst_head, &device->policy_auto_activate_lst);
|
|
device->policy_auto_activate_idle_source = nm_g_idle_add_source(_auto_activate_idle_cb, device);
|
|
}
|
|
|
|
static gboolean
|
|
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;
|
|
|
|
nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source);
|
|
|
|
min_stamp = 0;
|
|
now = nm_utils_get_monotonic_timestamp_sec();
|
|
connections = nm_settings_get_connections(priv->settings, NULL);
|
|
for (i = 0; connections[i]; i++) {
|
|
NMSettingsConnection *connection = connections[i];
|
|
|
|
con_stamp =
|
|
nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager, NULL, connection);
|
|
if (con_stamp == 0)
|
|
continue;
|
|
|
|
if (con_stamp <= now) {
|
|
nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, connection);
|
|
changed = TRUE;
|
|
} else if (min_stamp == 0 || min_stamp > con_stamp)
|
|
min_stamp = con_stamp;
|
|
}
|
|
|
|
/* Schedule the handler again if there are some stamps left */
|
|
if (min_stamp != 0) {
|
|
priv->reset_connections_retries_idle_source =
|
|
nm_g_timeout_add_seconds_source(min_stamp - now, reset_connections_retries, self);
|
|
}
|
|
|
|
/* If anything changed, try to activate the newly re-enabled connections */
|
|
if (changed)
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
_connection_autoconnect_retries_set(NMPolicy *self,
|
|
NMDevice *device,
|
|
NMSettingsConnection *connection,
|
|
guint32 tries)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
|
|
|
|
nm_manager_devcon_autoconnect_retries_set(priv->manager, device, connection, tries);
|
|
|
|
if (tries == 0) {
|
|
/* Schedule a handler to reset retries count */
|
|
if (!priv->reset_connections_retries_idle_source) {
|
|
gint32 retry_time;
|
|
|
|
retry_time = nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager,
|
|
device,
|
|
connection);
|
|
nm_assert(retry_time != 0);
|
|
|
|
priv->reset_connections_retries_idle_source = nm_g_timeout_add_seconds_source(
|
|
NM_MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()),
|
|
reset_connections_retries,
|
|
self);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
unblock_autoconnect_for_children(NMPolicy *self,
|
|
const char *parent_device,
|
|
const char *parent_uuid_settings,
|
|
const char *parent_uuid_applied,
|
|
const char *parent_mac_addr,
|
|
gboolean reset_devcon_autoconnect)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMSettingsConnection *const *connections;
|
|
gboolean changed;
|
|
guint i;
|
|
|
|
_LOGT(LOGD_CORE,
|
|
"block-autoconnect: unblocking child profiles for parent ifname=%s%s%s, uuid=%s%s%s"
|
|
"%s%s%s",
|
|
NM_PRINT_FMT_QUOTE_STRING(parent_device),
|
|
NM_PRINT_FMT_QUOTE_STRING(parent_uuid_settings),
|
|
NM_PRINT_FMT_QUOTED(parent_uuid_applied,
|
|
", applied-uuid=\"",
|
|
parent_uuid_applied,
|
|
"\"",
|
|
""));
|
|
|
|
changed = FALSE;
|
|
connections = nm_settings_get_connections(priv->settings, NULL);
|
|
for (i = 0; connections[i]; i++) {
|
|
NMSettingsConnection *sett_conn = connections[i];
|
|
NMConnection *connection;
|
|
NMDeviceFactory *factory;
|
|
const char *parent_name = NULL;
|
|
|
|
connection = nm_settings_connection_get_connection(sett_conn);
|
|
factory = nm_device_factory_manager_find_factory_for_connection(connection);
|
|
if (factory)
|
|
parent_name = nm_device_factory_get_connection_parent(factory, connection);
|
|
|
|
if (!parent_name)
|
|
continue;
|
|
|
|
if (!NM_IN_STRSET(parent_name,
|
|
parent_device,
|
|
parent_uuid_applied,
|
|
parent_uuid_settings,
|
|
parent_mac_addr))
|
|
continue;
|
|
|
|
if (reset_devcon_autoconnect) {
|
|
if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
|
|
changed = TRUE;
|
|
}
|
|
|
|
/* unblock the devices associated with that connection */
|
|
if (nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
NULL,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
FALSE)) {
|
|
if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
|
|
changed = TRUE;
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
|
|
static void
|
|
unblock_autoconnect_for_ports(NMPolicy *self,
|
|
const char *master_device,
|
|
const char *master_uuid_settings,
|
|
const char *master_uuid_applied,
|
|
gboolean reset_devcon_autoconnect)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMSettingsConnection *const *connections;
|
|
gboolean changed;
|
|
guint i;
|
|
|
|
_LOGT(LOGD_CORE,
|
|
"block-autoconnect: unblocking port profiles for controller ifname=%s%s%s, uuid=%s%s%s"
|
|
"%s%s%s",
|
|
NM_PRINT_FMT_QUOTE_STRING(master_device),
|
|
NM_PRINT_FMT_QUOTE_STRING(master_uuid_settings),
|
|
NM_PRINT_FMT_QUOTED(master_uuid_applied,
|
|
", applied-uuid=\"",
|
|
master_uuid_applied,
|
|
"\"",
|
|
""));
|
|
|
|
changed = FALSE;
|
|
connections = nm_settings_get_connections(priv->settings, NULL);
|
|
for (i = 0; connections[i]; i++) {
|
|
NMSettingsConnection *sett_conn = connections[i];
|
|
NMSettingConnection *s_slave_con;
|
|
const char *slave_master;
|
|
|
|
s_slave_con =
|
|
nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION);
|
|
slave_master = nm_setting_connection_get_master(s_slave_con);
|
|
if (!slave_master)
|
|
continue;
|
|
|
|
if (!NM_IN_STRSET(slave_master, master_device, master_uuid_applied, master_uuid_settings))
|
|
continue;
|
|
|
|
if (reset_devcon_autoconnect) {
|
|
if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
|
|
changed = TRUE;
|
|
}
|
|
|
|
/* unblock the devices associated with that connection */
|
|
if (nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
NULL,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
FALSE)) {
|
|
if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
|
|
changed = TRUE;
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
|
|
static void
|
|
unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection *sett_conn)
|
|
{
|
|
const char *master_device;
|
|
const char *master_uuid_settings;
|
|
NMSettingConnection *s_con;
|
|
|
|
nm_assert(NM_IS_POLICY(self));
|
|
nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
|
|
|
|
s_con = nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION);
|
|
|
|
nm_assert(NM_IS_SETTING_CONNECTION(s_con));
|
|
|
|
master_uuid_settings = nm_setting_connection_get_uuid(s_con);
|
|
master_device = nm_setting_connection_get_interface_name(s_con);
|
|
|
|
unblock_autoconnect_for_ports(self, master_device, master_uuid_settings, NULL, TRUE);
|
|
}
|
|
|
|
static void
|
|
activate_port_or_children_connections(NMPolicy *self,
|
|
NMDevice *device,
|
|
gboolean activate_children_connections_only)
|
|
{
|
|
const char *controller_device;
|
|
const char *controller_uuid_settings = NULL;
|
|
const char *controller_uuid_applied = NULL;
|
|
const char *parent_mac_addr = NULL;
|
|
NMActRequest *req;
|
|
gboolean internal_activation = FALSE;
|
|
|
|
controller_device = nm_device_get_iface(device);
|
|
nm_assert(controller_device);
|
|
|
|
parent_mac_addr = nm_device_get_permanent_hw_address(device);
|
|
|
|
req = nm_device_get_act_request(device);
|
|
if (req) {
|
|
NMConnection *connection;
|
|
NMSettingsConnection *sett_conn;
|
|
NMAuthSubject *subject;
|
|
|
|
sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req));
|
|
if (sett_conn)
|
|
controller_uuid_settings = nm_settings_connection_get_uuid(sett_conn);
|
|
|
|
connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req));
|
|
if (connection)
|
|
controller_uuid_applied = nm_connection_get_uuid(connection);
|
|
|
|
if (nm_streq0(controller_uuid_settings, controller_uuid_applied))
|
|
controller_uuid_applied = NULL;
|
|
|
|
subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req));
|
|
internal_activation =
|
|
subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
|
|
}
|
|
|
|
if (!activate_children_connections_only) {
|
|
unblock_autoconnect_for_ports(self,
|
|
controller_device,
|
|
controller_uuid_settings,
|
|
controller_uuid_applied,
|
|
!internal_activation);
|
|
}
|
|
unblock_autoconnect_for_children(self,
|
|
controller_device,
|
|
controller_uuid_settings,
|
|
controller_uuid_applied,
|
|
parent_mac_addr,
|
|
!internal_activation);
|
|
}
|
|
|
|
static gboolean
|
|
activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevice *device)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMSettingConnection *s_con;
|
|
NMActiveConnection *ac;
|
|
PendingSecondaryData *secondary_data;
|
|
GSList *secondary_ac_list = NULL;
|
|
GError *error = NULL;
|
|
guint32 i;
|
|
gboolean success = TRUE;
|
|
NMActivationStateFlags initial_state_flags;
|
|
|
|
s_con = nm_connection_get_setting_connection(connection);
|
|
nm_assert(NM_IS_SETTING_CONNECTION(s_con));
|
|
|
|
/* we propagate the activation's state flags. */
|
|
initial_state_flags = nm_device_get_activation_state_flags(device)
|
|
& NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
|
|
|
|
for (i = 0; i < nm_setting_connection_get_num_secondaries(s_con); i++) {
|
|
NMSettingsConnection *sett_conn;
|
|
const char *sec_uuid = nm_setting_connection_get_secondary(s_con, i);
|
|
NMActRequest *req;
|
|
|
|
sett_conn = nm_settings_get_connection_by_uuid(priv->settings, sec_uuid);
|
|
if (!sett_conn) {
|
|
_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_settings_connection_get_connection(sett_conn),
|
|
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(sett_conn),
|
|
sec_uuid);
|
|
success = FALSE;
|
|
break;
|
|
}
|
|
|
|
req = nm_device_get_act_request(device);
|
|
|
|
_LOGD(LOGD_DEVICE,
|
|
"activating secondary connection '%s (%s)' for base connection '%s (%s)'",
|
|
nm_settings_connection_get_id(sett_conn),
|
|
sec_uuid,
|
|
nm_connection_get_id(connection),
|
|
nm_connection_get_uuid(connection));
|
|
ac = nm_manager_activate_connection(
|
|
priv->manager,
|
|
sett_conn,
|
|
NULL,
|
|
nm_dbus_object_get_path(NM_DBUS_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)),
|
|
initial_state_flags,
|
|
&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(sett_conn),
|
|
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 *sett_conn = nm_device_get_settings_connection(device);
|
|
NMSettingConnection *s_con = NULL;
|
|
|
|
switch (nm_device_state_reason_check(reason)) {
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
|
|
case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
|
|
case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
|
|
/* Block autoconnection at settings level if there is any settings-specific
|
|
* error reported by the modem (e.g. wrong SIM-PIN or wrong APN). Do not block
|
|
* autoconnection at settings level for errors in the device domain (e.g.
|
|
* a missing SIM or wrong modem initialization).
|
|
*/
|
|
if (sett_conn) {
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
device,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
TRUE);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (new_state) {
|
|
case NM_DEVICE_STATE_FAILED:
|
|
g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
|
|
|
|
/* Mark the connection invalid if it failed during activation so that
|
|
* it doesn't get automatically chosen over and over and over again.
|
|
*/
|
|
if (sett_conn && old_state >= NM_DEVICE_STATE_PREPARE
|
|
&& old_state <= NM_DEVICE_STATE_ACTIVATED) {
|
|
gboolean blocked = FALSE;
|
|
guint64 con_v;
|
|
|
|
switch (nm_device_state_reason_check(reason)) {
|
|
case NM_DEVICE_STATE_REASON_NO_SECRETS:
|
|
/* we want to block the connection from auto-connect if it failed due to no-secrets.
|
|
* However, if a secret-agent registered, since the connection made the last
|
|
* secret-request, we do not block it. The new secret-agent might not yet
|
|
* 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(sett_conn);
|
|
if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) {
|
|
_LOGD(LOGD_DEVICE,
|
|
"block-autoconnect: connection '%s' now blocked from autoconnect due to "
|
|
"no secrets",
|
|
nm_settings_connection_get_id(sett_conn));
|
|
nm_settings_connection_autoconnect_blocked_reason_set(
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS,
|
|
TRUE);
|
|
blocked = TRUE;
|
|
}
|
|
break;
|
|
case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
|
|
/* A connection that fails due to dependency-failed is not able to
|
|
* reconnect until the connection it depends on activates again;
|
|
* when this happens, the controller or parent clears the blocked
|
|
* reason for all its dependent devices in activate_port_or_children_connections()
|
|
* and tries to reconnect them. For this to work, the port should
|
|
* be marked as blocked when it fails with dependency-failed.
|
|
*/
|
|
_LOGD(LOGD_DEVICE,
|
|
"block-autoconnect: connection[%p] (%s) now blocked from autoconnect due to "
|
|
"failed "
|
|
"dependency",
|
|
sett_conn,
|
|
nm_settings_connection_get_id(sett_conn));
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
device,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
TRUE);
|
|
blocked = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!blocked) {
|
|
guint32 tries;
|
|
|
|
tries = nm_manager_devcon_autoconnect_retries_get(priv->manager, device, sett_conn);
|
|
if (tries == 0) {
|
|
/* blocked */
|
|
} else if (tries != NM_AUTOCONNECT_RETRIES_FOREVER) {
|
|
_LOGD(LOGD_DEVICE,
|
|
"autoconnect: connection[%p] (%s): failed to autoconnect; %u tries left",
|
|
sett_conn,
|
|
nm_settings_connection_get_id(sett_conn),
|
|
tries - 1u);
|
|
_connection_autoconnect_retries_set(self, device, sett_conn, tries - 1u);
|
|
} else {
|
|
_LOGD(LOGD_DEVICE,
|
|
"autoconnect: connection[%p] (%s) failed to autoconnect; infinite tries "
|
|
"left",
|
|
sett_conn,
|
|
nm_settings_connection_get_id(sett_conn));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case NM_DEVICE_STATE_ACTIVATED:
|
|
if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE) {
|
|
/* When parent is ovs-interface, the kernel link is only created in stage3, we have to
|
|
* delay unblocking the children and schedule them for activation until parent is activated */
|
|
activate_port_or_children_connections(self, device, TRUE);
|
|
}
|
|
if (sett_conn) {
|
|
/* Reset auto retries back to default since connection was successful */
|
|
nm_manager_devcon_autoconnect_retries_reset(priv->manager, device, sett_conn);
|
|
}
|
|
|
|
/* Since there is no guarantee that device_l3cd_changed() is called
|
|
* again when the device becomes ACTIVATED, we need also to update
|
|
* routing and DNS here. */
|
|
nm_dns_manager_begin_updates(priv->dns_manager, __func__);
|
|
if (!nm_device_sys_iface_state_is_external(device)) {
|
|
nm_dns_manager_set_ip_config(priv->dns_manager,
|
|
AF_UNSPEC,
|
|
device,
|
|
nm_device_get_l3cd(device, TRUE),
|
|
nm_device_is_vpn(device) ? NM_DNS_IP_CONFIG_TYPE_VPN
|
|
: NM_DNS_IP_CONFIG_TYPE_DEFAULT,
|
|
TRUE);
|
|
}
|
|
update_ip_dns(self, AF_INET, device);
|
|
update_ip_dns(self, AF_INET6, device);
|
|
update_ip4_routing(self, TRUE);
|
|
update_ip6_routing(self, TRUE);
|
|
update_system_hostname(self, "routing and dns", TRUE);
|
|
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, device);
|
|
break;
|
|
case NM_DEVICE_STATE_DEACTIVATING:
|
|
if (sett_conn) {
|
|
NMSettingsAutoconnectBlockedReason blocked_reason =
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE;
|
|
|
|
switch (nm_device_state_reason_check(reason)) {
|
|
case NM_DEVICE_STATE_REASON_USER_REQUESTED:
|
|
blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST;
|
|
break;
|
|
case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
|
|
blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) {
|
|
_LOGD(LOGD_DEVICE,
|
|
"block-autoconnect: blocking autoconnect of connection '%s': %s",
|
|
nm_settings_connection_get_id(sett_conn),
|
|
NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_string,
|
|
nm_device_state_reason_check(reason)));
|
|
if (blocked_reason == NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED)
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(priv->manager,
|
|
device,
|
|
sett_conn,
|
|
blocked_reason,
|
|
TRUE);
|
|
else
|
|
nm_settings_connection_autoconnect_blocked_reason_set(sett_conn,
|
|
blocked_reason,
|
|
TRUE);
|
|
}
|
|
}
|
|
ip6_remove_device_prefix_delegations(self, device);
|
|
break;
|
|
case NM_DEVICE_STATE_DISCONNECTED:
|
|
g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
|
|
|
|
/* 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);
|
|
|
|
/* Device is now available for auto-activation */
|
|
nm_policy_device_recheck_auto_activate_schedule(self, device);
|
|
break;
|
|
|
|
case NM_DEVICE_STATE_PREPARE:
|
|
/* Reset auto-connect retries of all ports or children and schedule them for
|
|
* activation. */
|
|
activate_port_or_children_connections(self, device, FALSE);
|
|
|
|
/* 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 (sett_conn)
|
|
nm_manager_devcon_autoconnect_blocked_reason_set(
|
|
priv->manager,
|
|
device,
|
|
sett_conn,
|
|
NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
|
|
FALSE);
|
|
break;
|
|
case NM_DEVICE_STATE_SECONDARIES:
|
|
if (sett_conn)
|
|
s_con = nm_connection_get_setting_connection(
|
|
nm_settings_connection_get_connection(sett_conn));
|
|
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, device);
|
|
|
|
/* Activate secondary (VPN) connections */
|
|
if (!activate_secondary_connections(self,
|
|
nm_settings_connection_get_connection(sett_conn),
|
|
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_active_connections(self);
|
|
}
|
|
|
|
static void
|
|
device_l3cd_changed(NMDevice *device,
|
|
const NML3ConfigData *l3cd_old,
|
|
const NML3ConfigData *l3cd_new,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
NMDeviceState state;
|
|
|
|
nm_assert(!l3cd_new || NM_IS_L3_CONFIG_DATA(l3cd_new));
|
|
nm_assert(!l3cd_old || NM_IS_L3_CONFIG_DATA(l3cd_old));
|
|
|
|
nm_dns_manager_begin_updates(priv->dns_manager, __func__);
|
|
|
|
/* FIXME(l3cfg): Note that this function is not always called when the
|
|
* device becomes ACTIVATED. Previously, we would also update the DNS
|
|
* manager's IP config in device_state_change(ACTIVATED). There we would
|
|
* also special-case pseudo-VPNs like wireguard. I don't see the code where
|
|
* this is handled now.
|
|
*/
|
|
state = nm_device_get_state(device);
|
|
if (l3cd_new && state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
|
|
nm_dns_manager_set_ip_config(priv->dns_manager,
|
|
AF_UNSPEC,
|
|
device,
|
|
l3cd_new,
|
|
nm_device_is_vpn(device) ? NM_DNS_IP_CONFIG_TYPE_VPN
|
|
: NM_DNS_IP_CONFIG_TYPE_DEFAULT,
|
|
TRUE);
|
|
update_ip_dns(self, AF_INET, device);
|
|
update_ip_dns(self, AF_INET6, device);
|
|
update_ip4_routing(self, TRUE);
|
|
update_ip6_routing(self, TRUE);
|
|
/* FIXME: since we already monitor platform addresses changes,
|
|
* this is probably no longer necessary? */
|
|
update_system_hostname(self, "ip conf", FALSE);
|
|
} else {
|
|
nm_dns_manager_set_ip_config(priv->dns_manager,
|
|
AF_UNSPEC,
|
|
device,
|
|
l3cd_old,
|
|
NM_DNS_IP_CONFIG_TYPE_REMOVED,
|
|
TRUE);
|
|
}
|
|
|
|
nm_dns_manager_end_updates(priv->dns_manager, __func__);
|
|
}
|
|
|
|
static void
|
|
device_platform_address_changed(NMDevice *device, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
NMDeviceState state;
|
|
|
|
state = nm_device_get_state(device);
|
|
if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) {
|
|
update_system_hostname(self, "address changed", TRUE);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
device_autoconnect_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
|
|
nm_policy_device_recheck_auto_activate_schedule(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, G_CALLBACK(device_state_changed), priv);
|
|
g_signal_connect(device, NM_DEVICE_L3CD_CHANGED, G_CALLBACK(device_l3cd_changed), priv);
|
|
g_signal_connect(device,
|
|
NM_DEVICE_PLATFORM_ADDRESS_CHANGED,
|
|
G_CALLBACK(device_platform_address_changed),
|
|
priv);
|
|
g_signal_connect(device,
|
|
NM_DEVICE_IP6_PREFIX_DELEGATED,
|
|
G_CALLBACK(device_ip6_prefix_delegated),
|
|
priv);
|
|
g_signal_connect(device,
|
|
NM_DEVICE_IP6_SUBNET_NEEDED,
|
|
G_CALLBACK(device_ip6_subnet_needed),
|
|
priv);
|
|
g_signal_connect(device,
|
|
"notify::" NM_DEVICE_AUTOCONNECT,
|
|
G_CALLBACK(device_autoconnect_changed),
|
|
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 (!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);
|
|
|
|
/* TODO: is this needed? The delegations are cleaned up
|
|
* on transition to deactivated too. */
|
|
ip6_remove_device_prefix_delegations(self, device);
|
|
|
|
if (c_list_is_linked(&device->policy_auto_activate_lst))
|
|
_auto_activate_device_clear(self, device, FALSE);
|
|
|
|
if (g_hash_table_remove(priv->devices, device))
|
|
devices_list_unregister(self, device);
|
|
|
|
/* 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_update_dns(NMPolicy *self, NMVpnConnection *vpn, gboolean remove)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
int IS_IPv4;
|
|
|
|
nm_dns_manager_begin_updates(priv->dns_manager, __func__);
|
|
|
|
for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
|
|
int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
|
|
const NML3ConfigData *l3cd;
|
|
|
|
l3cd = nm_vpn_connection_get_l3cd(vpn);
|
|
nm_dns_manager_set_ip_config(priv->dns_manager,
|
|
addr_family,
|
|
vpn,
|
|
l3cd,
|
|
remove ? NM_DNS_IP_CONFIG_TYPE_REMOVED
|
|
: NM_DNS_IP_CONFIG_TYPE_VPN,
|
|
TRUE);
|
|
}
|
|
|
|
update_routing_and_dns(self, TRUE, NULL);
|
|
|
|
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)
|
|
{
|
|
/* FIXME(l3cfg): we need to track changes to nm_vpn_connection_get_l3cd(). */
|
|
if (new_state == NM_VPN_CONNECTION_STATE_ACTIVATED)
|
|
vpn_connection_update_dns(self, vpn, FALSE);
|
|
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_update_dns(self, vpn, TRUE);
|
|
}
|
|
}
|
|
|
|
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),
|
|
(nm_active_connection_get_state_flags(ac)
|
|
& NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY),
|
|
&error)) {
|
|
_LOGW(LOGD_DEVICE,
|
|
"VPN '%s' reconnect failed: %s",
|
|
nm_settings_connection_get_id(connection),
|
|
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_keep_alive_changed(NMKeepAlive *keep_alive, GParamSpec *pspec, NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv;
|
|
NMActiveConnection *ac;
|
|
GError *error = NULL;
|
|
|
|
nm_assert(NM_IS_POLICY(self));
|
|
nm_assert(NM_IS_KEEP_ALIVE(keep_alive));
|
|
nm_assert(NM_IS_ACTIVE_CONNECTION(nm_keep_alive_get_owner(keep_alive)));
|
|
|
|
if (nm_keep_alive_is_alive(keep_alive))
|
|
return;
|
|
|
|
ac = nm_keep_alive_get_owner(keep_alive);
|
|
|
|
if (nm_active_connection_get_state(ac) > NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
|
|
return;
|
|
|
|
priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
if (!nm_manager_deactivate_connection(priv->manager,
|
|
ac,
|
|
NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
|
|
&error)) {
|
|
_LOGW(LOGD_DEVICE,
|
|
"connection '%s' is no longer kept alive, but error deactivating it: %s",
|
|
nm_active_connection_get_settings_connection_id(ac),
|
|
error->message);
|
|
g_clear_error(&error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
active_connection_added(NMManager *manager, NMActiveConnection *active, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
NMKeepAlive *keep_alive;
|
|
|
|
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);
|
|
}
|
|
|
|
keep_alive = nm_active_connection_get_keep_alive(active);
|
|
|
|
nm_keep_alive_arm(keep_alive);
|
|
|
|
g_signal_connect(active,
|
|
"notify::" NM_ACTIVE_CONNECTION_STATE,
|
|
G_CALLBACK(active_connection_state_changed),
|
|
self);
|
|
g_signal_connect(keep_alive,
|
|
"notify::" NM_KEEP_ALIVE_ALIVE,
|
|
G_CALLBACK(active_connection_keep_alive_changed),
|
|
self);
|
|
active_connection_keep_alive_changed(keep_alive, NULL, 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);
|
|
g_signal_handlers_disconnect_by_func(nm_active_connection_get_keep_alive(active),
|
|
active_connection_keep_alive_changed,
|
|
self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_device_recheck_auto_activate_all_cb(gpointer user_data)
|
|
{
|
|
NMPolicy *self = user_data;
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
|
|
nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
|
|
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst)
|
|
nm_policy_device_recheck_auto_activate_schedule(self, device);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self)
|
|
{
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
|
|
/* always restart the idle handler. That way, we settle
|
|
* all other events before restarting to activate them. */
|
|
nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
|
|
|
|
priv->device_recheck_auto_activate_all_idle_source =
|
|
nm_g_idle_add_source(_device_recheck_auto_activate_all_cb, self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
connection_added(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
|
|
unblock_autoconnect_for_ports_for_sett_conn(self, connection);
|
|
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
|
|
static void
|
|
firewall_state_changed(NMFirewalldManager *manager, int signal_type_i, gpointer user_data)
|
|
{
|
|
const NMFirewalldManagerStateChangedType signal_type = signal_type_i;
|
|
NMPolicy *self = user_data;
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
|
|
if (signal_type == NM_FIREWALLD_MANAGER_STATE_CHANGED_TYPE_INITIALIZED) {
|
|
/* 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_firewalld_manager_get_running(manager))
|
|
return;
|
|
|
|
/* add interface of each device to correct zone */
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst)
|
|
nm_device_update_firewall_zone(device);
|
|
}
|
|
|
|
static void
|
|
dns_config_changed(NMDnsManager *dns_manager, gpointer user_data)
|
|
{
|
|
NMPolicy *self = (NMPolicy *) user_data;
|
|
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
|
|
NMDevice *device;
|
|
const CList *tmp_lst;
|
|
|
|
/* We are currently updating the hostname in the DNS manager.
|
|
* This doesn't warrant a new DNS lookup.*/
|
|
if (priv->updating_dns)
|
|
return;
|
|
|
|
if (!nm_dns_manager_is_unmanaged(dns_manager)) {
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
nm_device_clear_dns_lookup_data(device, "DNS configuration changed");
|
|
}
|
|
|
|
update_system_hostname(self, "DNS configuration changed", FALSE);
|
|
}
|
|
|
|
nm_dispatcher_call_dns_change();
|
|
}
|
|
|
|
static void
|
|
connection_updated(NMSettings *settings,
|
|
NMSettingsConnection *connection,
|
|
guint update_reason_u,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
NMPolicy *self = _PRIV_TO_SELF(priv);
|
|
NMSettingsConnectionUpdateReason update_reason = update_reason_u;
|
|
|
|
unblock_autoconnect_for_ports_for_sett_conn(self, connection);
|
|
|
|
if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) {
|
|
const CList *tmp_lst;
|
|
NMDevice *device;
|
|
|
|
/* find device with given connection */
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
if (nm_device_get_settings_connection(device) == connection)
|
|
nm_device_reapply_settings_immediately(device);
|
|
}
|
|
}
|
|
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
|
|
static void
|
|
connection_removed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
|
|
{
|
|
NMPolicyPrivate *priv = user_data;
|
|
|
|
nm_manager_deactivate_ac(priv->manager, 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_INT_FLAGS_VISIBLE)) {
|
|
if (!nm_settings_connection_autoconnect_is_blocked(connection))
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
}
|
|
|
|
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))
|
|
nm_policy_device_recheck_auto_activate_all_schedule(self);
|
|
}
|
|
|
|
NMActiveConnection *
|
|
nm_policy_get_default_ip4_ac(NMPolicy *self)
|
|
{
|
|
return NM_POLICY_GET_PRIVATE(self)->default_ac4;
|
|
}
|
|
|
|
NMActiveConnection *
|
|
nm_policy_get_default_ip6_ac(NMPolicy *self)
|
|
{
|
|
return NM_POLICY_GET_PRIVATE(self)->default_ac6;
|
|
}
|
|
|
|
NMActiveConnection *
|
|
nm_policy_get_activating_ip4_ac(NMPolicy *self)
|
|
{
|
|
return NM_POLICY_GET_PRIVATE(self)->activating_ac4;
|
|
}
|
|
|
|
NMActiveConnection *
|
|
nm_policy_get_activating_ip6_ac(NMPolicy *self)
|
|
{
|
|
return NM_POLICY_GET_PRIVATE(self)->activating_ac6;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static NM_UTILS_LOOKUP_STR_DEFINE(_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_AC:
|
|
g_value_set_object(value, priv->default_ac4);
|
|
break;
|
|
case PROP_DEFAULT_IP6_AC:
|
|
g_value_set_object(value, priv->default_ac6);
|
|
break;
|
|
case PROP_ACTIVATING_IP4_AC:
|
|
g_value_set_object(value, priv->activating_ac4);
|
|
break;
|
|
case PROP_ACTIVATING_IP6_AC:
|
|
g_value_set_object(value, priv->activating_ac6);
|
|
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);
|
|
gs_free char *hostname_mode = NULL;
|
|
|
|
c_list_init(&priv->policy_auto_activate_lst_head);
|
|
|
|
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(nm_direct_hash, NULL);
|
|
priv->pending_active_connections = g_hash_table_new(nm_direct_hash, 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->firewalld_manager = g_object_ref(nm_firewalld_manager_get());
|
|
g_signal_connect(priv->firewalld_manager,
|
|
NM_FIREWALLD_MANAGER_STATE_CHANGED,
|
|
G_CALLBACK(firewall_state_changed),
|
|
self);
|
|
|
|
priv->dns_manager = g_object_ref(nm_dns_manager_get());
|
|
nm_dns_manager_set_hostname(priv->dns_manager, priv->orig_hostname, TRUE);
|
|
priv->config_changed_id = g_signal_connect(priv->dns_manager,
|
|
NM_DNS_MANAGER_CONFIG_CHANGED,
|
|
G_CALLBACK(dns_config_changed),
|
|
self);
|
|
|
|
g_signal_connect(priv->hostname_manager,
|
|
"notify::" NM_HOSTNAME_MANAGER_STATIC_HOSTNAME,
|
|
G_CALLBACK(_static_hostname_changed_cb),
|
|
priv);
|
|
|
|
g_signal_connect(priv->manager,
|
|
"notify::" NM_MANAGER_SLEEPING,
|
|
G_CALLBACK(sleeping_changed),
|
|
priv);
|
|
g_signal_connect(priv->manager,
|
|
"notify::" NM_MANAGER_NETWORKING_ENABLED,
|
|
G_CALLBACK(sleeping_changed),
|
|
priv);
|
|
g_signal_connect(priv->manager,
|
|
NM_MANAGER_INTERNAL_DEVICE_ADDED,
|
|
G_CALLBACK(device_added),
|
|
priv);
|
|
g_signal_connect(priv->manager,
|
|
NM_MANAGER_INTERNAL_DEVICE_REMOVED,
|
|
G_CALLBACK(device_removed),
|
|
priv);
|
|
g_signal_connect(priv->manager,
|
|
NM_MANAGER_ACTIVE_CONNECTION_ADDED,
|
|
G_CALLBACK(active_connection_added),
|
|
priv);
|
|
g_signal_connect(priv->manager,
|
|
NM_MANAGER_ACTIVE_CONNECTION_REMOVED,
|
|
G_CALLBACK(active_connection_removed),
|
|
priv);
|
|
|
|
g_signal_connect(priv->settings,
|
|
NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
|
|
G_CALLBACK(connection_added),
|
|
priv);
|
|
g_signal_connect(priv->settings,
|
|
NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
|
|
G_CALLBACK(connection_updated),
|
|
priv);
|
|
g_signal_connect(priv->settings,
|
|
NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
|
|
G_CALLBACK(connection_removed),
|
|
priv);
|
|
g_signal_connect(priv->settings,
|
|
NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED,
|
|
G_CALLBACK(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));
|
|
update_system_hostname(self, "initial hostname", FALSE);
|
|
}
|
|
|
|
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);
|
|
|
|
nm_assert(c_list_is_empty(&priv->policy_auto_activate_lst_head));
|
|
nm_assert(g_hash_table_size(priv->devices) == 0);
|
|
|
|
nm_clear_g_object(&priv->default_ac4);
|
|
nm_clear_g_object(&priv->default_ac6);
|
|
nm_clear_g_object(&priv->activating_ac4);
|
|
nm_clear_g_object(&priv->activating_ac6);
|
|
nm_clear_pointer(&priv->pending_active_connections, g_hash_table_unref);
|
|
|
|
g_slist_free_full(priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free);
|
|
priv->pending_secondaries = NULL;
|
|
|
|
if (priv->firewalld_manager) {
|
|
g_signal_handlers_disconnect_by_func(priv->firewalld_manager, firewall_state_changed, self);
|
|
g_clear_object(&priv->firewalld_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);
|
|
}
|
|
|
|
/* 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_inst(&priv->reset_connections_retries_idle_source);
|
|
nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
|
|
nm_clear_g_source_inst(&priv->hostname_retry.source);
|
|
|
|
nm_clear_g_free(&priv->orig_hostname);
|
|
nm_clear_g_free(&priv->cur_hostname);
|
|
nm_clear_g_free(&priv->cur_hostname_full);
|
|
nm_clear_g_free(&priv->last_hostname);
|
|
|
|
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_AC] =
|
|
g_param_spec_object(NM_POLICY_DEFAULT_IP4_AC,
|
|
"",
|
|
"",
|
|
NM_TYPE_ACTIVE_CONNECTION,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
obj_properties[PROP_DEFAULT_IP6_AC] =
|
|
g_param_spec_object(NM_POLICY_DEFAULT_IP6_AC,
|
|
"",
|
|
"",
|
|
NM_TYPE_DEVICE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
obj_properties[PROP_ACTIVATING_IP4_AC] =
|
|
g_param_spec_object(NM_POLICY_ACTIVATING_IP4_AC,
|
|
"",
|
|
"",
|
|
NM_TYPE_DEVICE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
obj_properties[PROP_ACTIVATING_IP6_AC] =
|
|
g_param_spec_object(NM_POLICY_ACTIVATING_IP6_AC,
|
|
"",
|
|
"",
|
|
NM_TYPE_DEVICE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
|
}
|