vpn: merge branch 'th/vpn-route-bgo787370'

Fix and improve determining the route to the external VPN gateway.

https://bugzilla.gnome.org/show_bug.cgi?id=787370
This commit is contained in:
Thomas Haller 2017-09-07 12:05:06 +02:00
commit 42edfb4a7f
6 changed files with 97 additions and 72 deletions

View file

@ -10038,6 +10038,11 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
nm_assert (!old || NM_IS_IP4_CONFIG (old));
nm_assert (!config || NM_IS_IP4_CONFIG (config));
nm_assert (!old || nm_ip4_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
nm_assert (!config || nm_ip4_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
if (!_replace_vpn_config_in_list (&priv->vpn4_configs, (GObject *) old, (GObject *) config))
return;
@ -10167,6 +10172,11 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
nm_assert (!old || NM_IS_IP6_CONFIG (old));
nm_assert (!config || NM_IS_IP6_CONFIG (config));
nm_assert (!old || nm_ip6_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
nm_assert (!config || nm_ip6_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
if (!_replace_vpn_config_in_list (&priv->vpn6_configs, (GObject *) old, (GObject *) config))
return;

View file

@ -6033,6 +6033,7 @@ static NMPlatformError
ip_route_get (NMPlatform *platform,
int addr_family,
gconstpointer address,
int oif_ifindex,
NMPObject **out_route)
{
const gboolean is_v4 = (addr_family == AF_INET);
@ -6066,6 +6067,13 @@ ip_route_get (NMPlatform *platform,
if (!_nl_addattr_l (&req.n, sizeof (req), RTA_DST, address, addr_len))
nm_assert_not_reached ();
if (oif_ifindex > 0) {
gint32 ii = oif_ifindex;
if (!_nl_addattr_l (&req.n, sizeof (req), RTA_OIF, &ii, sizeof (ii)))
nm_assert_not_reached ();
}
seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
if (nle < 0) {

View file

@ -3888,12 +3888,14 @@ NMPlatformError
nm_platform_ip_route_get (NMPlatform *self,
int addr_family,
gconstpointer address /* in_addr_t or struct in6_addr */,
int oif_ifindex,
NMPObject **out_route)
{
nm_auto_nmpobj NMPObject *route = NULL;
NMPlatformError result;
char buf[NM_UTILS_INET_ADDRSTRLEN];
char buf_err[200];
char buf_oif[64];
_CHECK_SELF (self, klass, FALSE);
@ -3901,9 +3903,10 @@ nm_platform_ip_route_get (NMPlatform *self,
g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET,
AF_INET6), NM_PLATFORM_ERROR_BUG);
_LOGT ("route: get IPv%c route for: %s",
_LOGT ("route: get IPv%c route for: %s%s",
addr_family == AF_INET ? '4' : '6',
inet_ntop (addr_family, address, buf, sizeof (buf)));
inet_ntop (addr_family, address, buf, sizeof (buf)),
oif_ifindex > 0 ? nm_sprintf_buf (buf_oif, " oif %d", oif_ifindex) : "");
if (!klass->ip_route_get)
result = NM_PLATFORM_ERROR_OPNOTSUPP;
@ -3911,6 +3914,7 @@ nm_platform_ip_route_get (NMPlatform *self,
result = klass->ip_route_get (self,
addr_family,
address,
oif_ifindex,
&route);
}

View file

@ -823,6 +823,7 @@ typedef struct {
NMPlatformError (*ip_route_get) (NMPlatform *self,
int addr_family,
gconstpointer address,
int oif_ifindex,
NMPObject **out_route);
gboolean (*check_support_kernel_extended_ifa_flags) (NMPlatform *);
@ -1186,6 +1187,7 @@ gboolean nm_platform_ip_route_flush (NMPlatform *self,
NMPlatformError nm_platform_ip_route_get (NMPlatform *self,
int addr_family,
gconstpointer address,
int oif_ifindex,
NMPObject **out_route);
const char *nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len);

View file

@ -409,6 +409,7 @@ test_ip_route_get (void)
result = nm_platform_ip_route_get (NM_PLATFORM_GET,
AF_INET,
&a,
nmtst_get_rand_int () % 2 ? 0 : ifindex,
&route);
g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
@ -417,6 +418,7 @@ test_ip_route_get (void)
g_assert (route->parent._ref_count == 1);
r = NMP_OBJECT_CAST_IP4_ROUTE (route);
g_assert (r->rt_cloned);
g_assert (r->ifindex == ifindex);
g_assert (r->network == a);
g_assert (r->plen == 32);

View file

@ -710,47 +710,51 @@ add_ip4_vpn_gateway_route (NMIP4Config *config,
in_addr_t vpn_gw,
NMPlatform *platform)
{
NMIP4Config *parent_config;
guint32 parent_gw;
guint32 parent_gw = 0;
gboolean has_parent_gw = FALSE;
NMPlatformIP4Route route;
int ifindex;
guint32 route_metric;
nm_auto_nmpobj const NMPObject *route_resolved = NULL;
g_return_if_fail (NM_IS_IP4_CONFIG (config));
g_return_if_fail (NM_IS_DEVICE (parent_device));
g_return_if_fail (vpn_gw != 0);
nm_assert (nm_ip4_config_get_ifindex (config) > 0);
/* Set up a route to the VPN gateway's public IP address through the default
* network device if the VPN gateway is on a different subnet.
*/
parent_config = nm_device_get_ip4_config (parent_device);
g_return_if_fail (parent_config != NULL);
ifindex = nm_ip4_config_get_ifindex (config);
parent_gw = nm_ip4_config_get_gateway (parent_config);
nm_assert (ifindex > 0);
nm_assert (ifindex == nm_device_get_ip_ifindex (parent_device));
/* If the VPN gateway is in the same subnet as one of the parent device's
* IP addresses, don't add the host route to it, but a route through the
* parent device.
*/
if (nm_ip4_config_destination_is_direct (parent_config, vpn_gw, 32))
parent_gw = 0;
/* actually, let's ask kernel how to reach @vpn_gw. If (and only if)
* the destination is on @parent_device, then we take that @parent_gw. */
/* Ask kernel how to reach @vpn_gw. We can only inject the route in
* @parent_device, so whatever we resolve, it can only be on @ifindex. */
if (nm_platform_ip_route_get (platform,
AF_INET,
&vpn_gw,
ifindex,
(NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (route_resolved);
if (r->ifindex == nm_ip4_config_get_ifindex (config))
parent_gw = r->gateway;
if (r->ifindex == ifindex) {
/* `ip route get` always resolves the route, even if the destination is unreachable.
* In which case, it pretends the destination is directly reachable.
*
* So, only accept direct routes, if @vpn_gw is a private network. */
if ( r->gateway
|| nm_utils_ip_is_site_local (AF_INET, &vpn_gw)) {
parent_gw = r->gateway;
has_parent_gw = TRUE;
}
}
}
if (!has_parent_gw)
return;
route_metric = nm_device_get_ip4_route_metric (parent_device);
memset (&route, 0, sizeof (route));
route.ifindex = ifindex;
route.network = vpn_gw;
route.plen = 32;
route.gateway = parent_gw;
@ -779,51 +783,55 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
const struct in6_addr *vpn_gw,
NMPlatform *platform)
{
NMIP6Config *parent_config;
const struct in6_addr *parent_gw;
const struct in6_addr *parent_gw = NULL;
gboolean has_parent_gw = FALSE;
NMPlatformIP6Route route;
int ifindex;
guint32 route_metric;
nm_auto_nmpobj const NMPObject *route_resolved = NULL;
g_return_if_fail (NM_IS_IP6_CONFIG (config));
g_return_if_fail (NM_IS_DEVICE (parent_device));
g_return_if_fail (vpn_gw != NULL);
nm_assert (nm_ip6_config_get_ifindex (config) > 0);
parent_config = nm_device_get_ip6_config (parent_device);
g_return_if_fail (parent_config != NULL);
ifindex = nm_ip6_config_get_ifindex (config);
/* we add a direct route to the VPN gateway, but we only do that
* on the @parent_device. That is probably not correct in every case... */
parent_gw = nm_ip6_config_get_gateway (parent_config);
if (!parent_gw)
return;
nm_assert (ifindex > 0);
nm_assert (ifindex == nm_device_get_ip_ifindex (parent_device));
/* If the VPN gateway is in the same subnet as one of the parent device's
* IP addresses, don't add the host route to it, but a route through the
* parent device.
*/
if (nm_ip6_config_destination_is_direct (parent_config, vpn_gw, 128))
parent_gw = &in6addr_any;
/* actually, let's ask kernel how to reach @vpn_gw. If (and only if)
* the destination is on @parent_device, then we take that @parent_gw. */
/* Ask kernel how to reach @vpn_gw. We can only inject the route in
* @parent_device, so whatever we resolve, it can only be on @ifindex. */
if (nm_platform_ip_route_get (platform,
AF_INET6,
vpn_gw,
ifindex,
(NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (route_resolved);
if (r->ifindex == nm_ip6_config_get_ifindex (config))
parent_gw = &r->gateway;
if (r->ifindex == ifindex) {
/* `ip route get` always resolves the route, even if the destination is unreachable.
* In which case, it pretends the destination is directly reachable.
*
* So, only accept direct routes, if @vpn_gw is a private network. */
if ( !IN6_IS_ADDR_UNSPECIFIED (&r->gateway)
|| nm_utils_ip_is_site_local (AF_INET6, &vpn_gw)) {
parent_gw = &r->gateway;
has_parent_gw = TRUE;
}
}
}
if (!has_parent_gw)
return;
route_metric = nm_device_get_ip6_route_metric (parent_device);
memset (&route, 0, sizeof (route));
route.ifindex = ifindex;
route.network = *vpn_gw;
route.plen = 128;
route.gateway = *parent_gw;
if (parent_gw)
route.gateway = *parent_gw;
route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
route.metric = route_metric;
nm_ip6_config_add_route (config, &route);
@ -833,7 +841,7 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
* routes include a subnet that matches the parent device's subnet,
* the parent device's gateway would get routed through the VPN and fail.
*/
if (!IN6_IS_ADDR_UNSPECIFIED (parent_gw)) {
if (parent_gw && !IN6_IS_ADDR_UNSPECIFIED (parent_gw)) {
memset (&route, 0, sizeof (route));
route.network = *parent_gw;
route.plen = 128;
@ -1075,40 +1083,31 @@ apply_parent_device_config (NMVpnConnection *self)
{
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (self));
int ifindex;
NMIP4Config *vpn4_parent_config = NULL;
NMIP6Config *vpn6_parent_config = NULL;
if (priv->ip_ifindex > 0) {
if (priv->ip4_config) {
vpn4_parent_config = nm_ip4_config_new (nm_netns_get_multi_idx (priv->netns),
priv->ip_ifindex);
}
if (priv->ip6_config) {
vpn6_parent_config = nm_ip6_config_new (nm_netns_get_multi_idx (priv->netns),
priv->ip_ifindex);
}
} else {
int ifindex;
ifindex = nm_device_get_ip_ifindex (parent_dev);
if (ifindex > 0) {
/* If the VPN didn't return a network interface, it is a route-based
* VPN (like kernel IPSec) and all IP addressing and routing should
* be done on the parent interface instead.
*/
/* Also clear the gateway. We don't configure the gateway as part of the
* vpn-config. Instead we tell NMDefaultRouteManager directly about the
* default route. */
ifindex = nm_device_get_ip_ifindex (parent_dev);
if (ifindex > 0) {
if (priv->ip4_config) {
vpn4_parent_config = nm_ip4_config_new (nm_netns_get_multi_idx (priv->netns),
ifindex);
if (priv->ip4_config) {
vpn4_parent_config = nm_ip4_config_new (nm_netns_get_multi_idx (priv->netns),
ifindex);
if (priv->ip_ifindex <= 0)
nm_ip4_config_merge (vpn4_parent_config, priv->ip4_config, NM_IP_CONFIG_MERGE_NO_DNS);
}
if (priv->ip6_config) {
vpn6_parent_config = nm_ip6_config_new (nm_netns_get_multi_idx (priv->netns),
ifindex);
}
if (priv->ip6_config) {
vpn6_parent_config = nm_ip6_config_new (nm_netns_get_multi_idx (priv->netns),
ifindex);
if (priv->ip_ifindex <= 0) {
nm_ip6_config_merge (vpn6_parent_config, priv->ip6_config, NM_IP_CONFIG_MERGE_NO_DNS);
/* Also clear the gateway. We don't configure the gateway as part of the
* vpn-config. Instead we tell NMDefaultRouteManager directly about the
* default route. */
nm_ip6_config_set_gateway (vpn6_parent_config, NULL);
}
}
@ -1144,6 +1143,8 @@ nm_vpn_connection_apply_config (NMVpnConnection *self)
{
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
apply_parent_device_config (self);
if (priv->ip_ifindex > 0) {
nm_platform_link_set_up (nm_netns_get_platform (priv->netns), priv->ip_ifindex, NULL);
@ -1166,8 +1167,6 @@ nm_vpn_connection_apply_config (NMVpnConnection *self)
nm_platform_link_set_mtu (nm_netns_get_platform (priv->netns), priv->ip_ifindex, priv->mtu);
}
apply_parent_device_config (self);
nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);