core: support automatically adding DNS routes
When the "ipvX.routed-dns" property is set to true, add a route for each DNS server via the current interface. The feature works in the following way. A new routing rule is created ("priority $PRIO not fwmark $MARK lookup $TABLE") where $PRIO, $MARK and $TABLE are fixed values and are the same for all interfaces. This rule is evaluated before standard rules and tries to look up routes in table $TABLE, where NM adds the routes to DNS servers. To determine the next-hop to the name server, NM issues a RTM_GETROUTE netlink request to kernel, specifying to return the route via the current interface. In order to avoid results from $TABLE, NM also sets the fwmark as $MARK in the request.
This commit is contained in:
@@ -937,6 +937,9 @@ ipv6.ip6-privacy=0
|
|||||||
<term><varname>ip-tunnel.mtu</varname></term>
|
<term><varname>ip-tunnel.mtu</varname></term>
|
||||||
<listitem><para>If configured explicitly to 0, the MTU is not reconfigured during device activation unless it is required due to IPv6 constraints. If left unspecified, a DHCP/IPv6 SLAAC provided value is used or a default of 1500.</para></listitem>
|
<listitem><para>If configured explicitly to 0, the MTU is not reconfigured during device activation unless it is required due to IPv6 constraints. If left unspecified, a DHCP/IPv6 SLAAC provided value is used or a default of 1500.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>ipv4.routed-dns</varname></term>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>ipv4.dad-timeout</varname></term>
|
<term><varname>ipv4.dad-timeout</varname></term>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -993,6 +996,9 @@ ipv6.ip6-privacy=0
|
|||||||
removes extraneous routes from the tables.
|
removes extraneous routes from the tables.
|
||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>ipv6.routed-dns</varname></term>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>ipv6.addr-gen-mode</varname></term>
|
<term><varname>ipv6.addr-gen-mode</varname></term>
|
||||||
<listitem><para>If the per-profile setting is either "default" or "default-or-eui64", the
|
<listitem><para>If the per-profile setting is either "default" or "default-or-eui64", the
|
||||||
|
@@ -1361,6 +1361,35 @@ _prop_get_ipv6_ra_timeout(NMDevice *self)
|
|||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NMSettingIPConfigRoutedDns
|
||||||
|
_prop_get_ipvx_routed_dns(NMDevice *self, int addr_family)
|
||||||
|
{
|
||||||
|
NMSettingIPConfig *s_ip;
|
||||||
|
NMSettingIPConfigRoutedDns val;
|
||||||
|
int IS_IPv4;
|
||||||
|
|
||||||
|
g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_IP_CONFIG_ROUTED_DNS_NO);
|
||||||
|
IS_IPv4 = NM_IS_IPv4(addr_family);
|
||||||
|
|
||||||
|
s_ip = nm_device_get_applied_setting(self,
|
||||||
|
IS_IPv4 ? NM_TYPE_SETTING_IP4_CONFIG
|
||||||
|
: NM_TYPE_SETTING_IP6_CONFIG);
|
||||||
|
if (!s_ip)
|
||||||
|
return NM_SETTING_IP_CONFIG_ROUTED_DNS_NO;
|
||||||
|
|
||||||
|
val = nm_setting_ip_config_get_routed_dns(s_ip);
|
||||||
|
if (val != NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT)
|
||||||
|
return val;
|
||||||
|
|
||||||
|
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
|
||||||
|
IS_IPv4 ? NM_CON_DEFAULT("ipv4.routed-dns")
|
||||||
|
: NM_CON_DEFAULT("ipv6.routed-dns"),
|
||||||
|
self,
|
||||||
|
NM_SETTING_IP_CONFIG_ROUTED_DNS_NO,
|
||||||
|
NM_SETTING_IP_CONFIG_ROUTED_DNS_YES,
|
||||||
|
NM_SETTING_IP_CONFIG_ROUTED_DNS_NO);
|
||||||
|
}
|
||||||
|
|
||||||
static NMSettingConnectionMdns
|
static NMSettingConnectionMdns
|
||||||
_prop_get_connection_mdns(NMDevice *self)
|
_prop_get_connection_mdns(NMDevice *self)
|
||||||
{
|
{
|
||||||
@@ -10974,7 +11003,7 @@ static void
|
|||||||
_dev_ipmanual_start(NMDevice *self)
|
_dev_ipmanual_start(NMDevice *self)
|
||||||
{
|
{
|
||||||
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
|
||||||
nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
|
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
|
||||||
|
|
||||||
if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_NONE
|
if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_NONE
|
||||||
|| priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
|
|| priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
|
||||||
@@ -10984,6 +11013,13 @@ _dev_ipmanual_start(NMDevice *self)
|
|||||||
l3cd =
|
l3cd =
|
||||||
nm_device_create_l3_config_data_from_connection(self,
|
nm_device_create_l3_config_data_from_connection(self,
|
||||||
nm_device_get_applied_connection(self));
|
nm_device_get_applied_connection(self));
|
||||||
|
|
||||||
|
if (_prop_get_ipvx_routed_dns(self, AF_INET) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
|
||||||
|
nm_l3_config_data_set_routed_dns(l3cd, AF_INET, TRUE);
|
||||||
|
}
|
||||||
|
if (_prop_get_ipvx_routed_dns(self, AF_INET6) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
|
||||||
|
nm_l3_config_data_set_routed_dns(l3cd, AF_INET6, TRUE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!l3cd) {
|
if (!l3cd) {
|
||||||
|
@@ -10,7 +10,9 @@
|
|||||||
#include "nm-compat-headers/linux/if_addr.h"
|
#include "nm-compat-headers/linux/if_addr.h"
|
||||||
#include <linux/if_ether.h>
|
#include <linux/if_ether.h>
|
||||||
#include <linux/rtnetlink.h>
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <linux/fib_rules.h>
|
||||||
|
|
||||||
|
#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
|
||||||
#include "libnm-glib-aux/nm-prioq.h"
|
#include "libnm-glib-aux/nm-prioq.h"
|
||||||
#include "libnm-glib-aux/nm-time-utils.h"
|
#include "libnm-glib-aux/nm-time-utils.h"
|
||||||
#include "libnm-platform/nm-platform.h"
|
#include "libnm-platform/nm-platform.h"
|
||||||
@@ -671,7 +673,7 @@ _nm_l3cfg_emit_signal_notify_commit(NML3Cfg *self,
|
|||||||
NM_IN_SET(type, NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT));
|
NM_IN_SET(type, NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT));
|
||||||
|
|
||||||
notify_data.notify_type = type;
|
notify_data.notify_type = type;
|
||||||
notify_data.commit = (typeof(notify_data.commit)){
|
notify_data.commit = (typeof(notify_data.commit)) {
|
||||||
.l3cd_old = l3cd_old,
|
.l3cd_old = l3cd_old,
|
||||||
.l3cd_new = l3cd_new,
|
.l3cd_new = l3cd_new,
|
||||||
.l3cd_changed = l3cd_changed,
|
.l3cd_changed = l3cd_changed,
|
||||||
@@ -3886,6 +3888,139 @@ out_ip4_address:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
#define DNS_ROUTES_FWMARK_TABLE_PRIO 20053
|
||||||
|
|
||||||
|
static void
|
||||||
|
_l3cfg_routed_dns(NML3Cfg *self, NML3ConfigData *l3cd)
|
||||||
|
{
|
||||||
|
for (int IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
|
||||||
|
const char *const *nameservers;
|
||||||
|
guint i;
|
||||||
|
guint len;
|
||||||
|
int addr_family;
|
||||||
|
gboolean route_added = FALSE;
|
||||||
|
|
||||||
|
addr_family = IS_IPv4 ? AF_INET : AF_INET6;
|
||||||
|
if (!nm_l3_config_data_get_routed_dns(l3cd, addr_family)) {
|
||||||
|
if (self->priv.dns_route_added_x[IS_IPv4]) {
|
||||||
|
/* Even if the DNS-routes feature is disabled, it was enabled
|
||||||
|
* before. Therefore, we need to set one last time the routing
|
||||||
|
* table sync mode to FULL, to clear the DNS routes added
|
||||||
|
* previously. */
|
||||||
|
self->priv.dns_route_added_x[IS_IPv4] = FALSE;
|
||||||
|
nm_l3_config_data_set_route_table_sync(l3cd,
|
||||||
|
addr_family,
|
||||||
|
NM_IP_ROUTE_TABLE_SYNC_MODE_FULL);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &len);
|
||||||
|
nm_l3_config_data_set_route_table_sync(l3cd, addr_family, NM_IP_ROUTE_TABLE_SYNC_MODE_FULL);
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
nm_auto_nmpobj NMPObject *obj = NULL;
|
||||||
|
const NMPlatformIPXRoute *route;
|
||||||
|
NMPlatformIPXRoute route_new;
|
||||||
|
char addr_buf[INET6_ADDRSTRLEN];
|
||||||
|
char route_buf[128];
|
||||||
|
NMIPAddr addr;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!nm_utils_dnsname_parse_assert(addr_family, nameservers[i], NULL, &addr, NULL))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Find the gateway to the DNS over the current interface. When
|
||||||
|
* doing the lookup, we want to ignore existing DNS routes added
|
||||||
|
* before: use policy routing with a special fwmark that skips
|
||||||
|
* the table containing DNS routes. */
|
||||||
|
r = nm_platform_ip_route_get(self->priv.platform,
|
||||||
|
addr_family,
|
||||||
|
&addr,
|
||||||
|
DNS_ROUTES_FWMARK_TABLE_PRIO,
|
||||||
|
self->priv.ifindex,
|
||||||
|
&obj);
|
||||||
|
if (r < 0) {
|
||||||
|
_LOGT("could not get route to DNS %s",
|
||||||
|
nm_inet_ntop(addr_family, addr.addr_ptr, addr_buf));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
route = NMP_OBJECT_CAST_IPX_ROUTE(obj);
|
||||||
|
|
||||||
|
if (IS_IPv4) {
|
||||||
|
route_new.r4 = (NMPlatformIP4Route) {
|
||||||
|
.network = addr.addr4,
|
||||||
|
.plen = 32,
|
||||||
|
.table_any = FALSE,
|
||||||
|
.metric_any = TRUE,
|
||||||
|
.table_coerced = nm_platform_route_table_coerce(DNS_ROUTES_FWMARK_TABLE_PRIO),
|
||||||
|
.gateway = route->r4.gateway,
|
||||||
|
.rt_source = NM_IP_CONFIG_SOURCE_USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
nm_platform_ip_route_normalize(addr_family, &route_new.rx);
|
||||||
|
|
||||||
|
_LOGT("route to %s: %s",
|
||||||
|
nm_inet4_ntop(addr.addr4, addr_buf),
|
||||||
|
nm_platform_ip4_route_to_string(&route_new.r4, route_buf, sizeof(route_buf)));
|
||||||
|
|
||||||
|
nm_l3_config_data_add_route_4(l3cd, &route_new.r4);
|
||||||
|
nm_l3_config_data_set_route_table_sync(l3cd,
|
||||||
|
AF_INET,
|
||||||
|
NM_IP_ROUTE_TABLE_SYNC_MODE_FULL);
|
||||||
|
route_added = TRUE;
|
||||||
|
} else {
|
||||||
|
route_new.r6 = (NMPlatformIP6Route) {
|
||||||
|
.network = addr.addr6,
|
||||||
|
.plen = 128,
|
||||||
|
.table_any = FALSE,
|
||||||
|
.metric_any = TRUE,
|
||||||
|
.table_coerced = nm_platform_route_table_coerce(DNS_ROUTES_FWMARK_TABLE_PRIO),
|
||||||
|
.gateway = route->r6.gateway,
|
||||||
|
.rt_source = NM_IP_CONFIG_SOURCE_USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
nm_platform_ip_route_normalize(addr_family, &route_new.rx);
|
||||||
|
|
||||||
|
_LOGT("route to %s: %s",
|
||||||
|
nm_inet6_ntop(&addr.addr6, addr_buf),
|
||||||
|
nm_platform_ip6_route_to_string(&route_new.r6, route_buf, sizeof(route_buf)));
|
||||||
|
|
||||||
|
nm_l3_config_data_add_route_6(l3cd, &route_new.r6);
|
||||||
|
route_added = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route_added) {
|
||||||
|
NMPlatformRoutingRule rule;
|
||||||
|
|
||||||
|
/* Add a routing rule that selects the table when not using the
|
||||||
|
* special fwmark. Note that the rule is shared between all
|
||||||
|
* devices that use DNS routes. At the moment there is no cleanup
|
||||||
|
* mechanism: once added the rule stays forever. */
|
||||||
|
rule = ((NMPlatformRoutingRule) {
|
||||||
|
.addr_family = addr_family,
|
||||||
|
.flags = FIB_RULE_INVERT,
|
||||||
|
.priority = DNS_ROUTES_FWMARK_TABLE_PRIO,
|
||||||
|
.table = DNS_ROUTES_FWMARK_TABLE_PRIO,
|
||||||
|
.fwmark = DNS_ROUTES_FWMARK_TABLE_PRIO,
|
||||||
|
.fwmask = 0xffffffff,
|
||||||
|
.action = FR_ACT_TO_TBL,
|
||||||
|
.protocol = RTPROT_STATIC,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* FIXME: don't add the rule every time */
|
||||||
|
nmp_global_tracker_track_rule(self->priv.global_tracker, &rule, 10, self, NULL);
|
||||||
|
nmp_global_tracker_sync(self->priv.global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
self->priv.dns_route_added_x[IS_IPv4] = route_added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_l3cfg_update_combined_config(NML3Cfg *self,
|
_l3cfg_update_combined_config(NML3Cfg *self,
|
||||||
gboolean to_commit,
|
gboolean to_commit,
|
||||||
@@ -4053,6 +4188,8 @@ _l3cfg_update_combined_config(NML3Cfg *self,
|
|||||||
nm_assert(l3cd);
|
nm_assert(l3cd);
|
||||||
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
|
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
|
||||||
|
|
||||||
|
_l3cfg_routed_dns(self, l3cd);
|
||||||
|
|
||||||
nm_l3_config_data_seal(l3cd);
|
nm_l3_config_data_seal(l3cd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -198,6 +198,7 @@ struct _NML3Cfg {
|
|||||||
const NMPObject *plobj;
|
const NMPObject *plobj;
|
||||||
const NMPObject *plobj_next;
|
const NMPObject *plobj_next;
|
||||||
int ifindex;
|
int ifindex;
|
||||||
|
gboolean dns_route_added_x[2]; /* index with IS_IPv4 */
|
||||||
} priv;
|
} priv;
|
||||||
|
|
||||||
/* NML3Cfg strongly cooperates with NMNetns. The latter is
|
/* NML3Cfg strongly cooperates with NMNetns. The latter is
|
||||||
|
@@ -2448,6 +2448,10 @@ device_l3cd_changed(NMDevice *device,
|
|||||||
*/
|
*/
|
||||||
state = nm_device_get_state(device);
|
state = nm_device_get_state(device);
|
||||||
if (l3cd_new && state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
|
if (l3cd_new && state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
|
||||||
|
/* Since the device L3CD_CHANGED signal is emitted *after* the commit of
|
||||||
|
* configuration, addresses and routes are already set in kernel when we
|
||||||
|
* write the configuration to resolv.conf or send it to the DNS plugin.
|
||||||
|
* This prevents "leaks" of DNS queries via the wrong routes.*/
|
||||||
nm_dns_manager_set_ip_config(priv->dns_manager,
|
nm_dns_manager_set_ip_config(priv->dns_manager,
|
||||||
AF_UNSPEC,
|
AF_UNSPEC,
|
||||||
device,
|
device,
|
||||||
|
Reference in New Issue
Block a user