Commit 42499b5b authored by Dan Williams's avatar Dan Williams
Browse files

merge: DCB-related workarounds for lldpad and driver bugs (rh #799241) (rh #1081991)

lldpad 0.9.46 and earlier appear to be very sensitive to carrier, and if the
interface has no carrier will refuse to do anything with it, reporting that
"Device not found, link down or DCB not enabled".  So while setting up and
tearing down DCB configuration, we must wait for the carrier at various
points before proceeding, to ensure that the operations are successful.
parents 0b664ad4 55704170
......@@ -86,6 +86,20 @@ typedef struct Supplicant {
guint con_timeout_id;
} Supplicant;
typedef enum {
DCB_WAIT_UNKNOWN = 0,
/* Ensure carrier is up before enabling DCB */
DCB_WAIT_CARRIER_PREENABLE_UP,
/* Wait for carrier down when device starts enabling */
DCB_WAIT_CARRIER_PRECONFIG_DOWN,
/* Wait for carrier up when device has finished enabling */
DCB_WAIT_CARRIER_PRECONFIG_UP,
/* Wait carrier down when device starts configuring */
DCB_WAIT_CARRIER_POSTCONFIG_DOWN,
/* Wait carrier up when device has finished configuring */
DCB_WAIT_CARRIER_POSTCONFIG_UP,
} DcbWait;
typedef struct {
guint8 perm_hw_addr[ETH_ALEN]; /* Permanent MAC address */
guint8 initial_hw_addr[ETH_ALEN]; /* Initial MAC address (as seen when NM starts) */
......@@ -106,6 +120,11 @@ typedef struct {
NMIP4Config *pending_ip4_config;
gint32 last_pppoe_time;
guint pppoe_wait_id;
/* DCB */
DcbWait dcb_wait;
guint dcb_timeout_id;
guint dcb_carrier_id;
} NMDeviceEthernetPrivate;
enum {
......@@ -1094,33 +1113,217 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *reas
return ret;
}
/****************************************************************/
static void
dcb_timeout_cleanup (NMDevice *device)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
if (priv->dcb_timeout_id) {
g_source_remove (priv->dcb_timeout_id);
priv->dcb_timeout_id = 0;
}
}
static void
dcb_carrier_cleanup (NMDevice *device)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
if (priv->dcb_carrier_id) {
g_signal_handler_disconnect (device, priv->dcb_carrier_id);
priv->dcb_carrier_id = 0;
}
}
static void dcb_state (NMDevice *device, gboolean timeout);
static gboolean
dcb_carrier_timeout (gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
g_return_val_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG, G_SOURCE_REMOVE);
priv->dcb_timeout_id = 0;
if (priv->dcb_wait != DCB_WAIT_CARRIER_POSTCONFIG_DOWN) {
nm_log_warn (LOGD_DCB,
"(%s): DCB: timed out waiting for carrier (step %d)",
nm_device_get_iface (device),
priv->dcb_wait);
}
dcb_state (device, TRUE);
return G_SOURCE_REMOVE;
}
static gboolean
dcb_configure (NMDevice *device)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
NMSettingDcb *s_dcb;
const char *iface = nm_device_get_iface (device);
GError *error = NULL;
dcb_timeout_cleanup (device);
s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB);
g_assert (s_dcb);
if (!nm_dcb_setup (iface, s_dcb, &error)) {
nm_log_warn (LOGD_DCB,
"Activation (%s/wired) failed to enable DCB/FCoE: %s",
iface, error->message);
g_clear_error (&error);
return FALSE;
}
/* Pause again just in case the device takes the carrier down when
* setting specific DCB attributes.
*/
nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (postconfig down)", iface);
priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_DOWN;
priv->dcb_timeout_id = g_timeout_add_seconds (3, dcb_carrier_timeout, device);
return TRUE;
}
static gboolean
dcb_enable (NMDevice *device)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
const char *iface = nm_device_get_iface (device);
GError *error = NULL;
dcb_timeout_cleanup (device);
if (!nm_dcb_enable (iface, TRUE, &error)) {
nm_log_warn (LOGD_DCB,
"Activation (%s/wired) failed to enable DCB/FCoE: %s",
iface, error->message);
g_clear_error (&error);
return FALSE;
}
/* Pause for 3 seconds after enabling DCB to let the card reconfigure
* itself. Drivers will often re-initialize internal settings which
* takes the carrier down for 2 or more seconds. During this time,
* lldpad will refuse to do anything else with the card since the carrier
* is down. But NM might get the carrier-down signal long after calling
* "dcbtool dcb on", so we have to first wait for the carrier to go down.
*/
nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preconfig down)", iface);
priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_DOWN;
priv->dcb_timeout_id = g_timeout_add_seconds (3, dcb_carrier_timeout, device);
return TRUE;
}
static void
dcb_state (NMDevice *device, gboolean timeout)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
const char *iface = nm_device_get_iface (device);
gboolean carrier;
g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
carrier = nm_platform_link_is_connected (nm_device_get_ifindex (device));
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() wait %d carrier %d timeout %d", iface, priv->dcb_wait, carrier, timeout);
switch (priv->dcb_wait) {
case DCB_WAIT_CARRIER_PREENABLE_UP:
if (timeout || carrier) {
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() enabling DCB", iface);
dcb_timeout_cleanup (device);
if (!dcb_enable (device)) {
dcb_carrier_cleanup (device);
nm_device_state_changed (device,
NM_ACT_STAGE_RETURN_FAILURE,
NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
}
}
break;
case DCB_WAIT_CARRIER_PRECONFIG_DOWN:
dcb_timeout_cleanup (device);
priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_UP;
if (!carrier) {
/* Wait for the carrier to come back up */
nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preconfig up)", iface);
priv->dcb_timeout_id = g_timeout_add_seconds (5, dcb_carrier_timeout, device);
break;
}
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() preconfig down falling through", iface);
/* carrier never went down? fall through */
case DCB_WAIT_CARRIER_PRECONFIG_UP:
if (timeout || carrier) {
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() preconfig up configuring DCB", iface);
dcb_timeout_cleanup (device);
if (!dcb_configure (device)) {
dcb_carrier_cleanup (device);
nm_device_state_changed (device,
NM_ACT_STAGE_RETURN_FAILURE,
NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
}
}
break;
case DCB_WAIT_CARRIER_POSTCONFIG_DOWN:
dcb_timeout_cleanup (device);
priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_UP;
if (!carrier) {
/* Wait for the carrier to come back up */
nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (postconfig up)", iface);
priv->dcb_timeout_id = g_timeout_add_seconds (5, dcb_carrier_timeout, device);
break;
}
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() postconfig down falling through", iface);
/* carrier never went down? fall through */
case DCB_WAIT_CARRIER_POSTCONFIG_UP:
if (timeout || carrier) {
nm_log_dbg (LOGD_DCB, "(%s): dcb_state() postconfig up starting IP", iface);
dcb_timeout_cleanup (device);
dcb_carrier_cleanup (device);
priv->dcb_wait = DCB_WAIT_UNKNOWN;
nm_device_activate_schedule_stage3_ip_config_start (device);
}
break;
default:
g_assert_not_reached ();
}
}
static void
dcb_carrier_changed (NMDevice *device, GParamSpec *pspec, gpointer unused)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
if (priv->dcb_timeout_id) {
nm_log_dbg (LOGD_DCB, "(%s): carrier_changed() calling dcb_state()", nm_device_get_iface (device));
dcb_state (device, FALSE);
}
}
/****************************************************************/
static NMActStageReturn
act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
{
NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
NMSettingConnection *s_con;
const char *connection_type;
NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
NMSettingDcb *s_dcb;
GError *error = NULL;
g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
/* DCB and FCoE setup */
s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB);
if (s_dcb) {
if (!nm_dcb_setup (nm_device_get_iface (device), s_dcb, &error)) {
nm_log_warn (LOGD_DEVICE | LOGD_HW,
"Activation (%s/wired) failed to enable DCB/FCoE: %s",
nm_device_get_iface (device), error->message);
g_clear_error (&error);
*reason = NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED;
return NM_ACT_STAGE_RETURN_FAILURE;
}
}
s_con = NM_SETTING_CONNECTION (device_get_setting (device, NM_TYPE_SETTING_CONNECTION));
g_assert (s_con);
dcb_timeout_cleanup (device);
dcb_carrier_cleanup (device);
/* 802.1x has to run before any IP configuration since the 802.1x auth
* process opens the port up for normal traffic.
*/
......@@ -1129,8 +1332,36 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
NMSetting8021x *security;
security = (NMSetting8021x *) device_get_setting (device, NM_TYPE_SETTING_802_1X);
if (security)
ret = nm_8021x_stage2_config (NM_DEVICE_ETHERNET (device), reason);
if (security) {
/* FIXME: for now 802.1x is mutually exclusive with DCB */
return nm_8021x_stage2_config (NM_DEVICE_ETHERNET (device), reason);
}
}
/* DCB and FCoE setup */
s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB);
if (s_dcb) {
/* lldpad really really wants the carrier to be up */
if (nm_platform_link_is_connected (nm_device_get_ifindex (device))) {
if (!dcb_enable (device)) {
*reason = NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED;
return NM_ACT_STAGE_RETURN_FAILURE;
}
} else {
nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preenable up)",
nm_device_get_iface (device));
priv->dcb_wait = DCB_WAIT_CARRIER_PREENABLE_UP;
priv->dcb_timeout_id = g_timeout_add_seconds (4, dcb_carrier_timeout, device);
}
/* Watch carrier independently of NMDeviceClass::carrier_changed so
* we get instant notifications of disconnection that aren't deferred.
*/
priv->dcb_carrier_id = g_signal_connect (device,
"notify::" NM_DEVICE_CARRIER,
G_CALLBACK (dcb_carrier_changed),
NULL);
ret = NM_ACT_STAGE_RETURN_POSTPONE;
}
return ret;
......@@ -1206,6 +1437,10 @@ deactivate (NMDevice *device)
supplicant_interface_release (self);
priv->dcb_wait = DCB_WAIT_UNKNOWN;
dcb_timeout_cleanup (device);
dcb_carrier_cleanup (device);
/* Tear down DCB/FCoE if it was enabled */
s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB);
if (s_dcb) {
......@@ -1409,6 +1644,9 @@ dispose (GObject *object)
priv->pppoe_wait_id = 0;
}
dcb_timeout_cleanup (NM_DEVICE (self));
dcb_carrier_cleanup (NM_DEVICE (self));
G_OBJECT_CLASS (nm_device_ethernet_parent_class)->dispose (object);
}
......
......@@ -92,6 +92,19 @@ out:
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", \
......@@ -124,9 +137,6 @@ _dcb_setup (const char *iface,
g_assert (s_dcb);
if (!do_helper (iface, DCBTOOL, run_func, user_data, error, "dcb on"))
return FALSE;
/* FCoE */
flags = nm_setting_dcb_get_app_fcoe_flags (s_dcb);
SET_APP (flags, s_dcb, fcoe);
......@@ -229,7 +239,6 @@ _dcb_cleanup (const char *iface,
GError **error)
{
const char *cmds[] = {
"dcb off",
"app:fcoe e:0",
"app:iscsi e:0",
"app:fip e:0",
......@@ -247,6 +256,9 @@ _dcb_cleanup (const char *iface,
iter++;
}
if (!_dcb_enable (iface, FALSE, run_func, user_data, success ? error : NULL))
success = FALSE;
return success;
}
......@@ -354,6 +366,12 @@ run_helper (char **argv, guint which, gpointer user_data, GError **error)
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)
{
......@@ -366,17 +384,43 @@ nm_dcb_setup (const char *iface, NMSettingDcb *s_dcb, GError **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)
{
gboolean success;
/* Ignore FCoE cleanup errors */
_fcoe_cleanup (iface, run_helper, GUINT_TO_POINTER (FCOEADM), NULL);
success = _dcb_cleanup (iface, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
if (success) {
/* Only report FCoE errors if DCB cleanup was successful */
success = _fcoe_cleanup (iface, run_helper, GUINT_TO_POINTER (FCOEADM), success ? error : 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 success;
return _dcb_cleanup (iface, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
}
......@@ -48,6 +48,7 @@ GQuark nm_dcb_error_quark (void);
GType nm_dcb_error_get_type (void);
gboolean nm_dcb_enable (const char *iface, gboolean enable, GError **error);
gboolean nm_dcb_setup (const char *iface, NMSettingDcb *s_dcb, GError **error);
gboolean nm_dcb_cleanup (const char *iface, GError **error);
......@@ -68,6 +69,12 @@ gboolean do_helper (const char *iface,
const char *fmt,
...) G_GNUC_PRINTF(6, 7);
gboolean _dcb_enable (const char *iface,
gboolean enable,
DcbFunc run_func,
gpointer user_data,
GError **error);
gboolean _dcb_setup (const char *iface,
NMSettingDcb *s_dcb,
DcbFunc run_func,
......
......@@ -54,8 +54,7 @@ static void
test_dcb_fcoe (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:1 a:1 w:1",
{ "dcbtool sc eth0 app:fcoe e:1 a:1 w:1",
"dcbtool sc eth0 app:fcoe appcfg:40",
"dcbtool sc eth0 app:iscsi e:0 a:0 w:0",
"dcbtool sc eth0 app:fip e:0 a:0 w:0",
......@@ -85,8 +84,7 @@ static void
test_dcb_iscsi (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
{ "dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
"dcbtool sc eth0 app:iscsi e:1 a:0 w:1",
"dcbtool sc eth0 app:iscsi appcfg:08",
"dcbtool sc eth0 app:fip e:0 a:0 w:0",
......@@ -116,8 +114,7 @@ static void
test_dcb_fip (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
{ "dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
"dcbtool sc eth0 app:iscsi e:0 a:0 w:0",
"dcbtool sc eth0 app:fip e:1 a:1 w:0",
"dcbtool sc eth0 app:fip appcfg:01",
......@@ -147,8 +144,7 @@ static void
test_dcb_fip_default_prio (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
{ "dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
"dcbtool sc eth0 app:iscsi e:0 a:0 w:0",
"dcbtool sc eth0 app:fip e:1 a:1 w:0",
"dcbtool sc eth0 pfc e:0 a:0 w:0",
......@@ -177,8 +173,7 @@ static void
test_dcb_pfc (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
{ "dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
"dcbtool sc eth0 app:iscsi e:0 a:0 w:0",
"dcbtool sc eth0 app:fip e:0 a:0 w:0",
"dcbtool sc eth0 pfc e:1 a:1 w:1",
......@@ -216,8 +211,7 @@ static void
test_dcb_priority_groups (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb on",
"dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
{ "dcbtool sc eth0 app:fcoe e:0 a:0 w:0",
"dcbtool sc eth0 app:iscsi e:0 a:0 w:0",
"dcbtool sc eth0 app:fip e:0 a:0 w:0",
"dcbtool sc eth0 pfc e:0 a:0 w:0",
......@@ -268,20 +262,27 @@ static void
test_dcb_cleanup (void)
{
static DcbExpected expected = { 0,
{ "dcbtool sc eth0 dcb off",
{ "fcoeadm -d eth0",
"dcbtool sc eth0 app:fcoe e:0",
"dcbtool sc eth0 app:iscsi e:0",
"dcbtool sc eth0 app:fip e:0",
"dcbtool sc eth0 pfc e:0",
"dcbtool sc eth0 pg e:0",
"dcbtool sc eth0 dcb off",
NULL },
};
GError *error = NULL;
gboolean success;
success = _fcoe_cleanup ("eth0", test_dcb_func, &expected, &error);
g_assert_no_error (error);
g_assert (success);
success = _dcb_cleanup ("eth0", test_dcb_func, &expected, &error);
g_assert_no_error (error);
g_assert (success);
g_assert_cmpstr (expected.cmds[expected.num], ==, NULL);
}
static void
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment