// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2013 Red Hat, Inc. */ #include "nm-default.h" #include #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); }