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.c192
1 files changed, 142 insertions, 50 deletions
diff --git a/src/nm-system.c b/src/nm-system.c
index b980dc53..09e1da58 100644
--- a/src/nm-system.c
+++ b/src/nm-system.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2010 Red Hat, Inc.
+ * Copyright (C) 2004 - 2012 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  * Copyright (C) 1996 - 1997 Yoichi Hariguchi <yoichi@fore.com>
  * Copyright (C) January, 1998 Sergei Viznyuk <sv@phystech.com>
@@ -44,6 +44,7 @@
 #include <linux/if.h>
 #include <linux/sockios.h>
 #include <linux/if_bonding.h>
+#include <linux/if_vlan.h>
 
 #include "nm-system.h"
 #include "nm-device.h"
@@ -1734,6 +1735,63 @@ nm_system_iface_release (gint master_ifindex,
 }
 
 /**
+ * nm_system_get_iface_type_compat:
+ * @ifindex: interface index
+ * @name: name of interface
+ *
+ * Lookup the type of an interface.  At least one of @ifindex or @name must
+ * be provided.
+ *
+ * Returns: Interface type (NM_IFACE_TYPE_*) or NM_IFACE_TYPE_UNSPEC.
+ **/
+static int
+nm_system_compat_get_iface_type (int ifindex, const char *name)
+{
+	int res = NM_IFACE_TYPE_UNSPEC;
+	char *ifname = NULL;
+	struct vlan_ioctl_args ifv;
+	struct ifreq ifr;
+	struct ifbond ifb;
+	int fd;
+
+	g_return_val_if_fail (ifindex > 0 || name, NM_IFACE_TYPE_UNSPEC);
+
+	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
+		nm_log_err (LOGD_DEVICE, "couldn't open control socket.");
+		goto out;
+	}
+
+	if (!name) {
+		g_assert (ifindex > 0);
+		ifname = nm_netlink_index_to_iface (ifindex);
+	}
+
+	/* Check VLAN */
+	memset (&ifv, 0, sizeof (ifv));
+	ifv.cmd = GET_VLAN_VID_CMD;
+	strncpy (ifv.device1, ifname ? ifname : name, sizeof (ifv.device1) - 1);
+	if (ioctl (fd, SIOCGIFVLAN, &ifv) == 0) {
+		res = NM_IFACE_TYPE_VLAN;
+		goto out;
+	}
+
+	/* and bond */
+	memset (&ifr, 0, sizeof (ifr));
+	strncpy (ifr.ifr_name, ifname ? ifname : name, sizeof (ifr.ifr_name) - 1);
+	memset (&ifb, 0, sizeof (ifb));
+	ifr.ifr_data = (caddr_t) &ifb;
+	if (ioctl (fd, SIOCBONDINFOQUERY, &ifr) == 0) {
+		res = NM_IFACE_TYPE_BOND;
+		goto out;
+	}
+
+out:
+	close (fd);
+	g_free (ifname);
+	return res;
+}
+
+/**
  * nm_system_get_iface_type:
  * @ifindex: interface index
  * @name: name of interface
@@ -1750,6 +1808,7 @@ nm_system_get_iface_type (int ifindex, const char *name)
 	struct nl_sock *nlh;
 	char *type;
 	int res = NM_IFACE_TYPE_UNSPEC;
+	int err;
 
 	g_return_val_if_fail (ifindex > 0 || name, NM_IFACE_TYPE_UNSPEC);
 
@@ -1758,8 +1817,12 @@ nm_system_get_iface_type (int ifindex, const char *name)
 		goto out;
 
 	/* Prefer interface indexes to names */
-	if (rtnl_link_get_kernel (nlh, ifindex, ifindex < 0 ? name : NULL, &result) < 0)
+	err = rtnl_link_get_kernel (nlh, ifindex, ifindex < 0 ? name : NULL, &result);
+	if (err < 0) {
+		if (err == -NLE_OPNOTSUPP)
+			res = nm_system_compat_get_iface_type (ifindex, name);
 		goto out;
+	}
 
 	type = rtnl_link_get_type (result);
 
@@ -1901,7 +1964,7 @@ nm_system_iface_compat_add_vlan_device (const char *master, int vid)
 	if_request.u.VID = vid;
 
 	if (ioctl (fd, SIOCSIFVLAN, &if_request) < 0) {
-		nm_log_err (LOGD_DEVICE, "couldn't add vlan device %s vid %d.", master, vid);
+		nm_log_err (LOGD_DEVICE, "couldn't add vlan device %s vid %d: %d.", master, vid, errno);
 		close (fd);
 		return FALSE;
 	}
@@ -1937,8 +2000,8 @@ nm_system_iface_compat_rem_vlan_device (const char *iface)
 
 static gboolean
 nm_system_iface_compat_add_vlan (NMConnection *connection,
-				const char *iface,
-				int master_ifindex)
+                                 const char *iface,
+                                 int master_ifindex)
 {
 	NMSettingVlan *s_vlan;
 	int vlan_id;
@@ -1947,7 +2010,8 @@ nm_system_iface_compat_add_vlan (NMConnection *connection,
 	int ifindex;
 	struct rtnl_link *new_link = NULL;
 	char *master = nm_netlink_index_to_iface (master_ifindex);
-	char *name = NULL;
+	int itype;
+	gboolean created = FALSE;
 
 	s_vlan = nm_connection_get_setting_vlan (connection);
 	g_return_val_if_fail (s_vlan, FALSE);
@@ -1959,78 +2023,106 @@ nm_system_iface_compat_add_vlan (NMConnection *connection,
 		g_return_val_if_fail (iface != NULL, FALSE);
 	}
 
-	/*
-	 * Use VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD as default,
-	 * we will overwrite it with rtnl_link_set_name() later.
-	 */
-	name = nm_utils_new_vlan_name(master, vlan_id);
+	itype = nm_system_get_iface_type (-1, iface);
+	if (itype == NM_IFACE_TYPE_UNSPEC) {
+		char *name;
+
+		/* Create the VLAN interface.  Use VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD as
+		 * default and change the name later via nm_system_iface_compat_set_name().
+		 * The old ioctl-based VLAN kernel API has no ability to directly return
+		 * the new interface's name or index, so we have to create it with a
+		 * known name and do the rename dance instead.
+		 */
+		if (!nm_system_iface_compat_add_vlan_device (master, vlan_id))
+			return FALSE;
 
-	/*
-	 * vconfig add
-	 */
+		/* And rename it to what the connection wants */
+		name = nm_utils_new_vlan_name (master, vlan_id);
+		if (strcmp (name, iface) != 0) {
+			if (!nm_system_iface_compat_set_name (name, iface)) {
+				nm_system_iface_compat_rem_vlan_device (name);
+				g_free (name);
+				return FALSE;
+			}
+		}
+		g_free (name);
+		created = TRUE;
+	} else if (itype != NM_IFACE_TYPE_VLAN) {
+		nm_log_err (LOGD_DEVICE, "(%s): already exists but is not a VLAN interface.", iface);
+		return FALSE;
+	} else {
+		int tmp_vlan_id = -1, tmp_master_ifindex = -1;
+
+		/* VLAN interface with this name already exists. Be a bit paranoid and
+		 * double-check the VLAN ID and parent ifindex.
+		 */
+		ifindex = nm_netlink_iface_to_index (iface);
+		if (ifindex <= 0)
+			return FALSE;
 
-	if (!nm_system_iface_compat_add_vlan_device (master, vlan_id))
-		goto err_out;
+		if (!nm_system_get_iface_vlan_info (ifindex, &tmp_master_ifindex, &tmp_vlan_id)) {
+			nm_log_err (LOGD_DEVICE, "(%s): failed to get VLAN interface info.", iface);
+			return FALSE;
+		}
 
-	/*
-	 * get corresponding rtnl_link
-	 */
+		if (tmp_master_ifindex != master_ifindex) {
+			nm_log_err (LOGD_DEVICE, "(%s): master ifindex (%d) does match expected (%d).",
+			            iface, tmp_master_ifindex, master_ifindex);
+			return FALSE;
+		}
 
-	if (!nm_system_iface_compat_set_name (name, iface))
-		goto err_out_delete_vlan_with_default_name;
+		if (tmp_vlan_id != vlan_id) {
+			nm_log_err (LOGD_DEVICE, "(%s): VLAN ID %d does match expected ID %d.",
+			            iface, tmp_vlan_id, vlan_id);
+			return FALSE;
+		}
+
+		nm_log_dbg (LOGD_DEVICE, "(%s): found existing VLAN interface.", iface);
+	}
 
 	ifindex = nm_netlink_iface_to_index (iface);
 	if (ifindex <= 0)
-		goto err_out;
+		goto error;
 
 	new_link = nm_netlink_index_to_rtnl_link (ifindex);
 	if (!new_link)
-		goto err_out_delete_vlan_with_default_name;
+		goto error;
 
-	/*
-	 * vconfig set_flag
-	 */
+	/* vconfig set_flag */
 	vlan_flags = nm_setting_vlan_get_flags (s_vlan);
-	if (vlan_flags)
+	if (vlan_flags) {
 		if (rtnl_link_vlan_set_flags (new_link, vlan_flags))
-			goto err_out_delete_vlan_with_new_name;
+			goto error_new_link;
+	}
 
-	/*
-	 * vconfig set_ingress_map
-	 */
+	/* vconfig set_ingress_map */
 	num = nm_setting_vlan_get_num_priorities (s_vlan, NM_VLAN_INGRESS_MAP);
 	for (i = 0; i < num; i++) {
-		if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_INGRESS_MAP, i, &from, &to))
+		if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_INGRESS_MAP, i, &from, &to)) {
 			if (rtnl_link_vlan_set_ingress_map (new_link, from, to))
-				goto err_out_delete_vlan_with_new_name;
+				goto error_new_link;
+		}
 	}
 
-	/*
-	 * vconfig set_egress_map
-	 */
+	/* vconfig set_egress_map */
 	num = nm_setting_vlan_get_num_priorities (s_vlan, NM_VLAN_EGRESS_MAP);
 	for (i = 0; i < num; i++) {
-		if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_EGRESS_MAP, i, &from, &to))
+		if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_EGRESS_MAP, i, &from, &to)) {
 			if (rtnl_link_vlan_set_egress_map (new_link, from, to))
-				goto err_out_delete_vlan_with_new_name;
+				goto error_new_link;
+		}
 	}
 
 	rtnl_link_put (new_link);
 	return TRUE;
 
-err_out:
-	g_free (name);
-	return FALSE;
-
-err_out_delete_vlan_with_default_name:
-	nm_system_iface_compat_rem_vlan_device (name);
-	g_free (name);
-	return FALSE;
-
-err_out_delete_vlan_with_new_name:
+error_new_link:
 	rtnl_link_put (new_link);
-	nm_system_iface_compat_rem_vlan_device (iface);
-	g_free (name);
+	/* fall through */
+
+error:
+	if (created)
+		nm_system_iface_compat_rem_vlan_device (iface);
 	return FALSE;
 }