diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 464aadb63..605b1fc9d 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -937,6 +937,9 @@ ipv6.ip6-privacy=0 ip-tunnel.mtu 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. + + ipv4.routed-dns + ipv4.dad-timeout @@ -993,6 +996,9 @@ ipv6.ip6-privacy=0 removes extraneous routes from the tables. + + ipv6.routed-dns + ipv6.addr-gen-mode If the per-profile setting is either "default" or "default-or-eui64", the diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 5421dc42d..75a2aa9e2 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -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) { @@ -10973,8 +11002,8 @@ _dev_ipmanual_check_ready(NMDevice *self) static void _dev_ipmanual_start(NMDevice *self) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + 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) { diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 6ef6bd924..5f4cf7b1d 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -10,7 +10,9 @@ #include "nm-compat-headers/linux/if_addr.h" #include #include +#include +#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" @@ -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)); notify_data.notify_type = type; - notify_data.commit = (typeof(notify_data.commit)){ + notify_data.commit = (typeof(notify_data.commit)) { .l3cd_old = l3cd_old, .l3cd_new = l3cd_new, .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 _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); } diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 05e9ea0ae..f5ebc69ea 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -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 diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index a8d8e4068..f86d8115c 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -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,