core: track VPN routes on the master device, not the VPN

When a VPN wanted to add some routes (like the host route for the
VPN gateway) it would add them itself and listen for parent device
events and re-add them if necessary.  That's pretty fragile, plus
the platform blows away routes that aren't part of the IP config
that's getting applied.

So we might as well just have the VPN connection tell the parent
what the routes are, and have the parent device handle updating
the routing.  The routes are through the parent device anyway,
and so are "owned" by the parent too.
This commit is contained in:
Dan Williams 2013-08-01 10:59:42 -05:00
parent 468243baf2
commit 5c1ec7cedf
4 changed files with 117 additions and 176 deletions

View file

@ -234,6 +234,7 @@ typedef struct {
gulong dhcp4_state_sigid;
gulong dhcp4_timeout_sigid;
NMDHCP4Config * dhcp4_config;
NMIP4Config * vpn4_config; /* routes added by a VPN which uses this device */
PingInfo gw_ping;
@ -253,6 +254,7 @@ typedef struct {
/* IP6 configuration info */
NMIP6Config * ip6_config;
IpState ip6_state;
NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */
NMRDisc * rdisc;
gulong rdisc_config_changed_sigid;
@ -2292,6 +2294,8 @@ ip4_config_merge_and_apply (NMDevice *self,
composite = nm_ip4_config_new ();
nm_ip4_config_merge (composite, priv->dev_ip4_config);
if (priv->vpn4_config)
nm_ip4_config_merge (composite, priv->vpn4_config);
/* Merge user overrides into the composite config */
nm_ip4_config_merge_setting (composite, nm_connection_get_setting_ip4_config (connection));
@ -2691,11 +2695,13 @@ ip6_config_merge_and_apply (NMDevice *self,
composite = nm_ip6_config_new ();
g_assert (composite);
/* Merge RA and DHCPv6 configs into the composite config */
/* Merge all the IP configs into the composite config */
if (priv->ac_ip6_config)
nm_ip6_config_merge (composite, priv->ac_ip6_config);
if (priv->dhcp6_ip6_config)
nm_ip6_config_merge (composite, priv->dhcp6_ip6_config);
if (priv->vpn6_config)
nm_ip6_config_merge (composite, priv->vpn6_config);
/* Merge user overrides into the composite config */
nm_ip6_config_merge_setting (composite, nm_connection_get_setting_ip6_config (connection));
@ -4146,6 +4152,8 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason)
/* Clean up nameservers and addresses */
nm_device_set_ip4_config (self, NULL, TRUE, &ignored);
nm_device_set_ip6_config (self, NULL, TRUE, &ignored);
g_clear_object (&priv->vpn4_config);
g_clear_object (&priv->vpn6_config);
/* Clear legacy IPv4 address property */
priv->ip4_address = 0;
@ -4402,6 +4410,25 @@ nm_device_set_ip4_config (NMDevice *self,
return success;
}
void
nm_device_set_vpn4_config (NMDevice *device, NMIP4Config *config)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
if (priv->vpn4_config == config)
return;
g_clear_object (&priv->vpn4_config);
if (config)
priv->vpn4_config = g_object_ref (config);
/* NULL to use existing configs */
if (!ip4_config_merge_and_apply (device, NULL, NULL)) {
nm_log_warn (LOGD_IP4, "(%s): failed to set VPN routes for device",
nm_device_get_ip_iface (device));
}
}
static gboolean
nm_device_set_ip6_config (NMDevice *self,
NMIP6Config *new_config,
@ -4453,6 +4480,25 @@ nm_device_set_ip6_config (NMDevice *self,
return success;
}
void
nm_device_set_vpn6_config (NMDevice *device, NMIP6Config *config)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
if (priv->vpn6_config == config)
return;
g_clear_object (&priv->vpn6_config);
if (config)
priv->vpn6_config = g_object_ref (config);
/* NULL to use existing configs */
if (!ip6_config_merge_and_apply (device, NULL)) {
nm_log_warn (LOGD_IP6, "(%s): failed to set VPN routes for device",
nm_device_get_ip_iface (device));
}
}
NMDHCP6Config *
nm_device_get_dhcp6_config (NMDevice *self)
{
@ -4800,8 +4846,14 @@ dispose (GObject *object)
nm_device_take_down (self, FALSE);
}
g_clear_object (&priv->dev_ip4_config);
g_clear_object (&priv->vpn4_config);
g_clear_object (&priv->ip4_config);
g_clear_object (&priv->ip6_config);
g_clear_object (&priv->ac_ip6_config);
g_clear_object (&priv->dhcp6_ip6_config);
g_clear_object (&priv->vpn6_config);
/* reset the saved RA value */
if (priv->ip6_accept_ra_path) {
nm_utils_do_sysctl (priv->ip6_accept_ra_path,

View file

@ -220,7 +220,10 @@ NMDHCP4Config * nm_device_get_dhcp4_config (NMDevice *dev);
NMDHCP6Config * nm_device_get_dhcp6_config (NMDevice *dev);
NMIP4Config * nm_device_get_ip4_config (NMDevice *dev);
void nm_device_set_vpn4_config (NMDevice *dev, NMIP4Config *config);
NMIP6Config * nm_device_get_ip6_config (NMDevice *dev);
void nm_device_set_vpn6_config (NMDevice *dev, NMIP6Config *config);
/* Master */
gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave);

View file

@ -1592,8 +1592,8 @@ static void
vpn_connection_deactivated (NMPolicy *policy, NMVPNConnection *vpn)
{
NMDnsManager *mgr;
NMIP4Config *ip4_config, *parent_ip4 = NULL;
NMIP6Config *ip6_config, *parent_ip6 = NULL;
NMIP4Config *ip4_config;
NMIP6Config *ip6_config;
const char *ip_iface;
NMDevice *parent;
@ -1607,40 +1607,12 @@ vpn_connection_deactivated (NMPolicy *policy, NMVPNConnection *vpn)
if (ip4_config) {
/* Remove the VPN connection's IP4 config from DNS */
nm_dns_manager_remove_ip4_config (mgr, ip4_config);
/* Re-apply routes and addresses of the VPN connection's parent interface,
* which the VPN might have overridden.
*/
if (parent) {
parent_ip4 = nm_device_get_ip4_config (parent);
if (parent_ip4) {
if (!nm_ip4_config_commit (parent_ip4,
nm_device_get_ip_ifindex (parent),
nm_device_get_priority (parent))) {
nm_log_err (LOGD_VPN, "failed to re-apply VPN parent device IPv4 addresses and routes.");
}
}
}
}
ip6_config = nm_vpn_connection_get_ip6_config (vpn);
if (ip6_config) {
/* Remove the VPN connection's IP6 config from DNS */
nm_dns_manager_remove_ip6_config (mgr, ip6_config);
/* Re-apply routes and addresses of the VPN connection's parent interface,
* which the VPN might have overridden.
*/
if (parent) {
parent_ip6 = nm_device_get_ip6_config (parent);
if (parent_ip6) {
if (!nm_ip6_config_commit (parent_ip6,
nm_device_get_ip_ifindex (parent),
nm_device_get_priority (parent))) {
nm_log_err (LOGD_VPN, "failed to re-apply VPN parent device IPv6 addresses and routes.");
}
}
}
}
update_routing_and_dns (policy, TRUE);

View file

@ -73,8 +73,6 @@ typedef struct {
NMDevice *parent_dev;
gulong device_monitor;
gulong device_ip4;
gulong device_ip6;
NMVPNConnectionState vpn_state;
NMVPNConnectionStateReason failure_reason;
@ -93,9 +91,6 @@ typedef struct {
int ip_ifindex;
char *banner;
guint32 mtu;
NMPlatformIP4Route *ip4_gw_route;
NMPlatformIP6Route *ip6_gw_route;
} NMVPNConnectionPrivate;
#define NM_VPN_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_CONNECTION, NMVPNConnectionPrivate))
@ -178,23 +173,8 @@ vpn_cleanup (NMVPNConnection *connection)
nm_platform_address_flush (priv->ip_ifindex);
}
if (priv->ip4_gw_route) {
nm_platform_ip4_route_delete (
priv->ip4_gw_route->ifindex,
priv->ip4_gw_route->network,
priv->ip4_gw_route->plen,
priv->ip4_gw_route->metric);
g_clear_pointer (&priv->ip4_gw_route, g_free);
}
if (priv->ip6_gw_route) {
nm_platform_ip6_route_delete (
priv->ip6_gw_route->ifindex,
priv->ip6_gw_route->network,
priv->ip6_gw_route->plen,
priv->ip6_gw_route->metric);
g_clear_pointer (&priv->ip6_gw_route, g_free);
}
nm_device_set_vpn4_config (priv->parent_dev, NULL);
nm_device_set_vpn6_config (priv->parent_dev, NULL);
g_free (priv->banner);
priv->banner = NULL;
@ -315,149 +295,103 @@ device_state_changed (NMDevice *device,
}
}
static NMPlatformIP4Route *
static void
add_ip4_vpn_gateway_route (NMDevice *parent_device, guint32 vpn_gw)
{
NMIP4Config *parent_config;
guint32 parent_gw;
NMPlatformIP4Route *route = g_new0 (NMPlatformIP4Route, 1);
NMIP4Route *route;
NMIP4Config *vpn4_config;
g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
g_return_val_if_fail (vpn_gw != 0, NULL);
g_return_if_fail (NM_IS_DEVICE (parent_device));
g_return_if_fail (vpn_gw != 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_val_if_fail (parent_config != NULL, NULL);
g_return_if_fail (parent_config != NULL);
parent_gw = nm_ip4_config_get_gateway (parent_config);
if (!parent_gw)
return;
if (!parent_gw) {
g_free (route);
return NULL;
}
vpn4_config = nm_ip4_config_new ();
route->ifindex = nm_device_get_ip_ifindex (parent_device);
route->network = vpn_gw;
route->plen = 32;
route->gateway = parent_gw;
route->metric = 1024;
route->mss = nm_ip4_config_get_mss (parent_config);
route = nm_ip4_route_new ();
nm_ip4_route_set_dest (route, vpn_gw);
nm_ip4_route_set_prefix (route, 32);
nm_ip4_route_set_next_hop (route, parent_gw);
/* 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))
route->gateway = 0;
nm_ip4_route_set_next_hop (route, 0);
if (!nm_platform_ip4_route_add (route->ifindex,
route->network,
route->plen,
route->gateway,
route->metric,
route->mss)) {
g_free (route);
nm_log_err (LOGD_DEVICE | LOGD_IP4,
"(%s): failed to add IPv4 route to VPN gateway: %s",
nm_device_get_iface (parent_device),
nm_platform_get_error_msg ());
return NULL;
}
nm_ip4_config_take_route (vpn4_config, route);
return route;
/* Ensure there's a route to the parent device's gateway through the
* parent device, since if the VPN claims the default route and the VPN
* routes include a subnet that matches the parent device's subnet,
* the parent device's gateway would get routed through the VPN and fail.
*/
route = nm_ip4_route_new ();
nm_ip4_route_set_dest (route, parent_gw);
nm_ip4_route_set_prefix (route, 32);
nm_ip4_config_take_route (vpn4_config, route);
nm_device_set_vpn4_config (parent_device, vpn4_config);
g_object_unref (vpn4_config);
}
static void
device_ip4_config_changed (NMDevice *device,
GParamSpec *pspec,
gpointer user_data)
{
NMVPNConnection *vpn = NM_VPN_CONNECTION (user_data);
NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn);
if ( (priv->vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
|| !nm_device_get_ip4_config (device))
return;
/* Re-add the VPN gateway route */
if (priv->ip4_external_gw) {
g_free (&priv->ip4_gw_route);
priv->ip4_gw_route = add_ip4_vpn_gateway_route (priv->parent_dev,
priv->ip4_external_gw);
}
}
static NMPlatformIP6Route *
add_ip6_vpn_gateway_route (NMDevice *parent_device,
const struct in6_addr *vpn_gw)
const struct in6_addr *vpn_gw)
{
NMIP6Config *parent_config;
const struct in6_addr *parent_gw;
NMPlatformIP6Route *route = g_new0 (NMPlatformIP6Route, 1);
NMIP6Route *route;
NMIP6Config *vpn6_config;
g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
g_return_val_if_fail (vpn_gw != NULL, NULL);
g_return_if_fail (NM_IS_DEVICE (parent_device));
g_return_if_fail (vpn_gw != NULL);
parent_config = nm_device_get_ip6_config (parent_device);
g_return_val_if_fail (parent_config != NULL, NULL);
g_return_if_fail (parent_config != NULL);
parent_gw = nm_ip6_config_get_gateway (parent_config);
if (!parent_gw)
return;
if (!parent_gw) {
g_free (route);
return NULL;
}
vpn6_config = nm_ip6_config_new ();
route->ifindex = nm_device_get_ip_ifindex (parent_device);
route->network = *vpn_gw;
route->plen = 128;
route->gateway = *parent_gw;
route->metric = 1024;
route->mss = nm_ip6_config_get_mss (parent_config);
route = nm_ip6_route_new ();
nm_ip6_route_set_dest (route, vpn_gw);
nm_ip6_route_set_prefix (route, 128);
nm_ip6_route_set_next_hop (route, parent_gw);
/* 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))
route->gateway = in6addr_any;
nm_ip6_route_set_next_hop (route, &in6addr_any);
if (!nm_platform_ip6_route_add (route->ifindex,
route->network,
route->plen,
route->gateway,
route->metric,
route->mss)) {
g_free (route);
nm_log_err (LOGD_DEVICE | LOGD_IP6,
"(%s): failed to add IPv6 route to VPN gateway: %s",
nm_device_get_iface (parent_device),
nm_platform_get_error_msg ());
return NULL;
}
nm_ip6_config_take_route (vpn6_config, route);
return route;
}
/* Ensure there's a route to the parent device's gateway through the
* parent device, since if the VPN claims the default route and the VPN
* routes include a subnet that matches the parent device's subnet,
* the parent device's gateway would get routed through the VPN and fail.
*/
route = nm_ip6_route_new ();
nm_ip6_route_set_dest (route, parent_gw);
nm_ip6_route_set_prefix (route, 128);
nm_ip6_config_take_route (vpn6_config, route);
static void
device_ip6_config_changed (NMDevice *device,
GParamSpec *pspec,
gpointer user_data)
{
NMVPNConnection *vpn = NM_VPN_CONNECTION (user_data);
NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn);
if ( (priv->vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
|| !nm_device_get_ip6_config (device))
return;
/* Re-add the VPN gateway route */
if (priv->ip6_external_gw) {
g_free (priv->ip6_gw_route);
priv->ip6_gw_route = add_ip6_vpn_gateway_route (priv->parent_dev,
priv->ip6_external_gw);
}
nm_device_set_vpn6_config (parent_device, vpn6_config);
g_object_unref (vpn6_config);
}
NMVPNConnection *
@ -733,15 +667,10 @@ nm_vpn_connection_apply_config (NMVPNConnection *connection)
}
/* Add any explicit route to the VPN gateway through the parent device */
g_clear_pointer (&priv->ip4_gw_route, g_free);
g_clear_pointer (&priv->ip6_gw_route, g_free);
if (priv->ip4_external_gw) {
priv->ip4_gw_route = add_ip4_vpn_gateway_route (priv->parent_dev,
priv->ip4_external_gw);
} else if (priv->ip6_external_gw) {
priv->ip6_gw_route = add_ip6_vpn_gateway_route (priv->parent_dev,
priv->ip6_external_gw);
}
if (priv->ip4_external_gw)
add_ip4_vpn_gateway_route (priv->parent_dev, priv->ip4_external_gw);
if (priv->ip6_external_gw)
add_ip6_vpn_gateway_route (priv->parent_dev, priv->ip6_external_gw);
nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) complete.",
nm_connection_get_id (priv->connection));
@ -1750,13 +1679,6 @@ constructed (GObject *object)
priv->device_monitor = g_signal_connect (device, "state-changed",
G_CALLBACK (device_state_changed),
object);
priv->device_ip4 = g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONFIG,
G_CALLBACK (device_ip4_config_changed),
object);
priv->device_ip6 = g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONFIG,
G_CALLBACK (device_ip6_config_changed),
object);
}
static void
@ -1770,9 +1692,6 @@ dispose (GObject *object)
}
priv->disposed = TRUE;
g_clear_pointer (&priv->ip4_gw_route, g_free);
g_clear_pointer (&priv->ip6_gw_route, g_free);
if (priv->connect_hash)
g_hash_table_destroy (priv->connect_hash);
@ -1781,11 +1700,6 @@ dispose (GObject *object)
if (priv->ip6_external_gw)
g_free (priv->ip6_external_gw);
if (priv->device_ip4)
g_signal_handler_disconnect (priv->parent_dev, priv->device_ip4);
if (priv->device_ip6)
g_signal_handler_disconnect (priv->parent_dev, priv->device_ip6);
if (priv->device_monitor)
g_signal_handler_disconnect (priv->parent_dev, priv->device_monitor);