about summary refs log tree commit diff
path: root/src/nm-system.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-system.c')
-rw-r--r--src/nm-system.c268
1 files changed, 229 insertions, 39 deletions
diff --git a/src/nm-system.c b/src/nm-system.c
index 91153ec2..b980dc53 100644
--- a/src/nm-system.c
+++ b/src/nm-system.c
@@ -349,14 +349,15 @@ add_ip4_addresses (NMIP4Config *config, int ifindex)
 }
 
 struct rtnl_route *
-nm_system_add_ip4_vpn_gateway_route (NMDevice *parent_device, NMIP4Config *vpn_config)
+nm_system_add_ip4_vpn_gateway_route (NMDevice *parent_device, guint32 vpn_gw)
 {
 	NMIP4Config *parent_config;
-	guint32 parent_gw = 0, parent_prefix = 0, vpn_gw = 0, i;
+	guint32 parent_gw = 0, parent_prefix = 0, i;
 	NMIP4Address *tmp;
 	struct rtnl_route *route = NULL;
 
 	g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
+	g_return_val_if_fail (vpn_gw != 0, NULL);
 
 	/* 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.
@@ -374,15 +375,7 @@ nm_system_add_ip4_vpn_gateway_route (NMDevice *parent_device, NMIP4Config *vpn_c
 		}
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_addresses (vpn_config); i++) {
-		tmp = nm_ip4_config_get_address (vpn_config, i);
-		if (nm_ip4_address_get_gateway (tmp)) {
-			vpn_gw = nm_ip4_address_get_gateway (tmp);
-			break;
-		}
-	}
-
-	if (!parent_gw || !vpn_gw)
+	if (!parent_gw)
 		return NULL;
 
 	/* If the VPN gateway is in the same subnet as one of the parent device's
@@ -479,7 +472,7 @@ nm_system_set_ip6_route (int ifindex,
 	struct rtnl_route *route;
 	int err = 0;
 
-	g_return_val_if_fail (ifindex >= 0, -1);
+	g_return_val_if_fail (ifindex > 0, -1);
 
 	nlh = nm_netlink_get_default_handle ();
 	g_return_val_if_fail (nlh != NULL, -1);
@@ -520,6 +513,96 @@ nm_system_set_ip6_route (int ifindex,
 }
 
 static gboolean
+ip6_dest_in_same_subnet (NMIP6Config *config, const struct in6_addr *dest, guint32 dest_prefix)
+{
+	int num;
+	int i;
+
+	num = nm_ip6_config_get_num_addresses (config);
+	for (i = 0; i < num; i++) {
+		NMIP6Address *addr = nm_ip6_config_get_address (config, i);
+		guint32 prefix = nm_ip6_address_get_prefix (addr);
+		const struct in6_addr *address = nm_ip6_address_get_address (addr);
+
+		if (prefix <= dest_prefix) {
+			const guint8 *maskbytes = (const guint8 *)address;
+			const guint8 *addrbytes = (const guint8 *)dest;
+			int nbytes, nbits;
+
+			/* Copied from g_inet_address_mask_matches() */
+			nbytes = prefix / 8;
+			if (nbytes != 0 && memcmp (maskbytes, addrbytes, nbytes) != 0)
+				continue;
+
+			nbits = prefix % 8;
+			if (nbits == 0)
+				return TRUE;
+
+			if (maskbytes[nbytes] == (addrbytes[nbytes] & (0xFF << (8 - nbits))))
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+struct rtnl_route *
+nm_system_add_ip6_vpn_gateway_route (NMDevice *parent_device,
+                                     const struct in6_addr *vpn_gw)
+{
+	NMIP6Config *parent_config;
+	const struct in6_addr *parent_gw = NULL;
+	guint32 parent_prefix = 0;
+	int i, err;
+	NMIP6Address *tmp;
+	struct rtnl_route *route = NULL;
+
+	g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
+	g_return_val_if_fail (vpn_gw != NULL, NULL);
+
+	/* This is all just the same as
+	 * nm_system_add_ip4_vpn_gateway_route(), except with an IPv6
+	 * address for the VPN gateway.
+	 */
+
+	parent_config = nm_device_get_ip6_config (parent_device);
+	g_return_val_if_fail (parent_config != NULL, NULL);
+
+	for (i = 0; i < nm_ip6_config_get_num_addresses (parent_config); i++) {
+		tmp = nm_ip6_config_get_address (parent_config, i);
+		if (nm_ip6_address_get_gateway (tmp)) {
+			parent_gw = nm_ip6_address_get_gateway (tmp);
+			parent_prefix = nm_ip6_address_get_prefix (tmp);
+			break;
+		}
+	}
+
+	if (!parent_gw)
+		return NULL;
+
+	if (ip6_dest_in_same_subnet (parent_config, vpn_gw, parent_prefix)) {
+		err = nm_system_set_ip6_route (nm_device_get_ip_ifindex (parent_device),
+		                               vpn_gw, 128, NULL, 0,
+		                               nm_ip6_config_get_mss (parent_config),
+		                               RTPROT_UNSPEC, RT_TABLE_UNSPEC,
+		                               &route);
+	} else {
+		err = nm_system_set_ip6_route (nm_device_get_ip_ifindex (parent_device),
+		                               vpn_gw, 128, parent_gw, 0,
+		                               nm_ip6_config_get_mss (parent_config),
+		                               RTPROT_UNSPEC, RT_TABLE_UNSPEC,
+		                               &route);
+	}
+
+	if (err) {
+		nm_log_err (LOGD_DEVICE | LOGD_IP6,
+		            "(%s): failed to add IPv6 route to VPN gateway (%d)",
+		            nm_device_get_iface (parent_device), err);
+	}
+	return route;
+}
+
+static gboolean
 add_ip6_addresses (NMIP6Config *config, int ifindex)
 {
 	char *iface;
@@ -909,7 +992,7 @@ nm_system_replace_default_ip4_route_vpn (int ifindex,
 		goto out;
 
 	if ((err != -NLE_OBJ_NOTFOUND) && (err != -NLE_FAILURE)) {
-		nm_log_err (LOGD_DEVICE | LOGD_IP4,
+		nm_log_err (LOGD_DEVICE | LOGD_VPN | LOGD_IP4,
 		            "(%s): failed to set IPv4 default route: %d",
 		            iface, err);
 		goto out;
@@ -924,7 +1007,7 @@ nm_system_replace_default_ip4_route_vpn (int ifindex,
 	err = replace_default_ip4_route (ifindex, int_gw, mss);
 	if (err != 0) {
 		nm_netlink_route_delete (gw_route);
-		nm_log_err (LOGD_DEVICE | LOGD_IP4,
+		nm_log_err (LOGD_DEVICE | LOGD_VPN | LOGD_IP4,
 		            "(%s): failed to set IPv4 default route (pass #2): %d",
 		            iface, err);
 	} else
@@ -989,7 +1072,7 @@ out:
 }
 
 static struct rtnl_route *
-add_ip6_route_to_gateway (int ifindex, const struct in6_addr *gw)
+add_ip6_route_to_gateway (int ifindex, const struct in6_addr *gw, int mss)
 {
 	struct nl_sock *nlh;
 	struct rtnl_route *route = NULL;
@@ -999,7 +1082,7 @@ add_ip6_route_to_gateway (int ifindex, const struct in6_addr *gw)
 	g_return_val_if_fail (nlh != NULL, NULL);
 
 	/* Gateway might be over a bridge; try adding a route to gateway first */
-	route = nm_netlink_route_new (ifindex, AF_INET6, 0,
+	route = nm_netlink_route_new (ifindex, AF_INET6, mss,
 	                              NMNL_PROP_SCOPE, RT_SCOPE_LINK,
 	                              NMNL_PROP_TABLE, RT_TABLE_MAIN,
 	                              NULL);
@@ -1023,7 +1106,7 @@ add_ip6_route_to_gateway (int ifindex, const struct in6_addr *gw)
 }
 
 static int
-replace_default_ip6_route (int ifindex, const struct in6_addr *gw)
+add_default_ip6_route (int ifindex, const struct in6_addr *gw, int mss)
 {
 	struct rtnl_route *route = NULL;
 	struct nl_sock *nlh;
@@ -1034,25 +1117,81 @@ replace_default_ip6_route (int ifindex, const struct in6_addr *gw)
 	nlh = nm_netlink_get_default_handle ();
 	g_return_val_if_fail (nlh != NULL, -ENOMEM);
 
-	route = nm_netlink_route_new (ifindex, AF_INET6, 0,
+	route = nm_netlink_route_new (ifindex, AF_INET6, mss,
 	                              NMNL_PROP_SCOPE, RT_SCOPE_UNIVERSE,
 	                              NMNL_PROP_TABLE, RT_TABLE_MAIN,
+	                              NMNL_PROP_PRIO, 1,
 	                              NULL);
 	g_return_val_if_fail (route != NULL, -ENOMEM);
 
 	/* Add the new default route */
-	err = nm_netlink_route6_add (route, &in6addr_any, 0, gw, NLM_F_REPLACE);
-	if (err == -NLE_EXIST) {
-		/* FIXME: even though we use NLM_F_REPLACE the kernel won't replace
-		 * the route if it's the same.  Suppress the pointless error.
-		 */
+	err = nm_netlink_route6_add (route, &in6addr_any, 0, gw, NLM_F_CREATE);
+	if (err == -NLE_EXIST)
 		err = 0;
-	}
 
 	rtnl_route_put (route);
 	return err;
 }
 
+static struct rtnl_route *
+find_static_default_routes (struct rtnl_route *route,
+                            struct nl_addr *dst,
+                            const char *iface,
+                            gpointer user_data)
+{
+	GList **def_routes = user_data;
+
+	if (   nl_addr_get_prefixlen (dst) == 0
+	    && rtnl_route_get_protocol (route) == RTPROT_STATIC) {
+		rtnl_route_get (route);
+		*def_routes = g_list_prepend (*def_routes, route);
+	}
+
+	return NULL;
+}
+
+static int
+replace_default_ip6_route (int ifindex, const struct in6_addr *gw, int mss)
+{
+	GList *def_routes, *iter;
+	struct rtnl_route *route;
+	char *iface;
+	char gw_str[INET6_ADDRSTRLEN + 1];
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (gw != NULL, FALSE);
+
+	if (nm_logging_level_enabled (LOGL_DEBUG)) {
+		memset (gw_str, 0, sizeof (gw_str));
+		if (inet_ntop (AF_INET6, gw, gw_str, sizeof (gw_str) - 1))
+			nm_log_dbg (LOGD_IP6, "Setting IPv6 default route via %s", gw_str);
+	}
+
+	/* We can't just use NLM_F_REPLACE here like in the IPv4 case, because
+	 * the kernel doesn't like it if we replace the default routes it
+	 * creates. (See rh#785772.) So we delete any non-kernel default routes,
+	 * and then add a new default route of our own with a lower metric than
+	 * the kernel ones.
+	 */
+	def_routes = NULL;
+	nm_netlink_foreach_route (ifindex, AF_INET6, RT_SCOPE_UNIVERSE, TRUE,
+	                          find_static_default_routes, &def_routes);
+	for (iter = def_routes; iter; iter = iter->next) {
+		route = iter->data;
+		if (!nm_netlink_route_delete (route)) {
+			iface = nm_netlink_index_to_iface (ifindex);
+			nm_log_err (LOGD_DEVICE | LOGD_IP6,
+			            "(%s): failed to delete existing IPv6 default route",
+			            iface);
+			g_free (iface);
+		}
+		rtnl_route_put (route);
+	}
+	g_list_free (def_routes);
+
+	return add_default_ip6_route (ifindex, gw, mss);
+}
+
 /*
  * nm_system_replace_default_ip6_route
  *
@@ -1067,11 +1206,8 @@ nm_system_replace_default_ip6_route (int ifindex, const struct in6_addr *gw)
 	char *iface;
 	int err;
 
-	err = replace_default_ip6_route (ifindex, gw);
-	if (err == 0)
-		return TRUE;
-
-	if (err == -NLE_EXIST)
+	err = replace_default_ip6_route (ifindex, gw, 0);
+	if (err == 0 || err == -NLE_EXIST)
 		return TRUE;
 
 	iface = nm_netlink_index_to_iface (ifindex);
@@ -1086,12 +1222,12 @@ nm_system_replace_default_ip6_route (int ifindex, const struct in6_addr *gw)
 	}
 
 	/* Try adding a direct route to the gateway first */
-	gw_route = add_ip6_route_to_gateway (ifindex, gw);
+	gw_route = add_ip6_route_to_gateway (ifindex, gw, 0);
 	if (!gw_route)
 		goto out;
 
 	/* Try adding the original route again */
-	err = replace_default_ip6_route (ifindex, gw);
+	err = replace_default_ip6_route (ifindex, gw, 0);
 	if (err != 0) {
 		nm_netlink_route_delete (gw_route);
 		nm_log_err (LOGD_DEVICE | LOGD_IP6,
@@ -1107,6 +1243,60 @@ out:
 	return success;
 }
 
+gboolean
+nm_system_replace_default_ip6_route_vpn (int ifindex,
+                                         const struct in6_addr *ext_gw,
+                                         const struct in6_addr *int_gw,
+                                         guint32 mss,
+                                         int parent_ifindex,
+                                         guint32 parent_mss)
+{
+	struct rtnl_route *gw_route = NULL;
+	struct nl_sock *nlh;
+	gboolean success = FALSE;
+	int err;
+	char *iface;
+
+	nlh = nm_netlink_get_default_handle ();
+	g_return_val_if_fail (nlh != NULL, FALSE);
+
+	err = replace_default_ip6_route (ifindex, int_gw, mss);
+	if (err == 0)
+		return TRUE;
+
+	iface = nm_netlink_index_to_iface (ifindex);
+	if (!iface)
+		goto out;
+
+	if ((err != -NLE_OBJ_NOTFOUND) && (err != -NLE_FAILURE)) {
+		nm_log_err (LOGD_DEVICE | LOGD_VPN | LOGD_IP6,
+		            "(%s): failed to set IPv6 default route: %d",
+		            iface, err);
+		goto out;
+	}
+
+	/* Try adding a direct route to the gateway first */
+	gw_route = add_ip6_route_to_gateway (parent_ifindex, ext_gw, parent_mss);
+	if (!gw_route)
+		goto out;
+
+	/* Try adding the original route again */
+	err = replace_default_ip6_route (ifindex, int_gw, mss);
+	if (err != 0) {
+		nm_netlink_route_delete (gw_route);
+		nm_log_err (LOGD_DEVICE | LOGD_VPN | LOGD_IP6,
+		            "(%s): failed to set IPv6 default route (pass #2): %d",
+		            iface, err);
+	} else
+		success = TRUE;
+
+out:
+	if (gw_route)
+		rtnl_route_put (gw_route);
+	g_free (iface);
+	return success;
+}
+
 /*
  * nm_system_iface_flush_addresses
  *
@@ -1435,9 +1625,9 @@ nm_system_iface_enslave (gint master_ifindex,
 	struct nl_sock *sock;
 	int err;
 
-	g_return_val_if_fail (master_ifindex >= 0, FALSE);
+	g_return_val_if_fail (master_ifindex > 0, FALSE);
 	g_return_val_if_fail (master_iface != NULL, FALSE);
-	g_return_val_if_fail (slave_ifindex >= 0, FALSE);
+	g_return_val_if_fail (slave_ifindex > 0, FALSE);
 	g_return_val_if_fail (slave_iface != NULL, FALSE);
 
 	sock = nm_netlink_get_default_handle ();
@@ -1520,9 +1710,9 @@ nm_system_iface_release (gint master_ifindex,
 	struct nl_sock *sock;
 	int err;
 
-	g_return_val_if_fail (master_ifindex >= 0, FALSE);
+	g_return_val_if_fail (master_ifindex > 0, FALSE);
 	g_return_val_if_fail (master_iface != NULL, FALSE);
-	g_return_val_if_fail (slave_ifindex >= 0, FALSE);
+	g_return_val_if_fail (slave_ifindex > 0, FALSE);
 	g_return_val_if_fail (slave_iface != NULL, FALSE);
 
 	sock = nm_netlink_get_default_handle ();
@@ -1561,7 +1751,7 @@ nm_system_get_iface_type (int ifindex, const char *name)
 	char *type;
 	int res = NM_IFACE_TYPE_UNSPEC;
 
-	g_return_val_if_fail (ifindex >= 0 || name, NM_IFACE_TYPE_UNSPEC);
+	g_return_val_if_fail (ifindex > 0 || name, NM_IFACE_TYPE_UNSPEC);
 
 	nlh = nm_netlink_get_default_handle ();
 	if (!nlh)
@@ -1706,7 +1896,7 @@ nm_system_iface_compat_add_vlan_device (const char *master, int vid)
 	}
 
 	memset (&if_request, 0, sizeof (struct vlan_ioctl_args));
-	strcpy (if_request.device1, master);
+	g_strlcpy (if_request.device1, master, sizeof (if_request.device1));
 	if_request.cmd = ADD_VLAN_CMD;
 	if_request.u.VID = vid;
 
@@ -1732,7 +1922,7 @@ nm_system_iface_compat_rem_vlan_device (const char *iface)
 	}
 
 	memset (&if_request, 0, sizeof (struct vlan_ioctl_args));
-        strcpy (if_request.device1, iface);
+	g_strlcpy (if_request.device1, iface, sizeof (if_request.device1));
 	if_request.cmd = DEL_VLAN_CMD;
 
 	if (ioctl (fd, SIOCSIFVLAN, &if_request) < 0) {
@@ -1868,7 +2058,7 @@ nm_system_add_vlan_iface (NMConnection *connection,
 	guint32 vlan_flags = 0;
 	guint32 num, i, from, to;
 
-	g_return_val_if_fail (parent_ifindex >= 0, FALSE);
+	g_return_val_if_fail (parent_ifindex > 0, FALSE);
 
 	nlh = nm_netlink_get_default_handle ();
 	g_return_val_if_fail (nlh != NULL, FALSE);