Thomas Haller 2022-04-15 09:21:28 +02:00
commit f9f7f231c8
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
20 changed files with 873 additions and 387 deletions

View file

@ -2530,8 +2530,6 @@ src_core_libNetworkManager_la_SOURCES = \
src/core/dns/nm-dns-dnsmasq.h \
src/core/dns/nm-dns-systemd-resolved.c \
src/core/dns/nm-dns-systemd-resolved.h \
src/core/dns/nm-dns-unbound.c \
src/core/dns/nm-dns-unbound.h \
\
src/core/dnsmasq/nm-dnsmasq-manager.c \
src/core/dnsmasq/nm-dnsmasq-manager.h \

View file

@ -13,9 +13,6 @@
/* Define to path of dnsmasq binary */
#mesondefine DNSMASQ_PATH
/* Define to path of unbound dnssec-trigger-script */
#mesondefine DNSSEC_TRIGGER_PATH
/* Gettext package */
#mesondefine GETTEXT_PACKAGE

View file

@ -1006,18 +1006,6 @@ fi
AC_DEFINE_UNQUOTED(DNSMASQ_PATH, "$DNSMASQ_PATH", [Define to path of dnsmasq binary])
AC_SUBST(DNSMASQ_PATH)
# dnssec-trigger-script path
AC_ARG_WITH(dnssec_trigger,
AS_HELP_STRING([--with-dnssec-trigger=/path/to/dnssec-trigger-script], [path to unbound dnssec-trigger-script]))
if test "x${with_dnssec_trigger}" = x; then
AC_PATH_PROG(DNSSEC_TRIGGER_PATH, dnssec-trigger-script, /usr/libexec/dnssec-trigger-script,
/usr/local/libexec:/usr/local/lib:/usr/local/lib/dnssec-trigger:/usr/libexec:/usr/lib:/usr/lib/dnssec-trigger)
else
DNSSEC_TRIGGER_PATH="$with_dnssec_trigger"
fi
AC_DEFINE_UNQUOTED(DNSSEC_TRIGGER_PATH, "$DNSSEC_TRIGGER_PATH", [Define to path of unbound dnssec-trigger-script])
AC_SUBST(DNSSEC_TRIGGER_PATH)
# system CA certificates path
AC_ARG_WITH(system-ca-path,
AS_HELP_STRING([--with-system-ca-path=/path/to/ssl/certs], [path to system CA certificates]))

View file

@ -345,19 +345,12 @@ no-auto-default=*
<para><literal>systemd-resolved</literal>: NetworkManager will
push the DNS configuration to systemd-resolved</para>
<para><literal>unbound</literal>: NetworkManager will talk
to unbound and dnssec-triggerd, using "Conditional Forwarding"
with DNSSEC support. <filename>/etc/resolv.conf</filename>
will be managed by dnssec-trigger daemon. This option is
deprecated. Note that dnssec-trigger ships a NetworkManager dispatcher
script so this DNS plugin is not necessary.</para>
<para><literal>none</literal>: NetworkManager will not
modify resolv.conf. This implies
<literal>rc-manager</literal>&nbsp;<literal>unmanaged</literal></para>
<para>Note that the plugins <literal>dnsmasq</literal>, <literal>systemd-resolved</literal>
and <literal>unbound</literal> are caching local nameservers.
<para>Note that the plugins <literal>dnsmasq</literal> and <literal>systemd-resolved</literal>
are caching local nameservers.
Hence, when NetworkManager writes <filename>&nmrundir;/resolv.conf</filename>
and <filename>/etc/resolv.conf</filename> (according to <literal>rc-manager</literal>
setting below), the name server there will be localhost only.

View file

@ -683,18 +683,11 @@ endforeach
# external misc tools paths
default_paths = ['/sbin', '/usr/sbin']
dnssec_ts_paths = ['/usr/local/libexec',
'/usr/local/lib',
'/usr/local/lib/dnssec-trigger',
'/usr/libexec',
'/usr/lib',
'/usr/lib/dnssec-trigger']
# 0: cmdline option, 1: paths, 2: fallback
progs = [['iptables', default_paths, '/usr/sbin/iptables'],
['nft', default_paths, '/usr/sbin/nft'],
['dnsmasq', default_paths, ''],
['dnssec_trigger', dnssec_ts_paths, join_paths(nm_libexecdir, 'dnssec-trigger-script') ],
progs = [['iptables', default_paths, '/usr/sbin/iptables'],
['nft', default_paths, '/usr/sbin/nft'],
['dnsmasq', default_paths, ''],
]
foreach prog : progs

View file

@ -7,7 +7,6 @@ option('kernel_firmware_dir', type: 'string', value: '/lib/firmware', descriptio
option('iptables', type: 'string', value: '', description: 'path to iptables')
option('nft', type: 'string', value: '', description: 'path to nft')
option('dnsmasq', type: 'string', value: '', description: 'path to dnsmasq')
option('dnssec_trigger', type: 'string', value: '', description: 'path to unbound dnssec-trigger-script')
# platform
option('dist_version', type: 'string', value: '', description: 'Define the NM\'s distribution version string')

View file

@ -604,6 +604,7 @@ typedef struct _NMDevicePrivate {
const NMDeviceIPState state;
NMDeviceIPState state_;
};
gulong dnsmgr_update_pending_signal_id;
} ip_data;
union {
@ -2929,6 +2930,13 @@ _add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities)
/*****************************************************************************/
static void
_dev_ip_state_dnsmgr_update_pending_changed(NMDnsManager *dnsmgr, GParamSpec *pspec, NMDevice *self)
{
_dev_ip_state_check(self, AF_INET);
_dev_ip_state_check(self, AF_INET6);
}
static void
_dev_ip_state_req_timeout_cancel(NMDevice *self, int addr_family)
{
@ -3321,6 +3329,27 @@ got_ip_state:
combinedip_state = priv->ip_data.state;
}
if (combinedip_state == NM_DEVICE_IP_STATE_READY
&& priv->ip_data.state <= NM_DEVICE_IP_STATE_PENDING
&& nm_dns_manager_get_update_pending(nm_manager_get_dns_manager(priv->manager))) {
/* We would be ready, but a DNS update is pending. That prevents us from getting fully ready. */
if (priv->ip_data.dnsmgr_update_pending_signal_id == 0) {
priv->ip_data.dnsmgr_update_pending_signal_id =
g_signal_connect(nm_manager_get_dns_manager(priv->manager),
"notify::" NM_DNS_MANAGER_UPDATE_PENDING,
G_CALLBACK(_dev_ip_state_dnsmgr_update_pending_changed),
self);
_LOGT_ip(AF_UNSPEC,
"check-state: (combined) state: wait for DNS before becoming ready");
}
combinedip_state = NM_DEVICE_IP_STATE_PENDING;
}
if (combinedip_state != NM_DEVICE_IP_STATE_PENDING
&& priv->ip_data.dnsmgr_update_pending_signal_id != 0) {
nm_clear_g_signal_handler(nm_manager_get_dns_manager(priv->manager),
&priv->ip_data.dnsmgr_update_pending_signal_id);
}
_LOGT_ip(AF_UNSPEC,
"check-state: (combined) state %s => %s",
nm_device_ip_state_to_string(priv->ip_data.state),
@ -12329,7 +12358,8 @@ delete_on_deactivate_check_and_schedule(NMDevice *self)
static void
_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_dev_ipsharedx_cleanup(self, addr_family);
@ -12345,6 +12375,9 @@ _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gbool
_dev_ipmanual_cleanup(self);
nm_clear_g_signal_handler(nm_manager_get_dns_manager(priv->manager),
&priv->ip_data.dnsmgr_update_pending_signal_id);
_dev_ip_state_cleanup(self, AF_UNSPEC, from_reapply);
_dev_ip_state_cleanup(self, addr_family, from_reapply);
}

View file

@ -678,19 +678,23 @@ typedef struct {
char *name_owner;
GSource *main_timeout_source;
GSource *burst_retry_timeout_source;
gint64 burst_start_at;
GPid process_pid;
guint name_owner_changed_id;
guint main_timeout_id;
guint burst_retry_timeout_id;
guint8 burst_count;
bool is_stopped : 1;
bool set_server_ex_args_dirty : 1;
bool update_pending : 1;
} NMDnsDnsmasqPrivate;
struct _NMDnsDnsmasq {
@ -704,7 +708,8 @@ struct _NMDnsDnsmasqClass {
G_DEFINE_TYPE(NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN)
#define NM_DNS_DNSMASQ_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsDnsmasq, NM_IS_DNS_DNSMASQ)
#define NM_DNS_DNSMASQ_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMDnsDnsmasq, NM_IS_DNS_DNSMASQ, NMDnsPlugin)
/*****************************************************************************/
@ -717,6 +722,55 @@ static gboolean start_dnsmasq(NMDnsDnsmasq *self, gboolean force_start, GError *
/*****************************************************************************/
static gboolean
_update_pending_detect(NMDnsDnsmasq *self)
{
NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self);
if (priv->is_stopped)
return FALSE;
if (priv->main_timeout_source) {
/* we are waiting for dnsmasq to start. */
return TRUE;
}
if (priv->update_cancellable) {
/* An update is in progress. Busy. */
return TRUE;
}
if (priv->set_server_ex_args_dirty) {
/* the args just changed and were not yet sent. Busy. */
return TRUE;
}
return FALSE;
}
static void
_update_pending_maybe_changed(NMDnsDnsmasq *self)
{
NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self);
gboolean update_pending;
update_pending = _update_pending_detect(self);
if (priv->update_pending == update_pending)
return;
priv->update_pending = update_pending;
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
static gboolean
get_update_pending(NMDnsPlugin *plugin)
{
NMDnsDnsmasq *self = NM_DNS_DNSMASQ(plugin);
NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self);
nm_assert(priv->update_pending == _update_pending_detect(self));
return priv->update_pending;
}
/*****************************************************************************/
static void
add_dnsmasq_nameserver(NMDnsDnsmasq *self,
GVariantBuilder *servers,
@ -871,6 +925,7 @@ static void
dnsmasq_update_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
{
NMDnsDnsmasq *self;
NMDnsDnsmasqPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *response = NULL;
@ -880,10 +935,16 @@ dnsmasq_update_done(GObject *source_object, GAsyncResult *res, gpointer user_dat
return;
self = user_data;
priv = NM_DNS_DNSMASQ_GET_PRIVATE(self);
nm_clear_g_cancellable(&priv->update_cancellable);
if (!response)
_LOGW("dnsmasq update failed: %s", error->message);
else
_LOGD("dnsmasq update successful");
_update_pending_maybe_changed(self);
}
static void
@ -899,6 +960,8 @@ send_dnsmasq_update(NMDnsDnsmasq *self)
nm_clear_g_cancellable(&priv->update_cancellable);
priv->update_cancellable = g_cancellable_new();
priv->set_server_ex_args_dirty = FALSE;
g_dbus_connection_call(priv->dbus_connection,
priv->name_owner,
DNSMASQ_DBUS_PATH,
@ -911,6 +974,8 @@ send_dnsmasq_update(NMDnsDnsmasq *self)
priv->update_cancellable,
dnsmasq_update_done,
self);
_update_pending_maybe_changed(self);
}
/*****************************************************************************/
@ -928,17 +993,19 @@ _main_cleanup(NMDnsDnsmasq *self, gboolean emit_failed)
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
nm_clear_g_source(&priv->main_timeout_id);
nm_clear_g_source_inst(&priv->main_timeout_source);
nm_clear_g_cancellable(&priv->update_cancellable);
/* cancelling the main_cancellable will also cause _gl_pid_spawn*() to terminate the
* process in the background. */
nm_clear_g_cancellable(&priv->main_cancellable);
if (!priv->is_stopped && priv->burst_retry_timeout_id == 0) {
if (!priv->is_stopped && !priv->burst_retry_timeout_source) {
start_dnsmasq(self, FALSE, NULL);
send_dnsmasq_update(self);
}
_update_pending_maybe_changed(self);
}
static void
@ -961,8 +1028,10 @@ name_owner_changed(NMDnsDnsmasq *self, const char *name_owner)
}
_LOGT("D-Bus name for dnsmasq got owner %s", name_owner);
nm_clear_g_source(&priv->main_timeout_id);
nm_clear_g_source_inst(&priv->main_timeout_source);
send_dnsmasq_update(self);
_update_pending_maybe_changed(self);
}
static void
@ -1047,11 +1116,11 @@ _burst_retry_timeout_cb(gpointer user_data)
NMDnsDnsmasq *self = user_data;
NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self);
priv->burst_retry_timeout_id = 0;
nm_clear_g_source_inst(&priv->burst_retry_timeout_source);
start_dnsmasq(self, TRUE, NULL);
send_dnsmasq_update(self);
return G_SOURCE_REMOVE;
return G_SOURCE_CONTINUE;
}
static gboolean
@ -1090,33 +1159,35 @@ start_dnsmasq(NMDnsDnsmasq *self, gboolean force_start, GError **error)
|| priv->burst_start_at + RATELIMIT_INTERVAL_MSEC <= now) {
priv->burst_start_at = now;
priv->burst_count = 1;
nm_clear_g_source(&priv->burst_retry_timeout_id);
nm_clear_g_source_inst(&priv->burst_retry_timeout_source);
_LOGT("rate-limit: start burst interval of %d seconds %s",
RATELIMIT_INTERVAL_MSEC / 1000,
force_start ? " (force)" : "");
} else if (priv->burst_count < RATELIMIT_BURST) {
nm_assert(priv->burst_retry_timeout_id == 0);
nm_assert(!priv->burst_retry_timeout_source);
priv->burst_count++;
_LOGT("rate-limit: %u try within burst interval of %d seconds",
(guint) priv->burst_count,
RATELIMIT_INTERVAL_MSEC / 1000);
} else {
if (priv->burst_retry_timeout_id == 0) {
if (!priv->burst_retry_timeout_source) {
_LOGW("dnsmasq dies and gets respawned too quickly. Back off. Something is very wrong");
priv->burst_retry_timeout_id =
g_timeout_add_seconds((2 * RATELIMIT_INTERVAL_MSEC) / 1000,
_burst_retry_timeout_cb,
self);
priv->burst_retry_timeout_source =
nm_g_timeout_add_seconds_source((2 * RATELIMIT_INTERVAL_MSEC) / 1000,
_burst_retry_timeout_cb,
self);
} else
_LOGT("rate-limit: currently rate-limited from restart");
return TRUE;
}
priv->main_timeout_id = g_timeout_add(10000, spawn_timeout_cb, self);
priv->main_timeout_source = nm_g_timeout_add_source(10000, spawn_timeout_cb, self);
priv->main_cancellable = g_cancellable_new();
_gl_pid_spawn(dm_binary, priv->main_cancellable, spawn_notify, self);
_update_pending_maybe_changed(self);
return TRUE;
}
@ -1136,8 +1207,11 @@ update(NMDnsPlugin *plugin,
nm_clear_pointer(&priv->set_server_ex_args, g_variant_unref);
priv->set_server_ex_args =
g_variant_ref_sink(create_update_args(self, global_config, ip_data_lst_head, hostdomain));
priv->set_server_ex_args_dirty = TRUE;
send_dnsmasq_update(self);
_update_pending_maybe_changed(self);
return TRUE;
}
@ -1151,11 +1225,13 @@ stop(NMDnsPlugin *plugin)
priv->is_stopped = TRUE;
priv->burst_start_at = 0;
nm_clear_g_source(&priv->burst_retry_timeout_id);
nm_clear_g_source_inst(&priv->burst_retry_timeout_source);
/* Cancelling the cancellable will also terminate the
* process (in the background). */
_main_cleanup(self, FALSE);
_update_pending_maybe_changed(self);
}
/*****************************************************************************/
@ -1178,7 +1254,7 @@ dispose(GObject *object)
priv->is_stopped = TRUE;
nm_clear_g_source(&priv->burst_retry_timeout_id);
nm_clear_g_source_inst(&priv->burst_retry_timeout_source);
_main_cleanup(self, FALSE);
@ -1197,8 +1273,9 @@ nm_dns_dnsmasq_class_init(NMDnsDnsmasqClass *dns_class)
object_class->dispose = dispose;
plugin_class->plugin_name = "dnsmasq";
plugin_class->is_caching = TRUE;
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->plugin_name = "dnsmasq";
plugin_class->is_caching = TRUE;
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->get_update_pending = get_update_pending;
}

View file

@ -35,7 +35,6 @@
#include "nm-dns-dnsmasq.h"
#include "nm-dns-plugin.h"
#include "nm-dns-systemd-resolved.h"
#include "nm-dns-unbound.h"
#include "nm-ip-config.h"
#include "nm-l3-config-data.h"
#include "nm-manager.h"
@ -57,6 +56,8 @@
#define HAS_NETCONFIG 1
#endif
#define UPDATE_PENDING_UNBLOCK_TIMEOUT_MSEC 5000
/*****************************************************************************/
typedef enum { SR_SUCCESS, SR_NOTFOUND, SR_ERROR } SpawnResult;
@ -78,7 +79,11 @@ enum {
LAST_SIGNAL
};
NM_GOBJECT_PROPERTIES_DEFINE(NMDnsManager, PROP_MODE, PROP_RC_MANAGER, PROP_CONFIGURATION, );
NM_GOBJECT_PROPERTIES_DEFINE(NMDnsManager,
PROP_MODE,
PROP_RC_MANAGER,
PROP_CONFIGURATION,
PROP_UPDATE_PENDING, );
static guint signals[LAST_SIGNAL] = {0};
@ -89,6 +94,11 @@ typedef struct {
CList ip_data_lst_head;
GVariant *config_variant;
/* A DNS plugin should not be marked as pending indefinitely.
* We are only blocked if "update_pending" is TRUE and we have
* "update_pending_unblock" timer ticking. */
GSource *update_pending_unblock;
bool ip_data_lst_need_sort : 1;
bool configs_lst_need_sort : 1;
@ -98,6 +108,8 @@ typedef struct {
bool config_changed : 1;
bool update_pending : 1;
char *hostdomain;
guint updates_queue;
@ -109,6 +121,9 @@ typedef struct {
NMDnsPlugin *sd_resolve_plugin;
NMDnsPlugin *plugin;
gulong update_changed_signal_id_sd;
gulong update_changed_signal_id;
NMConfig *config;
struct {
@ -137,28 +152,23 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER
#define _NMLOG_PREFIX_NAME "dns-mgr"
#define _NMLOG_DOMAIN LOGD_DNS
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel __level = (level); \
\
if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
char __prefix[20]; \
const NMDnsManager *const __self = (self); \
\
_nm_log(__level, \
_NMLOG_DOMAIN, \
0, \
NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
((!__self || __self == singleton_instance) \
? "" \
: nm_sprintf_buf(__prefix, "[%p]", __self)) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel __level = (level); \
\
if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
_nm_unused const NMDnsManager *const __self = (self); \
\
_nm_log(__level, \
_NMLOG_DOMAIN, \
0, \
NULL, \
NULL, \
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
@ -207,6 +217,85 @@ static NM_UTILS_LOOKUP_STR_DEFINE(
/*****************************************************************************/
static gboolean
_update_pending_detect(NMDnsManager *self)
{
NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
if (priv->plugin && nm_dns_plugin_get_update_pending(priv->plugin))
return TRUE;
if (priv->sd_resolve_plugin && nm_dns_plugin_get_update_pending(priv->sd_resolve_plugin))
return TRUE;
return FALSE;
}
static gboolean
_update_pending_unblock_cb(gpointer user_data)
{
NMDnsManager *self = user_data;
NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
nm_assert(priv->update_pending);
nm_assert(priv->update_pending_unblock);
nm_assert(_update_pending_detect(self));
nm_clear_g_source_inst(&priv->update_pending_unblock);
_LOGW(
"update-pending changed: DNS plugin did not become ready again. Assume something is wrong");
_notify(self, PROP_UPDATE_PENDING);
return G_SOURCE_CONTINUE;
}
static void
_update_pending_maybe_changed(NMDnsManager *self)
{
NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
gboolean update_pending;
update_pending = _update_pending_detect(self);
if (priv->update_pending == update_pending)
return;
if (update_pending) {
nm_assert(!priv->update_pending_unblock);
priv->update_pending_unblock = nm_g_timeout_add_source(UPDATE_PENDING_UNBLOCK_TIMEOUT_MSEC,
_update_pending_unblock_cb,
self);
} else
nm_clear_g_source_inst(&priv->update_pending_unblock);
priv->update_pending = update_pending;
_LOGD("update-pending changed: %spending", update_pending ? "" : "not ");
_notify(self, PROP_UPDATE_PENDING);
}
static void
_update_pending_changed_cb(NMDnsPlugin *plugin, gboolean update_pending, NMDnsManager *self)
{
_update_pending_maybe_changed(self);
}
gboolean
nm_dns_manager_get_update_pending(NMDnsManager *self)
{
NMDnsManagerPrivate *priv;
g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE);
priv = NM_DNS_MANAGER_GET_PRIVATE(self);
nm_assert(priv->update_pending == _update_pending_detect(self));
nm_assert(priv->update_pending || !priv->update_pending_unblock);
/* update-pending can only be TRUE for a certain time (before we assume
* something is really wrong with the plugin). That is, as long as
* update_pending_unblock is ticking. */
return !!priv->update_pending_unblock;
}
/*****************************************************************************/
static int
_dns_config_ip_data_get_dns_priority1(const NML3ConfigData *l3cd, int addr_family)
{
@ -2120,6 +2209,7 @@ _clear_plugin(NMDnsManager *self)
nm_clear_g_source(&priv->plugin_ratelimit.timer);
if (priv->plugin) {
nm_clear_g_signal_handler(priv->plugin, &priv->update_changed_signal_id);
nm_dns_plugin_stop(priv->plugin);
g_clear_object(&priv->plugin);
return TRUE;
@ -2127,6 +2217,20 @@ _clear_plugin(NMDnsManager *self)
return FALSE;
}
static gboolean
_clear_sd_resolved_plugin(NMDnsManager *self)
{
NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
if (priv->sd_resolve_plugin) {
nm_clear_g_signal_handler(priv->sd_resolve_plugin, &priv->update_changed_signal_id_sd);
nm_dns_plugin_stop(priv->sd_resolve_plugin);
g_clear_object(&priv->sd_resolve_plugin);
return TRUE;
}
return FALSE;
}
static NMDnsManagerResolvConfManager
_check_resconf_immutable(NMDnsManagerResolvConfManager rc_manager)
{
@ -2313,16 +2417,14 @@ again:
priv->plugin = nm_dns_dnsmasq_new();
plugin_changed = TRUE;
}
} else if (nm_streq0(mode, "unbound")) {
if (force_reload_plugin || !NM_IS_DNS_UNBOUND(priv->plugin)) {
_clear_plugin(self);
priv->plugin = nm_dns_unbound_new();
plugin_changed = TRUE;
}
} else {
if (!NM_IN_STRSET(mode, "none", "default")) {
if (mode)
_LOGW("init: unknown dns mode '%s'", mode);
if (mode) {
if (nm_streq(mode, "unbound"))
_LOGW("init: ns mode 'unbound' was removed. Update your configuration");
else
_LOGW("init: unknown dns mode '%s'", mode);
}
mode = "default";
}
if (_clear_plugin(self))
@ -2359,7 +2461,7 @@ again:
priv->sd_resolve_plugin = nm_dns_systemd_resolved_new();
systemd_resolved_changed = TRUE;
}
} else if (nm_clear_g_object(&priv->sd_resolve_plugin))
} else if (_clear_sd_resolved_plugin(self))
systemd_resolved_changed = TRUE;
g_object_freeze_notify(G_OBJECT(self));
@ -2390,6 +2492,23 @@ again:
""));
}
if (plugin_changed && priv->plugin && priv->update_changed_signal_id == 0) {
priv->update_changed_signal_id = g_signal_connect(priv->plugin,
NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED,
G_CALLBACK(_update_pending_changed_cb),
self);
}
if (systemd_resolved_changed && priv->sd_resolve_plugin
&& priv->update_changed_signal_id_sd == 0) {
priv->update_changed_signal_id_sd = g_signal_connect(priv->sd_resolve_plugin,
NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED,
G_CALLBACK(_update_pending_changed_cb),
self);
}
_update_pending_maybe_changed(self);
g_object_thaw_notify(G_OBJECT(self));
}
@ -2594,6 +2713,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
case PROP_CONFIGURATION:
g_value_set_variant(value, _get_config_variant(self));
break;
case PROP_UPDATE_PENDING:
g_value_set_boolean(value, nm_dns_manager_get_update_pending(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@ -2641,9 +2763,11 @@ dispose(GObject *object)
if (priv->config)
g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self);
g_clear_object(&priv->sd_resolve_plugin);
_clear_sd_resolved_plugin(self);
_clear_plugin(self);
nm_clear_g_source_inst(&priv->update_pending_unblock);
c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_data_lst_head, ip_data_lst)
_dns_config_ip_data_free(ip_data);
@ -2719,6 +2843,13 @@ nm_dns_manager_class_init(NMDnsManagerClass *klass)
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_UPDATE_PENDING] =
g_param_spec_boolean(NM_DNS_MANAGER_UPDATE_PENDING,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
signals[CONFIG_CHANGED] = g_signal_new(NM_DNS_MANAGER_CONFIG_CHANGED,

View file

@ -80,9 +80,10 @@ typedef struct _NMDnsConfigData {
(G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_DNS_MANAGER, NMDnsManagerClass))
/* properties */
#define NM_DNS_MANAGER_MODE "mode"
#define NM_DNS_MANAGER_RC_MANAGER "rc-manager"
#define NM_DNS_MANAGER_CONFIGURATION "configuration"
#define NM_DNS_MANAGER_MODE "mode"
#define NM_DNS_MANAGER_RC_MANAGER "rc-manager"
#define NM_DNS_MANAGER_CONFIGURATION "configuration"
#define NM_DNS_MANAGER_UPDATE_PENDING "update-pending"
/* internal signals */
#define NM_DNS_MANAGER_CONFIG_CHANGED "config-changed"
@ -149,6 +150,8 @@ void nm_dns_manager_stop(NMDnsManager *self);
NMDnsPlugin *nm_dns_manager_get_systemd_resolved(NMDnsManager *self);
gboolean nm_dns_manager_get_update_pending(NMDnsManager *self);
/*****************************************************************************/
char *nmtst_dns_create_resolv_conf(const char *const *searches,

View file

@ -17,11 +17,16 @@
/*****************************************************************************/
enum {
UPDATE_PENDING_CHANGED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
typedef struct _NMDnsPluginPrivate {
GPid pid;
guint watch_id;
char *progname;
char *pidfile;
bool update_pending_inited : 1;
bool update_pending : 1;
} NMDnsPluginPrivate;
G_DEFINE_ABSTRACT_TYPE(NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT)
@ -32,26 +37,29 @@ G_DEFINE_ABSTRACT_TYPE(NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT)
#define _NMLOG_PREFIX_NAME "dns-plugin"
#define _NMLOG_DOMAIN LOGD_DNS
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel __level = (level); \
\
if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
char __prefix[20]; \
const NMDnsPlugin *const __self = (self); \
\
_nm_log(__level, \
_NMLOG_DOMAIN, \
0, \
NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
(!__self ? "" : nm_sprintf_buf(__prefix, "[%p]", __self)) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel __level = (level); \
\
if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
char __prefix[20]; \
const NMDnsPlugin *const __self = (self); \
\
_nm_log(__level, \
_NMLOG_DOMAIN, \
0, \
NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
(!__self ? "" \
: nm_sprintf_buf(__prefix, \
"[" NM_HASH_OBFUSCATE_PTR_FMT "]", \
NM_HASH_OBFUSCATE_PTR( \
__self))) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
@ -104,10 +112,109 @@ nm_dns_plugin_stop(NMDnsPlugin *self)
/*****************************************************************************/
static void
nm_dns_plugin_init(NMDnsPlugin *self)
{}
static gboolean
_get_update_pending(NMDnsPlugin *self)
{
NMDnsPluginClass *klass;
nm_assert(NM_IS_DNS_PLUGIN(self));
klass = NM_DNS_PLUGIN_GET_CLASS(self);
if (klass->get_update_pending) {
if (klass->get_update_pending(self))
return TRUE;
}
return FALSE;
}
gboolean
nm_dns_plugin_get_update_pending(NMDnsPlugin *self)
{
NMDnsPluginPrivate *priv;
g_return_val_if_fail(NM_IS_DNS_PLUGIN(self), FALSE);
priv = NM_DNS_PLUGIN_GET_PRIVATE(self);
/* We cache the boolean and rely on the subclass to call
* _nm_dns_plugin_update_pending_maybe_changed(). The subclass
* anyway must get it right to notify us when the value (maybe)
* changes. By caching the value, the subclass is free to notify
* even if the value did not actually change.
*
* Also, this allows the base implementation to combine multiple
* sources/reasons (if we need that in the future). */
if (!priv->update_pending_inited) {
priv->update_pending_inited = TRUE;
priv->update_pending = _get_update_pending(self);
_LOGD("[%s] update-pending changed (%spending)",
nm_dns_plugin_get_name(self),
priv->update_pending ? "" : "not ");
} else
nm_assert(priv->update_pending == _get_update_pending(self));
return priv->update_pending;
}
void
_nm_dns_plugin_update_pending_maybe_changed(NMDnsPlugin *self)
{
NMDnsPluginPrivate *priv;
gboolean v;
g_return_if_fail(NM_IS_DNS_PLUGIN(self));
priv = NM_DNS_PLUGIN_GET_PRIVATE(self);
v = _get_update_pending(self);
if (!priv->update_pending_inited)
priv->update_pending_inited = TRUE;
else if (priv->update_pending == v)
return;
priv->update_pending = v;
_LOGD("[%s] update-pending changed (%spending)",
nm_dns_plugin_get_name(self),
priv->update_pending ? "" : "not ");
g_signal_emit(self, signals[UPDATE_PENDING_CHANGED], 0, (gboolean) priv->update_pending);
}
/*****************************************************************************/
static void
nm_dns_plugin_class_init(NMDnsPluginClass *plugin_class)
{}
nm_dns_plugin_init(NMDnsPlugin *self)
{
NMDnsPluginPrivate *priv;
priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DNS_PLUGIN, NMDnsPluginPrivate);
self->_priv = priv;
nm_assert(priv->update_pending_inited == FALSE);
nm_assert(priv->update_pending == FALSE);
nm_shutdown_wait_obj_register_object(self, "dns-plugin");
}
static void
nm_dns_plugin_class_init(NMDnsPluginClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
g_type_class_add_private(object_class, sizeof(NMDnsPluginPrivate));
signals[UPDATE_PENDING_CHANGED] = g_signal_new(NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED,
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
}

View file

@ -19,8 +19,13 @@
#define NM_DNS_PLUGIN_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass))
#define NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED "update-pending-changed"
struct _NMDnsPluginPrivate;
typedef struct {
GObject parent;
GObject parent;
struct _NMDnsPluginPrivate *_priv;
} NMDnsPlugin;
typedef struct {
@ -39,6 +44,8 @@ typedef struct {
void (*stop)(NMDnsPlugin *self);
gboolean (*get_update_pending)(NMDnsPlugin *self);
const char *plugin_name;
/* Types should set to TRUE if they start a local caching nameserver
@ -63,4 +70,8 @@ gboolean nm_dns_plugin_update(NMDnsPlugin *self,
void nm_dns_plugin_stop(NMDnsPlugin *self);
gboolean nm_dns_plugin_get_update_pending(NMDnsPlugin *self);
void _nm_dns_plugin_update_pending_maybe_changed(NMDnsPlugin *self);
#endif /* __NM_DNS_PLUGIN_H__ */

View file

@ -40,8 +40,8 @@ static const char *const DBUS_OP_SET_LINK_DNS_OVER_TLS = "SetLinkDNSOverTLS";
/*****************************************************************************/
typedef struct {
int ifindex;
CList configs_lst_head;
int ifindex;
GPtrArray *ip_data_list;
} InterfaceConfig;
typedef struct {
@ -50,6 +50,7 @@ typedef struct {
GVariant *argument;
NMDnsSystemdResolved *self;
int ifindex;
int ref_count;
} RequestItem;
struct _NMDnsSystemdResolvedResolveHandle {
@ -82,10 +83,13 @@ typedef struct {
char *dbus_owner;
CList handle_lst_head;
guint name_owner_changed_id;
guint n_pending;
bool send_updates_warn_ratelimited : 1;
bool try_start_blocked : 1;
bool stopped : 1;
bool dbus_initied : 1;
bool send_updates_waiting : 1;
bool update_pending : 1;
/* These two variables ensure that the log is not spammed with
* API (not) supported messages.
* They can be removed when no distro uses systemd-resolved < v240 anymore
@ -106,7 +110,7 @@ struct _NMDnsSystemdResolvedClass {
G_DEFINE_TYPE(NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN)
#define NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED)
_NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED, NMDnsPlugin)
/*****************************************************************************/
@ -146,10 +150,88 @@ static void _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResol
/*****************************************************************************/
static void
_request_item_free(RequestItem *request_item)
static gboolean
_update_pending_detect(NMDnsSystemdResolved *self)
{
c_list_unlink_stale(&request_item->request_queue_lst);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
if (priv->n_pending > 0) {
/* we have pending calls. We definitely want to wait for them to complete. */
return TRUE;
}
if (!priv->dbus_initied) {
if (!priv->dbus_connection)
return FALSE;
/* D-Bus not yet initialized (and we don't know the name owner yet). Pending. */
return TRUE;
}
if (priv->try_start_timeout_source) {
/* We are waiting to D-Bus activate resolved. Pending. */
return TRUE;
}
if (priv->try_start_blocked) {
/* We earlier tried to start resolved, but are rate limited. We are not pending an update
* (that we expect to complete any time soon). */
return FALSE;
}
if (priv->send_updates_waiting) {
/* we wait to send updates. We are pending. */
return TRUE;
}
return FALSE;
}
static void
_update_pending_maybe_changed(NMDnsSystemdResolved *self)
{
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
gboolean update_pending;
/* Important: we need to make sure that we call _update_pending_maybe_changed(), when
* the state changes. */
update_pending = _update_pending_detect(self);
if (priv->update_pending != update_pending) {
priv->update_pending = update_pending;
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
}
static gboolean
get_update_pending(NMDnsPlugin *plugin)
{
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
nm_assert(priv->update_pending == _update_pending_detect(self));
return priv->update_pending;
}
/*****************************************************************************/
static RequestItem *
_request_item_ref(RequestItem *request_item)
{
nm_assert(request_item);
nm_assert(request_item->ref_count > 0);
nm_assert(request_item->ref_count < G_MAXINT);
nm_assert(!c_list_is_empty(&request_item->request_queue_lst));
request_item->ref_count++;
return request_item;
}
static void
_request_item_unref(RequestItem *request_item)
{
nm_assert(request_item);
nm_assert(request_item->ref_count > 0);
if (--request_item->ref_count > 0)
return;
nm_assert(c_list_is_empty(&request_item->request_queue_lst));
g_variant_unref(request_item->argument);
nm_g_slice_free(request_item);
}
@ -165,6 +247,7 @@ _request_item_append(NMDnsSystemdResolved *self,
request_item = g_slice_new(RequestItem);
*request_item = (RequestItem){
.ref_count = 1,
.operation = operation,
.argument = g_variant_ref_sink(argument),
.self = self,
@ -178,8 +261,8 @@ _request_item_append(NMDnsSystemdResolved *self,
static void
_interface_config_free(InterfaceConfig *config)
{
nm_c_list_elem_free_all(&config->configs_lst_head, NULL);
g_slice_free(InterfaceConfig, config);
nm_g_ptr_array_unref(config->ip_data_list);
nm_g_slice_free(config);
}
static void
@ -191,42 +274,48 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
NMDnsSystemdResolvedPrivate *priv;
RequestItem *request_item;
NMLogLevel log_level;
v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
if (nm_utils_error_is_cancelled(error))
return;
const char *operation;
int ifindex;
request_item = user_data;
self = request_item->self;
priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
operation = request_item->operation;
ifindex = request_item->ifindex;
_request_item_unref(request_item);
priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
if (nm_utils_error_is_cancelled(error))
goto out_dec_pending;
if (v) {
if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
&& priv->has_link_default_route == NM_TERNARY_DEFAULT) {
priv->has_link_default_route = NM_TERNARY_TRUE;
_LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported");
}
if (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
&& priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT) {
priv->has_link_dns_over_tls = NM_TERNARY_TRUE;
_LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API supported");
}
priv->send_updates_warn_ratelimited = FALSE;
return;
goto out_dec_pending;
}
if (nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
if (priv->has_link_default_route == NM_TERNARY_DEFAULT
&& request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
&& operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
priv->has_link_default_route = NM_TERNARY_FALSE;
_LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
}
if (priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT
&& request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
&& operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
priv->has_link_dns_over_tls = NM_TERNARY_FALSE;
_LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API not supported");
}
return;
goto out_dec_pending;
}
log_level = LOGL_DEBUG;
@ -234,18 +323,25 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
priv->send_updates_warn_ratelimited = TRUE;
log_level = LOGL_WARN;
}
_NMLOG(log_level,
"send-updates %s@%d failed: %s",
request_item->operation,
request_item->ifindex,
error->message);
_NMLOG(log_level, "send-updates %s@%d failed: %s", operation, ifindex, error->message);
out_dec_pending:
nm_assert(priv->n_pending > 0);
if (--priv->n_pending <= 0) {
_update_pending_maybe_changed(self);
/* We keep @self alive while pending operations are in progress. It's simpler
* to implement. But this requires that we implement "stop()" signal to cancel
* all pending requests. Cancelling is necessary, because during shutdown,
* we must wrap up fast, and not hang an undefined amount time. */
g_object_unref(self);
}
}
static gboolean
update_add_ip_config(NMDnsSystemdResolved *self,
GVariantBuilder *dns,
GVariantBuilder *domains,
NMDnsConfigIPData *ip_data)
update_add_ip_config(NMDnsSystemdResolved *self,
GVariantBuilder *dns,
GVariantBuilder *domains,
const NMDnsConfigIPData *ip_data)
{
gsize addr_size;
guint n;
@ -258,8 +354,12 @@ update_add_ip_config(NMDnsSystemdResolved *self,
addr_size = nm_utils_addr_family_to_size(ip_data->addr_family);
if ((!ip_data->domains.search || !ip_data->domains.search[0])
&& !ip_data->domains.has_default_route_exclusive && !ip_data->domains.has_default_route)
&& !ip_data->domains.has_default_route_exclusive && !ip_data->domains.has_default_route) {
/* we have no search domain (which systemd-resolved uses to routing the request), but
* also the "DefaultRoute" is not set on the interface. This setting has no effect and
* gets ignored. */
return FALSE;
}
nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n);
for (i = 0; i < n; i++) {
@ -295,23 +395,28 @@ free_pending_updates(NMDnsSystemdResolved *self)
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
RequestItem *request_item;
while ((request_item =
c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst)))
_request_item_free(request_item);
while (
(request_item =
c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) {
c_list_unlink(&request_item->request_queue_lst);
_request_item_unref(request_item);
}
}
static gboolean
prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
{
GVariantBuilder dns;
GVariantBuilder domains;
NMCListElem *elem;
NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
const char *mdns_arg = NULL, *llmnr_arg = NULL, *dns_over_tls_arg = NULL;
NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
const char *mdns_arg = NULL;
const char *llmnr_arg = NULL;
const char *dns_over_tls_arg = NULL;
gboolean has_config = FALSE;
gboolean has_default_route = FALSE;
guint i;
g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
g_variant_builder_add(&dns, "i", ic->ifindex);
@ -321,18 +426,22 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
g_variant_builder_add(&domains, "i", ic->ifindex);
g_variant_builder_open(&domains, G_VARIANT_TYPE("a(sb)"));
c_list_for_each_entry (elem, &ic->configs_lst_head, lst) {
NMDnsConfigIPData *ip_data = elem->data;
if (ic->ip_data_list) {
for (i = 0; i < ic->ip_data_list->len; i++) {
const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i];
has_config |= update_add_ip_config(self, &dns, &domains, ip_data);
if (update_add_ip_config(self, &dns, &domains, ip_data))
has_config = TRUE;
if (ip_data->domains.has_default_route)
has_default_route = TRUE;
if (ip_data->domains.has_default_route)
has_default_route = TRUE;
if (NM_IS_IPv4(ip_data->addr_family)) {
mdns = NM_MAX(mdns, nm_l3_config_data_get_mdns(ip_data->l3cd));
llmnr = NM_MAX(llmnr, nm_l3_config_data_get_llmnr(ip_data->l3cd));
dns_over_tls = NM_MAX(dns_over_tls, nm_l3_config_data_get_dns_over_tls(ip_data->l3cd));
if (NM_IS_IPv4(ip_data->addr_family)) {
mdns = NM_MAX(mdns, nm_l3_config_data_get_mdns(ip_data->l3cd));
llmnr = NM_MAX(llmnr, nm_l3_config_data_get_llmnr(ip_data->l3cd));
dns_over_tls =
NM_MAX(dns_over_tls, nm_l3_config_data_get_dns_over_tls(ip_data->l3cd));
}
}
}
@ -439,6 +548,7 @@ again:
goto again;
}
_update_pending_maybe_changed(self);
return G_SOURCE_CONTINUE;
}
@ -447,6 +557,9 @@ ensure_resolved_running(NMDnsSystemdResolved *self)
{
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
if (priv->stopped)
return NM_TERNARY_FALSE;
if (!priv->dbus_initied)
return NM_TERNARY_DEFAULT;
@ -469,6 +582,7 @@ ensure_resolved_running(NMDnsSystemdResolved *self)
NULL,
NULL,
NULL);
_update_pending_maybe_changed(self);
return NM_TERNARY_DEFAULT;
}
@ -523,6 +637,12 @@ send_updates(NMDnsSystemdResolved *self)
request_item->operation,
(ss = g_variant_print(request_item->argument, FALSE)));
if (priv->n_pending++ == 0) {
/* We are inside send_updates(). All callers are already calling
* _update_pending_maybe_changed() afterwards. */
g_object_ref(self);
}
g_dbus_connection_call(priv->dbus_connection,
priv->dbus_owner,
SYSTEMD_RESOLVED_DBUS_PATH,
@ -534,7 +654,7 @@ send_updates(NMDnsSystemdResolved *self)
-1,
priv->cancellable,
call_done,
request_item);
_request_item_ref(request_item));
}
start_resolve:
@ -554,43 +674,54 @@ update(NMDnsPlugin *plugin,
const char *hostdomain,
GError **error)
{
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
gs_unref_hashtable GHashTable *interfaces = NULL;
gs_free gpointer *interfaces_keys = NULL;
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
gs_unref_hashtable GHashTable *interfaces = NULL;
const NMUtilsNamedValue *interfaces_arr;
NMUtilsNamedValue interfaces_arr_stack[50];
gs_free NMUtilsNamedValue *interfaces_arr_heap = NULL;
guint interfaces_len;
int ifindex;
gpointer pointer;
NMDnsConfigIPData *ip_data;
GHashTableIter iter;
gs_unref_array GArray *dirty_array = NULL;
guint i;
nm_assert(!priv->stopped);
/* Group configs by ifindex/interfaces. */
interfaces =
g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free);
c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) {
InterfaceConfig *ic = NULL;
InterfaceConfig *ic = NULL;
int ifindex = ip_data->data->ifindex;
ifindex = ip_data->data->ifindex;
nm_assert(ifindex == nm_l3_config_data_get_ifindex(ip_data->l3cd));
ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex));
if (!ic) {
ic = g_slice_new(InterfaceConfig);
ic->ifindex = ifindex;
c_list_init(&ic->configs_lst_head);
ic = g_slice_new(InterfaceConfig);
*ic = (InterfaceConfig){
.ifindex = ifindex,
.ip_data_list = g_ptr_array_sized_new(4),
};
g_hash_table_insert(interfaces, GINT_TO_POINTER(ifindex), ic);
}
c_list_link_tail(&ic->configs_lst_head, &nm_c_list_elem_new_stale(ip_data)->lst);
g_ptr_array_add(ic->ip_data_list, ip_data);
}
free_pending_updates(self);
interfaces_keys =
nm_utils_hash_keys_to_array(interfaces, nm_cmp_int2ptr_p_with_data, NULL, &interfaces_len);
interfaces_arr = nm_utils_hash_to_array_with_buffer(interfaces,
&interfaces_len,
nm_cmp_int2ptr_p_with_data,
NULL,
interfaces_arr_stack,
&interfaces_arr_heap);
for (i = 0; i < interfaces_len; i++) {
InterfaceConfig *ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(interfaces_keys[i]));
const InterfaceConfig *ic = interfaces_arr[i].value_ptr;
if (prepare_one_interface(self, ic))
g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
@ -602,23 +733,38 @@ update(NMDnsPlugin *plugin,
* resolved, and the current update doesn't contain that interface,
* reset the resolved configuration for that ifindex. */
g_hash_table_iter_init(&iter, priv->dirty_interfaces);
while (g_hash_table_iter_next(&iter, (gpointer *) &pointer, NULL)) {
ifindex = GPOINTER_TO_INT(pointer);
if (!g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) {
while (g_hash_table_iter_next(&iter, &pointer, NULL)) {
int ifindex = GPOINTER_TO_INT(pointer);
if (g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) {
/* the interface is still tracked and still dirty. Keep. */
continue;
}
if (!dirty_array)
dirty_array = g_array_new(FALSE, FALSE, sizeof(int));
g_array_append_val(dirty_array, ifindex);
g_hash_table_iter_remove(&iter);
}
if (dirty_array) {
g_array_sort_with_data(dirty_array, nm_cmp_int2ptr_p_with_data, NULL);
for (i = 0; i < dirty_array->len; i++) {
int ifindex = g_array_index(dirty_array, int, i);
InterfaceConfig ic;
_LOGT("clear previously configured ifindex %d", ifindex);
ic = (InterfaceConfig){
.ifindex = ifindex,
.configs_lst_head = C_LIST_INIT(ic.configs_lst_head),
.ifindex = ifindex,
.ip_data_list = NULL,
};
prepare_one_interface(self, &ic);
g_hash_table_iter_remove(&iter);
}
}
priv->send_updates_waiting = TRUE;
send_updates(self);
_update_pending_maybe_changed(self);
return TRUE;
}
@ -649,6 +795,7 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
}
send_updates(self);
_update_pending_maybe_changed(self);
}
static void
@ -956,6 +1103,48 @@ nm_dns_systemd_resolved_resolve_cancel(NMDnsSystemdResolvedResolveHandle *handle
/*****************************************************************************/
static void
stop(NMDnsPlugin *plugin)
{
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
NMDnsSystemdResolvedResolveHandle *handle;
/* This function must be re-entrant!!
*
* Currently there is no concept of unregistering/shutting down. It's not
* clear whether we should de-configure anything in systemd-resolved, we
* don't.
*
* Implementing stop() is important because pending operations take a
* reference on @self. We can only cancel (fast shutdown) the instance
* by cancelling those requests. */
priv->stopped = TRUE;
priv->try_start_blocked = TRUE;
nm_clear_g_cancellable(&priv->cancellable);
nm_clear_g_free(&priv->dbus_owner);
while ((handle = c_list_first_entry(&priv->handle_lst_head,
NMDnsSystemdResolvedResolveHandle,
handle_lst))) {
gs_free_error GError *error = NULL;
nm_utils_error_set_cancelled(&error, TRUE, "NMDnsSystemdResolved");
_resolve_complete_error(handle, error);
}
free_pending_updates(self);
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
nm_clear_g_source_inst(&priv->try_start_timeout_source);
}
/*****************************************************************************/
static void
nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
{
@ -974,6 +1163,8 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
return;
}
priv->update_pending = TRUE;
priv->name_owner_changed_id =
nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
SYSTEMD_RESOLVED_DBUS_SERVICE,
@ -998,33 +1189,15 @@ nm_dns_systemd_resolved_new(void)
static void
dispose(GObject *object)
{
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(object);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
NMDnsSystemdResolvedResolveHandle *handle;
NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(object);
NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
while ((handle = c_list_first_entry(&priv->handle_lst_head,
NMDnsSystemdResolvedResolveHandle,
handle_lst))) {
gs_free_error GError *error = NULL;
nm_utils_error_set_cancelled(&error, TRUE, "NMDnsSystemdResolved");
_resolve_complete_error(handle, error);
}
free_pending_updates(self);
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
nm_clear_g_cancellable(&priv->cancellable);
nm_clear_g_source_inst(&priv->try_start_timeout_source);
stop(NM_DNS_PLUGIN(self));
g_clear_object(&priv->dbus_connection);
nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref);
nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_destroy);
G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object);
nm_clear_g_free(&priv->dbus_owner);
}
static void
@ -1035,7 +1208,9 @@ nm_dns_systemd_resolved_class_init(NMDnsSystemdResolvedClass *dns_class)
object_class->dispose = dispose;
plugin_class->plugin_name = "systemd-resolved";
plugin_class->is_caching = TRUE;
plugin_class->update = update;
plugin_class->plugin_name = "systemd-resolved";
plugin_class->is_caching = TRUE;
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->get_update_pending = get_update_pending;
}

View file

@ -1,84 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2014 Red Hat, Inc.
* Author: Pavel Šimerda <psimerda@redhat.com>
*/
#include "src/core/nm-default-daemon.h"
#include "nm-dns-unbound.h"
#include "NetworkManagerUtils.h"
/*****************************************************************************/
struct _NMDnsUnbound {
NMDnsPlugin parent;
};
struct _NMDnsUnboundClass {
NMDnsPluginClass parent;
};
G_DEFINE_TYPE(NMDnsUnbound, nm_dns_unbound, NM_TYPE_DNS_PLUGIN)
/*****************************************************************************/
static gboolean
update(NMDnsPlugin *plugin,
const NMGlobalDnsConfig *global_config,
const CList *ip_config_lst_head,
const char *hostdomain,
GError **error)
{
char *argv[] = {DNSSEC_TRIGGER_PATH, "--async", "--update", NULL};
gs_free_error GError *local = NULL;
int status;
/* TODO: We currently call a script installed with the dnssec-trigger
* package that queries all information itself. Later, the dependency
* on that package will be optional and the only hard dependency will
* be unbound.
*
* Unbound configuration should be later handled by this plugin directly,
* without calling custom scripts. The dnssec-trigger functionality
* may be eventually merged into NetworkManager.
*/
if (!g_spawn_sync("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, &local)) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
"error spawning dns-trigger: %s",
local->message);
return FALSE;
}
if (status != 0) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
"dns-trigger exited with error code %d",
status);
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static void
nm_dns_unbound_init(NMDnsUnbound *unbound)
{}
NMDnsPlugin *
nm_dns_unbound_new(void)
{
return g_object_new(NM_TYPE_DNS_UNBOUND, NULL);
}
static void
nm_dns_unbound_class_init(NMDnsUnboundClass *klass)
{
NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS(klass);
plugin_class->plugin_name = "unbound";
plugin_class->is_caching = TRUE;
plugin_class->update = update;
}

View file

@ -1,27 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2014 Red Hat, Inc.
*/
#ifndef __NETWORKMANAGER_DNS_UNBOUND_H__
#define __NETWORKMANAGER_DNS_UNBOUND_H__
#include "nm-dns-plugin.h"
#define NM_TYPE_DNS_UNBOUND (nm_dns_unbound_get_type())
#define NM_DNS_UNBOUND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_UNBOUND, NMDnsUnbound))
#define NM_DNS_UNBOUND_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_UNBOUND, NMDnsUnboundClass))
#define NM_IS_DNS_UNBOUND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_UNBOUND))
#define NM_IS_DNS_UNBOUND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_UNBOUND))
#define NM_DNS_UNBOUND_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_UNBOUND, NMDnsUnboundClass))
typedef struct _NMDnsUnbound NMDnsUnbound;
typedef struct _NMDnsUnboundClass NMDnsUnboundClass;
GType nm_dns_unbound_get_type(void);
NMDnsPlugin *nm_dns_unbound_new(void);
#endif /* __NETWORKMANAGER_DNS_UNBOUND_H__ */

View file

@ -123,7 +123,6 @@ libNetworkManager = static_library(
'dns/nm-dns-manager.c',
'dns/nm-dns-plugin.c',
'dns/nm-dns-systemd-resolved.c',
'dns/nm-dns-unbound.c',
'dnsmasq/nm-dnsmasq-manager.c',
'dnsmasq/nm-dnsmasq-utils.c',
'ppp/nm-ppp-manager-call.c',

View file

@ -20,6 +20,7 @@
#include "devices/nm-device-factory.h"
#include "devices/nm-device-generic.h"
#include "devices/nm-device.h"
#include "dns/nm-dns-manager.h"
#include "dhcp/nm-dhcp-manager.h"
#include "libnm-core-aux-intern/nm-common-macros.h"
#include "libnm-core-intern/nm-core-internal.h"
@ -144,6 +145,9 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMManager,
typedef struct {
NMPlatform *platform;
NMDnsManager *dns_mgr;
gulong dns_mgr_update_pending_signal_id;
GArray *capabilities;
CList active_connections_lst_head; /* Oldest ACs at the beginning */
@ -346,6 +350,9 @@ static NMActiveConnection *_new_active_connection(NMManager *self,
static void policy_activating_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data);
static void device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self);
static void check_if_startup_complete(NMManager *self);
static gboolean find_master(NMManager *self,
NMConnection *connection,
NMDevice *device,
@ -1601,7 +1608,11 @@ manager_device_state_changed(NMDevice *device,
nm_settings_device_added(priv->settings, device);
}
static void device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self);
static void
_dns_mgr_update_pending_cb(NMDevice *device, GParamSpec *pspec, NMManager *self)
{
check_if_startup_complete(self);
}
static void
check_if_startup_complete(NMManager *self)
@ -1616,6 +1627,20 @@ check_if_startup_complete(NMManager *self)
if (!priv->devices_inited)
return;
if (nm_dns_manager_get_update_pending(nm_manager_get_dns_manager(self))) {
if (priv->dns_mgr_update_pending_signal_id == 0) {
priv->dns_mgr_update_pending_signal_id =
g_signal_connect(nm_manager_get_dns_manager(self),
"notify::" NM_DNS_MANAGER_UPDATE_PENDING,
G_CALLBACK(_dns_mgr_update_pending_cb),
self);
}
return;
}
nm_clear_g_signal_handler(nm_manager_get_dns_manager(self),
&priv->dns_mgr_update_pending_signal_id);
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
reason = nm_device_has_pending_action_reason(device);
if (reason) {
@ -7790,6 +7815,28 @@ impl_manager_checkpoint_adjust_rollback_timeout(NMDBusObject
/*****************************************************************************/
NMDnsManager *
nm_manager_get_dns_manager(NMManager *self)
{
NMManagerPrivate *priv;
g_return_val_if_fail(NM_IS_MANAGER(self), NULL);
priv = NM_MANAGER_GET_PRIVATE(self);
if (G_UNLIKELY(!priv->dns_mgr)) {
/* Initialize lazily on first use.
*
* But keep a reference. This is to ensure proper lifetimes between
* singleton instances (i.e. nm_dns_manager_get() outlives NMManager). */
priv->dns_mgr = g_object_ref(nm_dns_manager_get());
}
return priv->dns_mgr;
}
/*****************************************************************************/
static void
auth_mgr_changed(NMAuthManager *auth_manager, gpointer user_data)
{
@ -8250,6 +8297,9 @@ dispose(GObject *object)
g_clear_object(&priv->concheck_mgr);
}
nm_clear_g_signal_handler(priv->dns_mgr, &priv->dns_mgr_update_pending_signal_id);
g_clear_object(&priv->dns_mgr);
if (priv->auth_mgr) {
g_signal_handlers_disconnect_by_func(priv->auth_mgr, G_CALLBACK(auth_mgr_changed), self);
g_clear_object(&priv->auth_mgr);

View file

@ -200,6 +200,10 @@ NMMetered nm_manager_get_metered(NMManager *self);
void nm_manager_notify_device_availability_maybe_changed(NMManager *self);
struct _NMDnsManager;
struct _NMDnsManager *nm_manager_get_dns_manager(NMManager *self);
/*****************************************************************************/
void nm_manager_device_auth_request(NMManager *self,

View file

@ -3432,51 +3432,6 @@ nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val)
G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0);
NMUtilsNamedValue *
nm_utils_named_values_from_strdict_full(GHashTable *hash,
guint *out_len,
GCompareDataFunc compare_func,
gpointer user_data,
NMUtilsNamedValue *provided_buffer,
guint provided_buffer_len,
NMUtilsNamedValue **out_allocated_buffer)
{
GHashTableIter iter;
NMUtilsNamedValue *values;
guint i, len;
nm_assert(provided_buffer_len == 0 || provided_buffer);
nm_assert(!out_allocated_buffer || !*out_allocated_buffer);
if (!hash || !(len = g_hash_table_size(hash))) {
NM_SET_OUT(out_len, 0);
return NULL;
}
if (provided_buffer_len >= len + 1) {
/* the buffer provided by the caller is large enough. Use it. */
values = provided_buffer;
} else {
/* allocate a new buffer. */
values = g_new(NMUtilsNamedValue, len + 1);
NM_SET_OUT(out_allocated_buffer, values);
}
i = 0;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &values[i].name, &values[i].value_ptr))
i++;
nm_assert(i == len);
values[i].name = NULL;
values[i].value_ptr = NULL;
if (compare_func)
nm_utils_named_value_list_sort(values, len, compare_func, user_data);
NM_SET_OUT(out_len, len);
return values;
}
gssize
nm_utils_named_value_list_find(const NMUtilsNamedValue *arr,
gsize len,
@ -3626,6 +3581,52 @@ nm_utils_hash_values_to_array(GHashTable *hash,
return arr;
}
NMUtilsNamedValue *
nm_utils_hash_to_array_full(GHashTable *hash,
guint *out_len,
GCompareDataFunc compare_func,
gpointer user_data,
NMUtilsNamedValue *provided_buffer,
guint provided_buffer_len,
NMUtilsNamedValue **out_allocated_buffer)
{
GHashTableIter iter;
NMUtilsNamedValue *values;
guint len;
guint i;
nm_assert(provided_buffer_len == 0 || provided_buffer);
nm_assert(!out_allocated_buffer || !*out_allocated_buffer);
if (!hash || ((len = g_hash_table_size(hash)) == 0)) {
NM_SET_OUT(out_len, 0);
return NULL;
}
if (provided_buffer_len >= len + 1) {
/* the buffer provided by the caller is large enough. Use it. */
values = provided_buffer;
} else {
/* allocate a new buffer. */
values = g_new(NMUtilsNamedValue, len + 1);
NM_SET_OUT(out_allocated_buffer, values);
}
i = 0;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, &values[i].name_ptr, &values[i].value_ptr))
i++;
nm_assert(i == len);
values[i].name_ptr = NULL;
values[i].value_ptr = NULL;
if (compare_func && len > 1)
g_qsort_with_data(values, len, sizeof(NMUtilsNamedValue), compare_func, user_data);
NM_SET_OUT(out_len, len);
return values;
}
/*****************************************************************************/
/**

View file

@ -1977,6 +1977,7 @@ typedef struct {
NMUtilsNamedEntry named_entry;
const char *name;
char *name_mutable;
gpointer name_ptr;
};
union {
const char *value_str;
@ -1990,14 +1991,28 @@ typedef struct {
.name = (n), .value_ptr = (v) \
}
NMUtilsNamedValue *
nm_utils_named_values_from_strdict_full(GHashTable *hash,
guint *out_len,
GCompareDataFunc compare_func,
gpointer user_data,
NMUtilsNamedValue *provided_buffer,
guint provided_buffer_len,
NMUtilsNamedValue **out_allocated_buffer);
NMUtilsNamedValue *nm_utils_hash_to_array_full(GHashTable *hash,
guint *out_len,
GCompareDataFunc compare_func,
gpointer user_data,
NMUtilsNamedValue *provided_buffer,
guint provided_buffer_len,
NMUtilsNamedValue **out_allocated_buffer);
#define nm_utils_named_values_from_strdict_full(hash, \
out_len, \
compare_func, \
user_data, \
provided_buffer, \
provided_buffer_len, \
out_allocated_buffer) \
nm_utils_hash_to_array_full((hash), \
(out_len), \
(compare_func), \
(user_data), \
(provided_buffer), \
(provided_buffer_len), \
(out_allocated_buffer))
#define nm_utils_named_values_from_strdict(hash, out_len, array, out_allocated_buffer) \
nm_utils_named_values_from_strdict_full((hash), \
@ -2038,6 +2053,29 @@ gpointer *nm_utils_hash_values_to_array(GHashTable *hash,
gpointer user_data,
guint *out_len);
static inline NMUtilsNamedValue *
nm_utils_hash_to_array(GHashTable *hash,
GCompareDataFunc compare_func,
gpointer user_data,
guint *out_len)
{
return nm_utils_hash_to_array_full(hash, out_len, compare_func, user_data, NULL, 0, NULL);
}
#define nm_utils_hash_to_array_with_buffer(hash, \
out_len, \
compare_func, \
user_data, \
array, \
out_allocated_buffer) \
nm_utils_hash_to_array_full((hash), \
(out_len), \
(compare_func), \
(user_data), \
(array), \
G_N_ELEMENTS(array), \
(out_allocated_buffer))
static inline const char **
nm_strdict_get_keys(const GHashTable *hash, gboolean sorted, guint *out_length)
{