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