policy: use the hostname setting

Rework update_system_hostname() to use the new properties from the
hostname setting.

In the default configuration where all the 3 boolean properties
hostname.{from-dhcp,from-dns,only-from-default} are true, the behavior
is the same as before.
This commit is contained in:
Beniamino Galvani 2020-11-12 08:47:25 +01:00
parent abd002642f
commit 09c8387114
3 changed files with 465 additions and 77 deletions

View file

@ -206,6 +206,23 @@ typedef struct {
NMEthtoolRingState * ring;
} EthtoolState;
typedef enum {
RESOLVER_WAIT_ADDRESS = 0,
RESOLVER_IN_PROGRESS,
RESOLVER_DONE,
} ResolverState;
typedef struct {
ResolverState state;
GResolver * resolver;
GInetAddress *address;
GCancellable *cancellable;
char * hostname;
NMDevice * device;
guint timeout_id; /* Used when waiting for the address */
int addr_family;
} HostnameResolver;
/*****************************************************************************/
enum {
@ -218,6 +235,7 @@ enum {
REMOVED,
RECHECK_AUTO_ACTIVATE,
RECHECK_ASSUME,
DNS_LOOKUP_DONE,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
@ -324,6 +342,14 @@ typedef struct _NMDevicePrivate {
int auth_retries;
union {
struct {
HostnameResolver *hostname_resolver_6;
HostnameResolver *hostname_resolver_4;
};
HostnameResolver *hostname_resolver_x[2];
};
union {
const guint8 hw_addr_len; /* read-only */
guint8 hw_addr_len_;
@ -866,6 +892,22 @@ static NM_UTILS_LOOKUP_STR_DEFINE(mtu_source_to_str,
/*****************************************************************************/
static void
_hostname_resolver_free(HostnameResolver *resolver)
{
if (!resolver)
return;
nm_clear_g_source(&resolver->timeout_id);
nm_clear_g_cancellable(&resolver->cancellable);
nm_g_object_unref(resolver->resolver);
nm_g_object_unref(resolver->address);
g_free(resolver->hostname);
nm_g_slice_free(resolver);
}
/*****************************************************************************/
static NMSettingIP6ConfigPrivacy
_ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr)
{
@ -15618,6 +15660,7 @@ static void
_cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint i;
_cancel_activation(self);
@ -15642,6 +15685,9 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release);
for (i = 0; i < 2; i++)
nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
_cleanup_ip_pre(self, AF_INET, cleanup_type);
_cleanup_ip_pre(self, AF_INET6, cleanup_type);
}
@ -17467,6 +17513,178 @@ nm_device_auth_retries_try_next(NMDevice *self)
return TRUE;
}
static void
hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
{
HostnameResolver *resolver;
NMDevice * self;
gs_free char * hostname = NULL;
gs_free char * addr_str = NULL;
gs_free_error GError *error = NULL;
hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
resolver = user_data;
self = resolver->device;
resolver->state = RESOLVER_DONE;
resolver->hostname = g_strdup(hostname);
_LOGD(LOGD_DNS,
"hostname-from-dns: lookup done for %s, result %s%s%s",
(addr_str = g_inet_address_to_string(resolver->address)),
NM_PRINT_FMT_QUOTE_STRING(hostname));
nm_clear_g_cancellable(&resolver->cancellable);
g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
}
static gboolean
hostname_dns_address_timeout(gpointer user_data)
{
HostnameResolver *resolver = user_data;
NMDevice * self = resolver->device;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
nm_assert(resolver->state == RESOLVER_WAIT_ADDRESS);
nm_assert(!resolver->address);
nm_assert(!resolver->cancellable);
_LOGT(LOGD_DNS,
"hostname-from-dns: timed out while waiting IPv%c address",
nm_utils_addr_family_to_char(resolver->addr_family));
resolver->timeout_id = 0;
resolver->state = RESOLVER_DONE;
g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
return G_SOURCE_REMOVE;
}
/* return value is valid only immediately */
const char *
nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait)
{
NMDevicePrivate * priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
HostnameResolver *resolver;
NMIPConfig * ip_config;
const char * method;
gboolean address_changed = FALSE;
gs_unref_object GInetAddress *new_address = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
/* If the device is not supposed to have addresses,
* return an immediate empty result.*/
method = nm_device_get_effective_ip_config_method(self, addr_family);
if (IS_IPv4) {
if (NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
NM_SET_OUT(out_wait, FALSE);
return NULL;
}
} else {
if (NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
NM_SET_OUT(out_wait, FALSE);
return NULL;
}
}
resolver = priv->hostname_resolver_x[IS_IPv4];
if (!resolver) {
resolver = g_slice_new(HostnameResolver);
*resolver = (HostnameResolver){
.resolver = g_resolver_get_default(),
.device = self,
.addr_family = addr_family,
.state = RESOLVER_WAIT_ADDRESS,
};
priv->hostname_resolver_x[IS_IPv4] = resolver;
}
/* Determine the first address of the interface and
* whether it changed from the previous lookup */
ip_config = priv->ip_config_x[IS_IPv4];
if (ip_config) {
const NMPlatformIPAddress *addr;
addr = nm_ip_config_get_first_address(ip_config);
if (addr) {
new_address = g_inet_address_new_from_bytes(addr->address_ptr,
IS_IPv4 ? G_SOCKET_FAMILY_IPV4
: G_SOCKET_FAMILY_IPV6);
}
}
if (new_address && resolver->address) {
if (!g_inet_address_equal(new_address, resolver->address))
address_changed = TRUE;
} else if (new_address != resolver->address)
address_changed = TRUE;
{
gs_free char *old_str = NULL;
gs_free char *new_str = NULL;
_LOGT(LOGD_DNS,
"hostname-from-dns: ipv%c resolver state %d, old address %s, new address %s",
nm_utils_addr_family_to_char(resolver->addr_family),
resolver->state,
resolver->address ? (old_str = g_inet_address_to_string(resolver->address))
: "(null)",
new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)");
}
/* In every state, if the address changed, we restart
* the resolution with the new address */
if (address_changed) {
nm_clear_g_cancellable(&resolver->cancellable);
nm_g_object_unref(resolver->address);
resolver->state = RESOLVER_WAIT_ADDRESS;
}
if (address_changed && new_address) {
gs_free char *str = NULL;
_LOGT(LOGD_DNS,
"hostname-from-dns: starting lookup for address %s",
(str = g_inet_address_to_string(new_address)));
resolver->state = RESOLVER_IN_PROGRESS;
resolver->cancellable = g_cancellable_new();
resolver->address = g_steal_pointer(&new_address);
g_resolver_lookup_by_address_async(resolver->resolver,
resolver->address,
resolver->cancellable,
hostname_dns_lookup_callback,
resolver);
nm_clear_g_source(&resolver->timeout_id);
}
switch (resolver->state) {
case RESOLVER_WAIT_ADDRESS:
if (!resolver->timeout_id)
resolver->timeout_id = g_timeout_add(30000, hostname_dns_address_timeout, resolver);
NM_SET_OUT(out_wait, TRUE);
return NULL;
case RESOLVER_IN_PROGRESS:
NM_SET_OUT(out_wait, TRUE);
return NULL;
case RESOLVER_DONE:
NM_SET_OUT(out_wait, FALSE);
return resolver->hostname;
}
return nm_assert_unreachable_val(NULL);
}
/*****************************************************************************/
static const char *
@ -18644,6 +18862,16 @@ nm_device_class_init(NMDeviceClass *klass)
NULL,
G_TYPE_NONE,
0);
signals[DNS_LOOKUP_DONE] = g_signal_new(NM_DEVICE_DNS_LOOKUP_DONE,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
}
/* Connection defaults from plugins */

View file

@ -116,6 +116,7 @@ nm_device_state_reason_check(NMDeviceStateReason reason)
#define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
/* Internal signals */
#define NM_DEVICE_DNS_LOOKUP_DONE "dns-lookup-done"
#define NM_DEVICE_IP4_CONFIG_CHANGED "ip4-config-changed"
#define NM_DEVICE_IP6_CONFIG_CHANGED "ip6-config-changed"
#define NM_DEVICE_IP6_PREFIX_DELEGATED "ip6-prefix-delegated"
@ -863,4 +864,7 @@ const char *nm_device_state_reason_to_str(NMDeviceStateReason reason);
gboolean nm_device_is_vpn(NMDevice *self);
const char *
nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_pending);
#endif /* __NETWORKMANAGER_DEVICE_H__ */

View file

@ -117,6 +117,8 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv)
/*****************************************************************************/
#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 \
{ \
@ -131,6 +133,7 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv)
/*****************************************************************************/
static void update_system_hostname(NMPolicy *self, const char *msg);
static void schedule_activate_all(NMPolicy *self);
static void schedule_activate_check(NMPolicy *self, NMDevice *device);
static NMDevice *get_default_device(NMPolicy *self, int addr_family);
@ -671,20 +674,168 @@ lookup_by_address(NMPolicy *self)
self);
}
typedef struct {
NMDevice *device;
int priority;
bool from_dhcp : 1;
bool from_dns : 1;
union {
struct {
bool ip_6;
bool ip_4;
};
bool ip_x[2];
};
} 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);
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;
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;
}
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,
NM_TERNARY_TRUE);
}
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;
device = nm_active_connection_get_device(ac);
if (!device)
continue;
only_from_default =
device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT);
if (only_from_default && ac != priv->default_ac4 && ac != priv->default_ac6)
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),
.ip_4 = priv->default_ac4 || !only_from_default,
.ip_6 = priv->default_ac6 || !only_from_default,
};
}
if (array && array->len > 1) {
const DeviceHostnameInfo *info0;
guint i;
g_array_sort(array, device_hostname_info_compare);
info0 = &g_array_index(array, DeviceHostnameInfo, 0);
if (info0->priority < 0) {
for (i = 1; i < array->len; i++) {
const DeviceHostnameInfo *info = &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");
}
static void
update_system_hostname(NMPolicy *self, const char *msg)
{
NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self);
const char * configured_hostname;
gs_free char * temp_hostname = NULL;
const char * dhcp_hostname, *p;
NMIP4Config * ip4_config;
NMIP6Config * ip6_config;
gboolean external_hostname = FALSE;
const NMPlatformIP4Address *addr4;
const NMPlatformIP6Address *addr6;
NMDevice * device;
NMDhcpConfig * dhcp_config;
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 IS_IPv4;
g_return_if_fail(self != NULL);
@ -724,10 +875,9 @@ update_system_hostname(NMPolicy *self, const char *msg)
/* Hostname precedence order:
*
* 1) a configured hostname (from settings)
* 2) automatic hostname from the default device's config (DHCP, VPN, etc)
* 3) the last hostname set outside NM
* 4) reverse-DNS of the best device's IPv4 address
*
* 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 */
@ -738,40 +888,73 @@ update_system_hostname(NMPolicy *self, const char *msg)
return;
}
if (priv->default_ac4) {
/* Grab a hostname out of the device's DHCP4 config */
dhcp_config = nm_device_get_dhcp_config(get_default_device(self, AF_INET), AF_INET);
if (dhcp_config) {
dhcp_hostname = nm_dhcp_config_get_option(dhcp_config, "host_name");
if (dhcp_hostname && dhcp_hostname[0]) {
p = nm_str_skip_leading_spaces(dhcp_hostname);
if (p[0]) {
_set_hostname(self, p, "from DHCPv4");
priv->dhcp_hostname = TRUE;
return;
}
_LOGW(LOGD_DNS,
"set-hostname: DHCPv4-provided hostname '%s' looks invalid; ignoring it",
dhcp_hostname);
}
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 = &g_array_index(infos, DeviceHostnameInfo, i);
_LOGT(LOGD_DNS,
" - prio:%4d ipv:%c%c dhcp:%d dns:%d dev:%s",
info->priority,
info->ip_4 ? '4' : '-',
info->ip_6 ? '6' : '-',
info->from_dhcp,
info->from_dns,
nm_device_get_iface(info->device));
}
}
if (priv->default_ac6) {
/* Grab a hostname out of the device's DHCP6 config */
dhcp_config = nm_device_get_dhcp_config(get_default_device(self, AF_INET6), AF_INET6);
if (dhcp_config) {
dhcp_hostname = nm_dhcp_config_get_option(dhcp_config, "fqdn_fqdn");
if (dhcp_hostname && dhcp_hostname[0]) {
p = nm_str_skip_leading_spaces(dhcp_hostname);
if (p[0]) {
_set_hostname(self, p, "from DHCPv6");
priv->dhcp_hostname = TRUE;
return;
for (i = 0; infos && i < infos->len; i++) {
info = &g_array_index(infos, DeviceHostnameInfo, i);
g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self);
for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
if (info->from_dhcp && info->ip_x[IS_IPv4]) {
dhcp_config = nm_device_get_dhcp_config(info->device, addr_family);
if (dhcp_config) {
dhcp_hostname =
nm_dhcp_config_get_option(dhcp_config, 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, IS_IPv4 ? "from DHCPv4" : "from DHCPv6");
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) {
for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
const int addr_family = IS_IPv4 ? AF_INET6 : AF_INET;
if (info->from_dns && info->ip_x[IS_IPv4]) {
const char *result;
gboolean wait;
result =
nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
if (result) {
_set_hostname(self, result, "from address lookup");
return;
}
if (wait) {
g_signal_connect(info->device,
NM_DEVICE_DNS_LOOKUP_DONE,
(GCallback) device_dns_lookup_done,
self);
return;
}
}
_LOGW(LOGD_DNS,
"set-hostname: DHCPv6-provided hostname '%s' looks invalid; ignoring it",
dhcp_hostname);
}
}
}
@ -795,14 +978,6 @@ update_system_hostname(NMPolicy *self, const char *msg)
priv->dhcp_hostname = FALSE;
if (!priv->default_ac4 && !priv->default_ac6) {
/* No best device; fall back to the last hostname set externally
* to NM or if there wasn't one, 'localhost.localdomain'
*/
_set_hostname(self, priv->orig_hostname, "no default device");
return;
}
/* If no automatically-configured hostname, try using the last hostname
* set externally to NM
*/
@ -811,30 +986,7 @@ update_system_hostname(NMPolicy *self, const char *msg)
return;
}
/* No configured hostname, no automatically determined hostname, and no
* bootup hostname. Start reverse DNS of the current IPv4 or IPv6 address.
*/
device = get_default_device(self, AF_INET);
ip4_config = device ? nm_device_get_ip4_config(device) : NULL;
device = get_default_device(self, AF_INET6);
ip6_config = device ? nm_device_get_ip6_config(device) : NULL;
if (ip4_config && (addr4 = nm_ip4_config_get_first_address(ip4_config))) {
g_clear_object(&priv->lookup.addr);
priv->lookup.addr =
g_inet_address_new_from_bytes((guint8 *) &addr4->address, G_SOCKET_FAMILY_IPV4);
} else if (ip6_config && (addr6 = nm_ip6_config_get_first_address(ip6_config))) {
g_clear_object(&priv->lookup.addr);
priv->lookup.addr =
g_inet_address_new_from_bytes((guint8 *) &addr6->address, G_SOCKET_FAMILY_IPV6);
} else {
/* No valid IP config; fall back to localhost.localdomain */
_set_hostname(self, NULL, "no IP config");
return;
}
lookup_by_address(self);
_set_hostname(self, NULL, "no hostname found");
}
static void
@ -1796,6 +1948,8 @@ device_state_changed(NMDevice * device,
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.
*/
@ -1934,6 +2088,8 @@ device_state_changed(NMDevice * device,
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.
*/