about summary refs log tree commit diff
path: root/src/nm-dcb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-dcb.c')
-rw-r--r--src/nm-dcb.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
new file mode 100644
index 00000000..3bdbf7d5
--- /dev/null
+++ b/src/nm-dcb.c
@@ -0,0 +1,426 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <sys/wait.h>
+#include <string.h>
+
+#include <glib.h>
+#include "nm-dcb.h"
+#include "nm-platform.h"
+#include "NetworkManagerUtils.h"
+#include "nm-posix-signals.h"
+#include "nm-logging.h"
+
+GQuark
+nm_dcb_error_quark (void)
+{
+	static GQuark ret = 0;
+
+	if (ret == 0)
+		ret = g_quark_from_static_string ("nm-dcb-error");
+	return ret;
+}
+
+static const char *helper_names[] = { "dcbtool", "fcoeadm" };
+
+gboolean
+do_helper (const char *iface,
+           guint which,
+           DcbFunc run_func,
+           gpointer user_data,
+           GError **error,
+           const char *fmt,
+           ...)
+{
+	char **argv = NULL, **split = NULL, *cmdline, *errmsg = NULL;
+	gboolean success = FALSE;
+	guint i, u;
+	va_list args;
+
+	g_return_val_if_fail (fmt != NULL, FALSE);
+
+	va_start (args, fmt);
+	cmdline = g_strdup_vprintf (fmt, args);
+	va_end (args);
+
+	split = g_strsplit_set (cmdline, " ", 0);
+	if (!split) {
+		g_set_error (error, NM_DCB_ERROR, NM_DCB_ERROR_INTERNAL,
+		             "failure parsing %s command line", helper_names[which]);
+		goto out;
+	}
+
+	/* Allocate space for path, custom arg, interface name, arguments, and NULL */
+	i = u = 0;
+	argv = g_new0 (char *, g_strv_length (split) + 4);
+	argv[i++] = NULL;  /* Placeholder for dcbtool path */
+	if (which == DCBTOOL) {
+		argv[i++] = "sc";
+		argv[i++] = (char *) iface;
+	}
+	while (u < g_strv_length (split))
+		argv[i++] = split[u++];
+	argv[i++] = NULL;
+	success = run_func (argv, which, user_data, error);
+	if (!success && error)
+		g_assert (*error);
+
+out:
+	if (split)
+		g_strfreev (split);
+	g_free (argv);
+	g_free (cmdline);
+	g_free (errmsg);
+	return success;
+}
+
+gboolean
+_dcb_enable (const char *iface,
+             gboolean enable,
+             DcbFunc run_func,
+             gpointer user_data,
+             GError **error)
+{
+	if (enable)
+		return do_helper (iface, DCBTOOL, run_func, user_data, error, "dcb on");
+	else
+		return do_helper (iface, DCBTOOL, run_func, user_data, error, "dcb off");
+}
+
+#define SET_FLAGS(f, tag) \
+G_STMT_START { \
+	if (!do_helper (iface, DCBTOOL, run_func, user_data, error, tag " e:%c a:%c w:%c", \
+	                 f & NM_SETTING_DCB_FLAG_ENABLE ? '1' : '0', \
+	                 f & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0', \
+	                 f & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0')) \
+		return FALSE; \
+} G_STMT_END
+
+#define SET_APP(f, s, tag) \
+G_STMT_START { \
+	gint prio = nm_setting_dcb_get_app_##tag##_priority (s); \
+ \
+	SET_FLAGS (f, "app:" #tag); \
+	if ((f & NM_SETTING_DCB_FLAG_ENABLE) && (prio >= 0)) { \
+		if (!do_helper (iface, DCBTOOL, run_func, user_data, error, "app:" #tag " appcfg:%02x", (1 << prio))) \
+			return FALSE; \
+	} \
+} G_STMT_END
+
+gboolean
+_dcb_setup (const char *iface,
+            NMSettingDcb *s_dcb,
+            DcbFunc run_func,
+            gpointer user_data,
+            GError **error)
+{
+	NMSettingDcbFlags flags;
+	guint i;
+
+	g_assert (s_dcb);
+
+	/* FCoE */
+	flags = nm_setting_dcb_get_app_fcoe_flags (s_dcb);
+	SET_APP (flags, s_dcb, fcoe);
+
+	/* iSCSI */
+	flags = nm_setting_dcb_get_app_iscsi_flags (s_dcb);
+	SET_APP (flags, s_dcb, iscsi);
+
+	/* FIP */
+	flags = nm_setting_dcb_get_app_fip_flags (s_dcb);
+	SET_APP (flags, s_dcb, fip);
+
+	/* Priority Flow Control */
+	flags = nm_setting_dcb_get_priority_flow_control_flags (s_dcb);
+	SET_FLAGS (flags, "pfc");
+	if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
+		char buf[10];
+
+		for (i = 0; i < 8; i++)
+			buf[i] = nm_setting_dcb_get_priority_flow_control (s_dcb, i) ? '1' : '0';
+		buf[i] = 0;
+		if (!do_helper (iface, DCBTOOL, run_func, user_data, error, "pfc pfcup:%s", buf))
+			return FALSE;
+	}
+
+	/* Priority Groups */
+	flags = nm_setting_dcb_get_priority_group_flags (s_dcb);
+	if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
+		GString *s;
+		gboolean success;
+		guint id;
+
+		s = g_string_sized_new (150);
+
+		g_string_append_printf (s, "pg e:1 a:%c w:%c",
+		                        flags & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0',
+		                        flags & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0');
+
+		/* Priority Groups */
+		g_string_append (s, " pgid:");
+		for (i = 0; i < 8; i++) {
+			id = nm_setting_dcb_get_priority_group_id (s_dcb, i);
+			g_assert (id < 8 || id == 15);
+			g_string_append_c (s, (id < 8) ? ('0' + id) : 'f');
+		}
+
+		/* Priority Group Bandwidth */
+		g_string_append_printf (s, " pgpct:%u,%u,%u,%u,%u,%u,%u,%u",
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 0),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 1),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 2),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 3),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 4),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 5),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 6),
+		                        nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 7));
+
+		/* Priority Bandwidth */
+		g_string_append_printf (s, " uppct:%u,%u,%u,%u,%u,%u,%u,%u",
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 0),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 1),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 2),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 3),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 4),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 5),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 6),
+		                        nm_setting_dcb_get_priority_bandwidth (s_dcb, 7));
+
+		/* Strict Bandwidth */
+		g_string_append (s, " strict:");
+		for (i = 0; i < 8; i++)
+			g_string_append_c (s, nm_setting_dcb_get_priority_strict_bandwidth (s_dcb, i) ? '1' : '0');
+
+		/* Priority Traffic Class */
+		g_string_append (s, " up2tc:");
+		for (i = 0; i < 8; i++) {
+			id = nm_setting_dcb_get_priority_traffic_class (s_dcb, i);
+			g_assert (id < 8);
+			g_string_append_c (s, '0' + id);
+		}
+
+		success = do_helper (iface, DCBTOOL, run_func, user_data, error, "%s", s->str);
+		g_string_free (s, TRUE);
+		if (!success)
+			return FALSE;
+	} else {
+		/* Ignore disable failure since lldpad <= 0.9.46 does not support disabling
+		 * priority groups without specifying an entire PG config.
+		 */
+		(void) do_helper (iface, DCBTOOL, run_func, user_data, error, "pg e:0");
+	}
+
+	return TRUE;
+}
+
+gboolean
+_dcb_cleanup (const char *iface,
+              DcbFunc run_func,
+              gpointer user_data,
+              GError **error)
+{
+	const char *cmds[] = {
+		"app:fcoe e:0",
+		"app:iscsi e:0",
+		"app:fip e:0",
+		"pfc e:0",
+		"pg e:0",
+		NULL
+	};
+	const char **iter = cmds;
+	gboolean success = TRUE;
+
+	/* Turn everything off and return first error we get (if any) */
+	while (iter && *iter) {
+		if (!do_helper (iface, DCBTOOL, run_func, user_data, success ? error : NULL, "%s", *iter))
+			success = FALSE;
+		iter++;
+	}
+
+	if (!_dcb_enable (iface, FALSE, run_func, user_data, success ? error : NULL))
+		success = FALSE;
+
+	return success;
+}
+
+gboolean
+_fcoe_setup (const char *iface,
+             NMSettingDcb *s_dcb,
+             DcbFunc run_func,
+             gpointer user_data,
+             GError **error)
+{
+	NMSettingDcbFlags flags;
+
+	g_assert (s_dcb);
+
+	flags = nm_setting_dcb_get_app_fcoe_flags (s_dcb);
+	if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
+		const char *mode = nm_setting_dcb_get_app_fcoe_mode (s_dcb);
+
+		if (!do_helper (NULL, FCOEADM, run_func, user_data, error, "-m %s -c %s", mode, iface))
+			return FALSE;
+	} else {
+		if (!do_helper (NULL, FCOEADM, run_func, user_data, error, "-d %s", iface))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+_fcoe_cleanup (const char *iface,
+               DcbFunc run_func,
+               gpointer user_data,
+               GError **error)
+{
+	return do_helper (NULL, FCOEADM, run_func, user_data, error, "-d %s", iface);
+}
+
+
+static const char *dcbpaths[] = {
+	"/sbin/dcbtool",
+	"/usr/sbin/dcbtool",
+	"/usr/local/sbin/dcbtool",
+	NULL
+};
+static const char *fcoepaths[] = {
+	"/sbin/fcoeadm",
+	"/usr/sbin/fcoeadm",
+	"/usr/local/sbin/fcoeadm",
+	NULL
+};
+
+
+static gboolean
+run_helper (char **argv, guint which, gpointer user_data, GError **error)
+{
+	static const char *helper_path[2] = { NULL, NULL };
+	int exit_status = 0;
+	gboolean success;
+	char *errmsg = NULL, *outmsg = NULL;
+	const char **iter;
+	char *cmdline;
+
+	if (G_UNLIKELY (helper_path[which] == NULL)) {
+		iter = (which == DCBTOOL) ? dcbpaths : fcoepaths;
+		while (*iter) {
+			if (g_file_test (*iter, G_FILE_TEST_EXISTS))
+				helper_path[which] = *iter;
+			iter++;
+		}
+		if (!helper_path[which]) {
+			g_set_error (error, NM_DCB_ERROR, NM_DCB_ERROR_HELPER_NOT_FOUND,
+			             "%s not found",
+			             which == DCBTOOL ? "dcbtool" : "fcoadm");
+			return FALSE;
+		}
+	}
+
+	argv[0] = (char *) helper_path[which];
+	cmdline = g_strjoinv (" ", argv);
+	nm_log_dbg (LOGD_DCB, "%s", cmdline);
+
+	success = g_spawn_sync ("/", argv, NULL, 0 /*G_SPAWN_DEFAULT*/,
+	                        nm_unblock_posix_signals, NULL,
+	                        &outmsg, &errmsg, &exit_status, error);
+	/* Log any stderr output */
+	if (success && WIFEXITED (exit_status) && WEXITSTATUS (exit_status) && (errmsg || outmsg)) {
+		gboolean ignore_error = FALSE;
+
+		/* Ignore fcoeadm "success" errors like when FCoE is already set up */
+		if (errmsg && strstr (errmsg, "Connection already created"))
+			ignore_error = TRUE;
+
+		if (ignore_error == FALSE) {
+			nm_log_warn (LOGD_DCB, "'%s' failed: '%s'",
+			             cmdline, (errmsg && strlen (errmsg)) ? errmsg : outmsg);
+			g_set_error (error, NM_DCB_ERROR, NM_DCB_ERROR_HELPER_FAILED,
+			             "Failed to run '%s'", cmdline);
+			success = FALSE;
+		}
+	}
+	g_free (outmsg);
+	g_free (errmsg);
+
+	g_free (cmdline);
+	return success;
+}
+
+gboolean
+nm_dcb_enable (const char *iface, gboolean enable, GError **error)
+{
+	return _dcb_enable (iface, enable, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
+}
+
+gboolean
+nm_dcb_setup (const char *iface, NMSettingDcb *s_dcb, GError **error)
+{
+	gboolean success;
+
+	success = _dcb_setup (iface, s_dcb, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
+	if (success)
+		success = _fcoe_setup (iface, s_dcb, run_helper, GUINT_TO_POINTER (FCOEADM), error);
+
+	return success;
+}
+
+static void
+carrier_wait (const char *iface, guint secs, gboolean up)
+{
+	int ifindex, count = secs * 10;
+
+	g_return_if_fail (iface != NULL);
+
+	ifindex = nm_platform_link_get_ifindex (iface);
+	if (ifindex > 0) {
+		/* To work around driver quirks and lldpad handling of carrier status,
+		 * we must wait a short period of time to see if the carrier goes
+		 * down, and then wait for the carrier to come back up again.  Otherwise
+		 * subsequent lldpad calls may fail with "Device not found, link down
+		 * or DCB not enabled" errors.
+		 */
+		nm_log_dbg (LOGD_DCB, "(%s): cleanup waiting for carrier %s",
+		            iface, up ? "up" : "down");
+		g_usleep (G_USEC_PER_SEC / 4);
+		while (nm_platform_link_is_connected (ifindex) != up && count-- > 0) {
+			g_usleep (G_USEC_PER_SEC / 10);
+			nm_platform_link_refresh (ifindex);
+		}
+	}
+}
+
+gboolean
+nm_dcb_cleanup (const char *iface, GError **error)
+{
+	/* Ignore FCoE cleanup errors */
+	_fcoe_cleanup (iface, run_helper, GUINT_TO_POINTER (FCOEADM), NULL);
+
+	/* Must pause a bit to wait for carrier-up since disabling FCoE may
+	 * cause the device to take the link down, making lldpad return errors.
+	 */
+	carrier_wait (iface, 2, FALSE);
+	carrier_wait (iface, 4, TRUE);
+
+	return _dcb_cleanup (iface, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
+}
+