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>
|
||||
<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>
|
||||
<term><varname>ipv4.routed-dns</varname></term>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>ipv4.dad-timeout</varname></term>
|
||||
</varlistentry>
|
||||
@@ -993,6 +996,9 @@ ipv6.ip6-privacy=0
|
||||
removes extraneous routes from the tables.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>ipv6.routed-dns</varname></term>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>ipv6.addr-gen-mode</varname></term>
|
||||
<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);
|
||||
}
|
||||
|
||||
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
|
||||
_prop_get_connection_mdns(NMDevice *self)
|
||||
{
|
||||
@@ -10974,7 +11003,7 @@ static void
|
||||
_dev_ipmanual_start(NMDevice *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
|
||||
|| priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
|
||||
@@ -10984,6 +11013,13 @@ _dev_ipmanual_start(NMDevice *self)
|
||||
l3cd =
|
||||
nm_device_create_l3_config_data_from_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) {
|
||||
|
@@ -10,7 +10,9 @@
|
||||
#include "nm-compat-headers/linux/if_addr.h"
|
||||
#include <linux/if_ether.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-time-utils.h"
|
||||
#include "libnm-platform/nm-platform.h"
|
||||
@@ -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
|
||||
_l3cfg_update_combined_config(NML3Cfg *self,
|
||||
gboolean to_commit,
|
||||
@@ -4053,6 +4188,8 @@ _l3cfg_update_combined_config(NML3Cfg *self,
|
||||
nm_assert(l3cd);
|
||||
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
|
||||
|
||||
_l3cfg_routed_dns(self, l3cd);
|
||||
|
||||
nm_l3_config_data_seal(l3cd);
|
||||
}
|
||||
|
||||
|
@@ -198,6 +198,7 @@ struct _NML3Cfg {
|
||||
const NMPObject *plobj;
|
||||
const NMPObject *plobj_next;
|
||||
int ifindex;
|
||||
gboolean dns_route_added_x[2]; /* index with IS_IPv4 */
|
||||
} priv;
|
||||
|
||||
/* NML3Cfg strongly cooperates with NMNetns. The latter is
|
||||
|
@@ -2448,6 +2448,10 @@ device_l3cd_changed(NMDevice *device,
|
||||
*/
|
||||
state = nm_device_get_state(device);
|
||||
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,
|
||||
AF_UNSPEC,
|
||||
device,
|
||||
|
Reference in New Issue
Block a user