Re: [PATCH v2] systemd-resolved: Use DNS servers of the default service

Jussi Laakkonen <[email protected]> Mon, 26 Jan 2026 15:09:53 +0200
Newsgroups dev.linux.lists.connman
Message-ID <[email protected]>
Hi all,

Heh, I think the first patch mail was lost in transit. If it does not 
appear, regard this as v1 instead of v2 - just sent the wrong file by 
accident.

- Jussi

On 1/26/26 2:59 PM, Jussi Laakkonen wrote:
> Register to get the notifies about the default service, service state
> and offline mode changes. When default service changes set it as
> default route in resolved, revert other links' status and flush the cache
> so only the default service's DNSs are used. This brings the similar
> behavior that is is in dnsproxy.c, and prevents leaking of DNS queries
> outside VPNs as well. Thus, effectively making only the DNS servers of
> the default online service to be used.
> 
> To ensure that the servers are gone the domains and servers lists are
> cleared when the service is disconnecting. The current default index is
> being tracked and when it is being reset, the cache will be flushed as
> well.
> 
> ---
> Changes since v2:
>   - Send the correct patch file without the internal bug reference.
> 
>   src/dns-systemd-resolved.c | 340 ++++++++++++++++++++++++++++++++++++-
>   1 file changed, 337 insertions(+), 3 deletions(-)
> 
> diff --git a/src/dns-systemd-resolved.c b/src/dns-systemd-resolved.c
> index 912ab3fe..9dd0328c 100644
> --- a/src/dns-systemd-resolved.c
> +++ b/src/dns-systemd-resolved.c
> @@ -36,7 +36,7 @@
>   #define SYSTEMD_RESOLVED_SERVICE "org.freedesktop.resolve1"
>   #define SYSTEMD_RESOLVED_PATH "/org/freedesktop/resolve1"
>   
> -struct mdns_data {
> +struct resolved_dbus_data {
>   	int index;
>   	bool enabled;
>   };
> @@ -48,6 +48,7 @@ static GDBusProxy *resolved_proxy;
>   
>   /* update after a full set of instructions has been received */
>   static guint update_interfaces_source;
> +static gint current_default_index = -1;
>   
>   struct dns_interface {
>   	GList *domains;
> @@ -55,6 +56,7 @@ struct dns_interface {
>   	int index;
>   	bool needs_domain_update;
>   	bool needs_server_update;
> +	bool enabled;
>   };
>   
>   static gboolean compare_index(gconstpointer a, gconstpointer b)
> @@ -405,7 +407,7 @@ static int setup_resolved(void)
>   }
>   
>   static void setlinkmulticastdns_append(DBusMessageIter *iter, void *user_data) {
> -	struct mdns_data *data = user_data;
> +	struct resolved_dbus_data *data = user_data;
>   	char *val = "no";
>   
>   	if (data->enabled)
> @@ -419,7 +421,7 @@ static void setlinkmulticastdns_append(DBusMessageIter *iter, void *user_data) {
>   
>   int __connman_dnsproxy_set_mdns(int index, bool enabled)
>   {
> -	struct mdns_data data = { .index = index, .enabled = enabled };
> +	struct resolved_dbus_data data = { .index = index, .enabled = enabled };
>   
>   	if (!resolved_proxy)
>   		return -ENOENT;
> @@ -434,6 +436,332 @@ int __connman_dnsproxy_set_mdns(int index, bool enabled)
>   	return 0;
>   }
>   
> +static void setlinkdefaultroute_append(DBusMessageIter *iter, void *user_data)
> +{
> +	struct resolved_dbus_data *data = user_data;
> +	dbus_bool_t val = data->enabled;
> +
> +	DBG("SetLinkDefaultRoute: %d/%s", data->index, val ? "yes" : "no");
> +
> +	dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &data->index);
> +	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val);
> +}
> +
> +static int resolved_set_default_route(int index, bool enabled)
> +{
> +	struct resolved_dbus_data data = { .index = index, .enabled = enabled };
> +
> +	if (!resolved_proxy)
> +		return -ENOENT;
> +
> +	if (index < 0)
> +		return -ENODEV;
> +
> +	if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkDefaultRoute",
> +			setlinkdefaultroute_append, NULL, &data, NULL))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int resolved_flush_cache()
> +{
> +	DBG("");
> +
> +	if (!resolved_proxy)
> +		return -ENOENT;
> +
> +	if (!g_dbus_proxy_method_call(resolved_proxy, "FlushCaches",
> +			NULL, NULL, NULL, NULL))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static void setrevertlink_append(DBusMessageIter *iter, void *user_data)
> +{
> +	int index = GPOINTER_TO_INT(user_data);
> +
> +	DBG("RevertLink: %d", index);
> +
> +	dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &index);
> +}
> +
> +static int resolved_disable_interface(int index)
> +{
> +	if (!resolved_proxy)
> +		return -ENOENT;
> +
> +	if (index < 0)
> +		return -ENODEV;
> +
> +	if (!g_dbus_proxy_method_call(resolved_proxy, "RevertLink",
> +			setrevertlink_append, NULL, GINT_TO_POINTER(index),
> +			NULL))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +struct interface_search_data {
> +	bool enable;
> +	bool forced;
> +	bool disconnected;
> +	int index4;
> +	int index6;
> +	int vpn_index;
> +};
> +
> +static bool interface_index_match(int index,
> +				struct interface_search_data *search_data)
> +{
> +	if (search_data->forced)
> +		return true;
> +
> +	if (index == search_data->index4 || index == search_data->index6) {
> +		DBG("interface %d match", index);
> +		return true;
> +	}
> +
> +	if (index == search_data->vpn_index) {
> +		DBG("VPN interface %d match", index);
> +		return true;
> +	}
> +
> +	return false;
> +}
> +
> +static void toggle_interface_dns(struct dns_interface *iface, bool enable)
> +{
> +	DBG("%s interface %p/%d", enable ? "enable" : "disable", iface,
> +								iface->index);
> +
> +	if (iface->enabled == enable) {
> +		DBG("already %s", enable ? "enabled" : "disabled");
> +		return;
> +	}
> +
> +	iface->enabled = enable;
> +
> +	if (!enable) {
> +		DBG("disabling interface %d", iface->index);
> +		resolved_disable_interface(iface->index);
> +	} else {
> +		DBG("re-enabling interface %d", iface->index);
> +		set_systemd_resolved_values(iface);
> +	}
> +
> +	if (is_empty(iface)) {
> +		DBG("removing interface %d", iface->index);
> +		g_hash_table_remove(interface_hash,
> +					GUINT_TO_POINTER(iface->index));
> +	}
> +}
> +
> +static void check_interface(gpointer key, gpointer value, gpointer user_data)
> +{
> +	struct dns_interface *iface = value;
> +	struct interface_search_data *search_data = user_data;
> +
> +	if (interface_index_match(iface->index, search_data)) {
> +		/*
> +		 * Clear domains and servers before updating the status to
> +		 * resolved when interface is disconnected.
> +		 */
> +		if (!search_data->enable && search_data->disconnected) {
> +			DBG("clear domains and servers of %d", iface->index);
> +
> +			g_list_free_full(iface->domains, g_free);
> +			iface->domains = NULL;
> +
> +			g_list_free_full(iface->servers, g_free);
> +			iface->servers = NULL;
> +		}
> +
> +		toggle_interface_dns(iface, search_data->enable);
> +	/* If DNSs of an interface are to be enabled disable the others */
> +	} else if (search_data->enable) {
> +		toggle_interface_dns(iface, false);
> +	} else {
> +		return;
> +	}
> +
> +	if (!update_interfaces_source)
> +		update_interfaces_source = g_idle_add(update_systemd_resolved,
> +									NULL);
> +}
> +
> +static void resolved_offline_mode(bool enabled)
> +{
> +	struct interface_search_data search_data = { 0 };
> +	int err;
> +
> +	DBG("enabled %d", enabled);
> +
> +	search_data.enable = !enabled;
> +	search_data.forced = true;
> +
> +	g_hash_table_foreach(interface_hash, check_interface, &search_data);
> +
> +	/* Offline mode is not on, skip default unset and flushing of cache */
> +	if (!enabled)
> +		return;
> +
> +	err = resolved_set_default_route(current_default_index, false);
> +	if (err && err != -ENODEV)
> +		DBG("failed to unset interface %d as default route",
> +							current_default_index);
> +
> +	current_default_index = -1;
> +
> +	resolved_flush_cache();
> +}
> +
> +static void resolved_default_changed(struct connman_service *service)
> +{
> +	struct connman_ipconfig *ipconfig;
> +	struct interface_search_data search_data = { 0 };
> +	int index;
> +	int err;
> +
> +	DBG("service %p interface %d", service,
> +					__connman_service_get_index(service));
> +
> +	if (!service) {
> +		/* When no services are active, then disable DNSs */
> +		resolved_offline_mode(true);
> +		return;
> +	}
> +
> +	if (connman_service_get_type(service) == CONNMAN_SERVICE_TYPE_VPN) {
> +		/* VPN is either or take both to be sure */
> +		search_data.index4 = search_data.index6 = -1;
> +		search_data.vpn_index = __connman_service_get_index(service);
> +
> +		if (search_data.vpn_index < 0)
> +			return;
> +
> +		index = search_data.vpn_index;
> +	} else {
> +		ipconfig = __connman_service_get_ip4config(service);
> +		search_data.index4 = __connman_ipconfig_get_index(ipconfig);
> +
> +		ipconfig = __connman_service_get_ip6config(service);
> +		search_data.index6 = __connman_ipconfig_get_index(ipconfig);
> +
> +		if (search_data.index4 < 0 && search_data.index6 < 0)
> +			return;
> +
> +		/*
> +		 * In case non-split-routed VPN is set as split routed the DNS
> +		 * servers the VPN must be enabled as well, when the transport
> +		 * becomes the default service. Try first with IPv4 and then
> +		 * attempt using IPv6 if not set. VPN can be either or.
> +		 */
> +		search_data.vpn_index = __connman_gateway_get_vpn_index(
> +							search_data.index4);
> +		if (search_data.vpn_index < 0)
> +			search_data.vpn_index =
> +					__connman_gateway_get_vpn_index(
> +							search_data.index6);
> +
> +		if (search_data.index4 == search_data.index6)
> +			index = search_data.index4;
> +		else
> +			index = search_data.index6; // Prefer IPv6, as usual.
> +	}
> +
> +	if (current_default_index != index) {
> +		err = resolved_set_default_route(current_default_index, false);
> +		if (err && err != -ENODEV)
> +			DBG("failed to unset %d as default",
> +							current_default_index);
> +
> +		current_default_index = -1;
> +
> +		err = resolved_set_default_route(index, true);
> +		if (err && err != -ENODEV)
> +			DBG("failed to set %d as default", index);
> +		else
> +			current_default_index = index;
> +	}
> +
> +	search_data.enable = true;
> +	g_hash_table_foreach(interface_hash, check_interface, &search_data);
> +
> +	resolved_flush_cache();
> +}
> +
> +static void resolved_service_state_changed(struct connman_service *service,
> +			enum connman_service_state state)
> +{
> +	struct connman_ipconfig *ipconfig;
> +	struct interface_search_data search_data = { 0 };
> +	int index;
> +	int err;
> +
> +	index = __connman_service_get_index(service);
> +	DBG("service %p interface %d state %d", service, index, state);
> +
> +	switch (state) {
> +	case CONNMAN_SERVICE_STATE_DISCONNECT:
> +	case CONNMAN_SERVICE_STATE_IDLE:
> +	case CONNMAN_SERVICE_STATE_FAILURE:
> +	case CONNMAN_SERVICE_STATE_UNKNOWN:
> +		DBG("disable interface %d", index);
> +		break;
> +	case CONNMAN_SERVICE_STATE_ASSOCIATION:
> +	case CONNMAN_SERVICE_STATE_CONFIGURATION:
> +	case CONNMAN_SERVICE_STATE_ONLINE:
> +	case CONNMAN_SERVICE_STATE_READY:
> +		DBG("connecting/connected mode, skip interface %d", index);
> +		return;
> +	}
> +
> +	ipconfig = __connman_service_get_ip4config(service);
> +	search_data.index4 = __connman_ipconfig_get_index(ipconfig);
> +
> +	ipconfig = __connman_service_get_ip6config(service);
> +	search_data.index6 = __connman_ipconfig_get_index(ipconfig);
> +
> +	if (current_default_index == search_data.index4)
> +		index = search_data.index4;
> +	else if (current_default_index == search_data.index6)
> +		index = search_data.index6;
> +	else
> +		index = -1;
> +
> +	if (index != -1) {
> +		err = resolved_set_default_route(index, false);
> +		if (err && err != -ENODEV)
> +			DBG("failed to unset %d as default route", index);
> +
> +		current_default_index = -1;
> +		resolved_flush_cache(); // Do when index is unset
> +	}
> +
> +	/*
> +	 * Disable DNS servers when the service is not connected and clear the
> +	 * domains and servers. Use both indexes as service can have two
> +	 * separate interfaces, both of which can be registered to resolved.
> +	 */
> +	search_data.enable = false;
> +	search_data.disconnected = true;
> +
> +	g_hash_table_foreach(interface_hash, check_interface, &search_data);
> +
> +	if (!update_interfaces_source)
> +		update_interfaces_source = g_idle_add(update_systemd_resolved,
> +									NULL);
> +}
> +
> +static struct connman_notifier resolved_notifier = {
> +	.name			= "systemd-resolved",
> +	.default_changed	= resolved_default_changed,
> +	.offline_mode		= resolved_offline_mode,
> +	.service_state_changed	= resolved_service_state_changed,
> +};
> +
>   int __connman_dnsproxy_init(void)
>   {
>   	int ret;
> @@ -451,6 +779,10 @@ int __connman_dnsproxy_init(void)
>   	if (!interface_hash)
>   		return -ENOMEM;
>   
> +	ret = connman_notifier_register(&resolved_notifier);
> +	if (ret < 0)
> +		DBG("cannot register notifier");
> +
>   	return 0;
>   }
>   
> @@ -458,6 +790,8 @@ void __connman_dnsproxy_cleanup(void)
>   {
>   	DBG("");
>   
> +	connman_notifier_unregister(&resolved_notifier);
> +
>   	if (update_interfaces_source) {
>   		/*
>   		 * It might be that we don't get to an idle loop anymore, so