// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 Red Hat, Inc.
*/
#include "nm-default.h"
#include <sys/wait.h>
#include "nm-dcb.h"
#include "platform/nm-platform.h"
#include "NetworkManagerUtils.h"
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,
...)
{
gs_free const char **split = NULL;
gs_free char *cmdline = NULL;
gs_free const char **argv = NULL;
gsize i;
gsize 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 = nm_utils_strsplit_set_with_empty (cmdline, " ");
if (!split) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
"failure parsing %s command line", helper_names[which]);
return FALSE;
}
/* Allocate space for path, custom arg, interface name, arguments, and NULL */
i = 0;
argv = g_new (const char *, NM_PTRARRAY_LEN (split) + 4);
argv[i++] = NULL; /* Placeholder for dcbtool path */
if (which == DCBTOOL) {
argv[i++] = "sc";
argv[i++] = (char *) iface;
}
for (u = 0; split[u]; u++)
argv[i++] = split[u];
argv[i++] = NULL;
if (!run_func ((char **) argv, which, user_data, error)) {
g_assert (!error || *error);
return FALSE;
}
return TRUE;
}
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 { \
int 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 gboolean
run_helper (char **argv, guint which, gpointer user_data, GError **error)
{
const char *helper_path;
int exit_status = 0;
gboolean success;
char *errmsg = NULL, *outmsg = NULL;
char *cmdline;
helper_path = nm_utils_find_helper ((which == DCBTOOL) ? "dcbtool" : "fcoeadm", NULL, error);
if (!helper_path)
return FALSE;
argv[0] = (char *) helper_path;
cmdline = g_strjoinv (" ", argv);
nm_log_dbg (LOGD_DCB, "%s", cmdline);
success = g_spawn_sync ("/", argv, NULL, 0 /*G_SPAWN_DEFAULT*/,
NULL, 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_MANAGER_ERROR, NM_MANAGER_ERROR_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 (NM_PLATFORM_GET, 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 (NM_PLATFORM_GET, ifindex) != up && count-- > 0) {
g_usleep (G_USEC_PER_SEC / 10);
nm_platform_link_refresh (NM_PLATFORM_GET, 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);
}