diff --git a/include/stp.h b/include/stp.h index 69f2bd4..652883a 100755 --- a/include/stp.h +++ b/include/stp.h @@ -94,6 +94,7 @@ #define g_stp_protect_mask stp_global.protect_mask #define g_stp_protect_do_disable_mask stp_global.protect_do_disable_mask #define g_stp_root_protect_mask stp_global.root_protect_mask +#define g_stp_loop_protect_mask stp_global.loop_protect_mask #define g_stp_protect_disabled_mask stp_global.protect_disabled_mask #define g_stp_enable_mask stp_global.enable_mask #define g_stp_enable_config_mask stp_global.enable_admin_mask @@ -120,6 +121,7 @@ #define STP_IS_PROTECT_DO_DISABLE_CONFIGURED(_port_) IS_MEMBER(stp_global.protect_do_disable_mask, (_port_)) #define STP_IS_PROTECT_DO_DISABLED(_port_) IS_MEMBER(stp_global.protect_disabled_mask, (_port_)) #define STP_IS_ROOT_PROTECT_CONFIGURED(_p_) IS_MEMBER(stp_global.root_protect_mask, (_p_)) +#define STP_IS_LOOP_PROTECT_CONFIGURED(_p_) IS_MEMBER(stp_global.loop_protect_mask, (_p_)) #define STP_IS_PROTOCOL_ENABLED(mode) (stp_global.enable == true && stp_global.proto_mode == mode) // _port_ is a pointer to either STP_PORT_CLASS or RSTP_PORT_CLASS @@ -303,6 +305,8 @@ typedef struct TIMER forward_delay_timer; TIMER hold_timer; TIMER root_protect_timer; + bool loop_guard_active; + bool loop_guard_synced; UINT32 forward_transitions; UINT32 rx_config_bpdu; @@ -334,7 +338,8 @@ typedef struct #define STP_PORT_CLASS_PORT_FAST_BIT 14 #define STP_PORT_CLASS_ROOT_PROTECT_BIT 15 #define STP_PORT_CLASS_BPDU_PROTECT_BIT 16 -#define STP_PORT_CLASS_CLEAR_STATS_BIT 17 +#define STP_PORT_CLASS_LOOP_PROTECT_BIT 17 +#define STP_PORT_CLASS_CLEAR_STATS_BIT 18 UINT32 modified_fields; } __attribute__((aligned(4))) STP_PORT_CLASS; @@ -371,6 +376,7 @@ typedef struct PORT_MASK *protect_do_disable_mask; PORT_MASK *protect_disabled_mask; PORT_MASK *root_protect_mask; + PORT_MASK *loop_protect_mask; uint16_t root_protect_timeout; L2_PROTO_MODE proto_mode; @@ -391,6 +397,7 @@ typedef enum STP_RAS_MES_AGE_TIMER_EXPIRY, STP_RAS_ROOT_PROTECT_TIMER_EXPIRY, STP_RAS_ROOT_PROTECT_VIOLATION, + STP_RAS_LOOP_PROTECT_VIOLATION, STP_RAS_ROOT_ROLE, STP_RAS_DESIGNATED_ROLE, STP_RAS_MP_RX_DELAY_EVENT, diff --git a/include/stp_dbsync.h b/include/stp_dbsync.h index 7f8add4..f67343c 100644 --- a/include/stp_dbsync.h +++ b/include/stp_dbsync.h @@ -71,6 +71,7 @@ typedef struct { uint32_t tx_tcn_bpdu; uint32_t rx_tcn_bpdu; uint32_t root_protect_timer; + uint8_t loop_guard_active; uint8_t clear_stats; uint32_t modified_fields; diff --git a/include/stp_ipc.h b/include/stp_ipc.h index 6585329..eadb4f6 100644 --- a/include/stp_ipc.h +++ b/include/stp_ipc.h @@ -138,6 +138,7 @@ typedef struct STP_PORT_CONFIG_MSG char intf_name[IFNAMSIZ]; uint8_t enabled; uint8_t root_guard; + uint8_t loop_guard; uint8_t bpdu_guard; uint8_t bpdu_guard_do_disable; uint8_t portfast; diff --git a/stp/stp.c b/stp/stp.c index 93f11c0..85a06ab 100755 --- a/stp/stp.c +++ b/stp/stp.c @@ -491,7 +491,7 @@ void make_forwarding(STP_CLASS *stp_class, PORT_ID port_number) { STP_PORT_CLASS *stp_port_class = GET_STP_PORT_CLASS(stp_class, port_number); - if ((stp_port_class->state == BLOCKING) && (!is_timer_active(&stp_port_class->root_protect_timer))) + if ((stp_port_class->state == BLOCKING) && (!is_timer_active(&stp_port_class->root_protect_timer)) && (!stp_port_class->loop_guard_active)) { stp_port_class->state = LISTENING; @@ -726,6 +726,24 @@ void message_age_timer_expiry(STP_CLASS *stp_class, PORT_ID port_number) stp_port_class = GET_STP_PORT_CLASS(stp_class, port_number); stp_port_class->self_loop = false; + /* Loop Guard: if configured on a non-designated port, enter loop-inconsistent + * state instead of allowing the port to become designated/forwarding */ + if (STP_IS_LOOP_PROTECT_CONFIGURED(port_number) && + !designated_port(stp_class, port_number)) + { + stp_port_class->loop_guard_active = true; + stp_port_class->loop_guard_synced = false; + stp_port_class->state = BLOCKING; + stputil_set_port_state(stp_class, stp_port_class); + SET_BIT(stp_port_class->modified_fields, STP_PORT_CLASS_LOOP_PROTECT_BIT); + SET_BIT(stp_port_class->modified_fields, STP_PORT_CLASS_MEMBER_PORT_STATE_BIT); + STP_SYSLOG("STP: Loop Guard interface %s, VLAN %u loop-inconsistent (BPDUs lost)", + stp_intf_get_port_name(port_number), stp_class->vlan_id); + STP_LOG_INFO("STP_RAS_LOOP_PROTECT_VIOLATION I:%lu P:%lu V:%u", + GET_STP_INDEX(stp_class), port_number, stp_class->vlan_id); + return; + } + root = root_bridge(stp_class); become_designated_port(stp_class, port_number); configuration_update(stp_class); @@ -876,6 +894,9 @@ void send_tcn_bpdu(STP_CLASS* stp_class, PORT_ID port_number) if (stptimer_is_active(&stp_port_class->root_protect_timer)) return; - + + if (stp_port_class->loop_guard_active) + return; + stputil_send_pvst_bpdu(stp_class, port_number, TCN_BPDU_TYPE); } diff --git a/stp/stp_data.c b/stp/stp_data.c index 4600950..d6669e1 100755 --- a/stp/stp_data.c +++ b/stp/stp_data.c @@ -35,6 +35,7 @@ int8_t stpdata_init_global_port_mask() ret |= bmp_alloc(&g_stp_protect_mask, g_max_stp_port); ret |= bmp_alloc(&g_stp_protect_do_disable_mask, g_max_stp_port); ret |= bmp_alloc(&g_stp_root_protect_mask, g_max_stp_port); + ret |= bmp_alloc(&g_stp_loop_protect_mask, g_max_stp_port); ret |= bmp_alloc(&g_stp_protect_disabled_mask, g_max_stp_port); /* By default fast span is enabled on all ports */ diff --git a/stp/stp_debug.c b/stp/stp_debug.c index 84f6e89..c97d74d 100755 --- a/stp/stp_debug.c +++ b/stp/stp_debug.c @@ -118,6 +118,7 @@ void stpdm_global() UINT8 enable_string[500], enable_admin_string[500]; UINT8 fastspan_string[500], fastspan_admin_string[500], fastuplink_admin_string[500]; UINT8 protect_string[500], protect_do_disable_string[500], protect_disabled_string[500], root_protect_string[500]; + UINT8 loop_protect_string[500]; mask_to_string(g_stp_enable_mask, enable_string, 500); mask_to_string(g_stp_enable_config_mask, enable_admin_string, 500); @@ -125,6 +126,7 @@ void stpdm_global() mask_to_string(stp_global.protect_do_disable_mask, protect_do_disable_string, sizeof(protect_do_disable_string)); mask_to_string(stp_global.protect_disabled_mask, protect_disabled_string, sizeof(protect_disabled_string)); mask_to_string(stp_global.root_protect_mask, root_protect_string, sizeof(root_protect_string)); + mask_to_string(stp_global.loop_protect_mask, loop_protect_string, sizeof(loop_protect_string)); mask_to_string(g_fastspan_mask, fastspan_string, 500); mask_to_string(g_fastspan_config_mask, fastspan_admin_string, 500); mask_to_string(g_fastuplink_mask, fastuplink_admin_string, 500); @@ -151,6 +153,7 @@ void stpdm_global() "protect_do_disable_mask= %s\n\t" "protect_disabled_mask = %s\n\t" "root_protect_mask = %s\n\t" + "loop_protect_mask = %s\n\t" "root_protect_timeout = %u\n\t" "fastspan_mask = %s\n\t" "fastspan_admin_mask = %s\n\t" @@ -176,6 +179,7 @@ void stpdm_global() protect_do_disable_string, protect_disabled_string, root_protect_string, + loop_protect_string, stp_global.root_protect_timeout, fastspan_string, fastspan_admin_string, @@ -289,6 +293,7 @@ void stpdm_port_class(STP_CLASS *stp_class, PORT_ID port_number) "forward_delay_timer = %s %d\n" "hold timer = %s %d\n" "root_protect_timer = %s %u\n" + "loop_guard_active = %s\n" "forward_transitions = %d\n" "rx_config_bpdu = %d\n" "tx_config_bpdu = %d\n" @@ -313,6 +318,7 @@ void stpdm_port_class(STP_CLASS *stp_class, PORT_ID port_number) stp_port->hold_timer.value, STP_TIMER_STRING(&stp_port->root_protect_timer), stp_port->root_protect_timer.value, + stp_port->loop_guard_active ? "YES" : "NO", stp_port->forward_transitions, stp_port->rx_config_bpdu, stp_port->tx_config_bpdu, diff --git a/stp/stp_mgr.c b/stp/stp_mgr.c index 1cfcc6b..046281a 100755 --- a/stp/stp_mgr.c +++ b/stp/stp_mgr.c @@ -295,6 +295,11 @@ void stpmgr_disable_port(STP_CLASS *stp_class, PORT_ID port_number) stptimer_stop(&stp_port_class->root_protect_timer); } + if (stp_port_class->loop_guard_active) + { + stp_port_class->loop_guard_active = false; + } + clear_mask_bit(stp_class->enable_mask, port_number); configuration_update(stp_class); port_state_selection(stp_class); @@ -1249,6 +1254,20 @@ bool stpmgr_config_root_protect(PORT_ID port_id, bool enable) return true; } +/*****************************************************************************/ +/* stpmgr_config_loop_protect: enables/disables loop-guard feature on the */ +/* input port. */ +/*****************************************************************************/ +static bool stpmgr_config_loop_protect(PORT_ID port_id, bool enable) +{ + if (enable) + set_mask_bit(stp_global.loop_protect_mask, port_id); + else + clear_mask_bit(stp_global.loop_protect_mask, port_id); + + return true; +} + /*****************************************************************************/ /* stpmgr_config_root_protect_timeout: configures the timeout in seconds for */ /* which the violated stp port is kept in blocking state. */ @@ -1717,9 +1736,9 @@ static void stpmgr_process_intf_config_msg(void *msg) return; } - STP_LOG_INFO("op:%d, intf:%s, enable:%d, root_grd:%d, bpdu_grd:%d , do_dis:%d, cost:%d, pri:%d, portfast:%d, uplink_fast:%d, count:%d", - pmsg->opcode, pmsg->intf_name, pmsg->enabled, pmsg->root_guard, pmsg->bpdu_guard, - pmsg->bpdu_guard_do_disable, pmsg->path_cost, pmsg->priority, + STP_LOG_INFO("op:%d, intf:%s, enable:%d, root_grd:%d, loop_grd:%d, bpdu_grd:%d , do_dis:%d, cost:%d, pri:%d, portfast:%d, uplink_fast:%d, count:%d", + pmsg->opcode, pmsg->intf_name, pmsg->enabled, pmsg->root_guard, pmsg->loop_guard, pmsg->bpdu_guard, + pmsg->bpdu_guard_do_disable, pmsg->path_cost, pmsg->priority, pmsg->portfast, pmsg->uplink_fast, pmsg->count); port_id = stp_intf_get_port_id_by_name(pmsg->intf_name); @@ -1771,8 +1790,9 @@ static void stpmgr_process_intf_config_msg(void *msg) if (pmsg->enabled) { stpmgr_config_root_protect(port_id, pmsg->root_guard); + stpmgr_config_loop_protect(port_id, pmsg->loop_guard); stpmgr_config_protect(port_id, pmsg->bpdu_guard, pmsg->bpdu_guard_do_disable); - + stpmgr_config_fastspan(port_id, pmsg->portfast); stpmgr_config_fastuplink(port_id, pmsg->uplink_fast); } @@ -1788,6 +1808,7 @@ static void stpmgr_process_intf_config_msg(void *msg) if (pmsg->opcode == STP_DEL_COMMAND || !pmsg->enabled) { stpmgr_config_root_protect(port_id, false); + stpmgr_config_loop_protect(port_id, false); stpmgr_config_protect(port_id, false, false); stpmgr_config_fastspan(port_id, true); diff --git a/stp/stp_util.c b/stp/stp_util.c index 0da09bf..061339c 100755 --- a/stp/stp_util.c +++ b/stp/stp_util.c @@ -498,6 +498,27 @@ static bool stputil_root_protect_violation(STP_CLASS *stp_class, PORT_ID port_nu return true; } + +/*****************************************************************************/ +/* stputil_loop_guard_bpdu_recovery: called when a BPDU is received on a */ +/* loop-inconsistent port. clears the loop guard active flag, logs recovery, */ +/* and marks the port state as modified. */ +/*****************************************************************************/ +static void stputil_loop_guard_bpdu_recovery(STP_CLASS *stp_class, PORT_ID port_number) +{ + STP_PORT_CLASS *stp_port; + + stp_port = GET_STP_PORT_CLASS(stp_class, port_number); + + STP_SYSLOG("STP: Loop Guard interface %s, VLAN %u consistent (BPDU received)", + stp_intf_get_port_name(port_number), stp_class->vlan_id); + + stp_port->loop_guard_active = false; + stp_port->loop_guard_synced = false; + SET_BIT(stp_port->modified_fields, STP_PORT_CLASS_LOOP_PROTECT_BIT); + SET_BIT(stp_port->modified_fields, STP_PORT_CLASS_MEMBER_PORT_STATE_BIT); +} + /*****************************************************************************/ /* stputil_root_protect_validate: routine validates the bpdu to see that it */ /* is conforming to the root protect configuration. */ @@ -779,6 +800,16 @@ void stputil_process_bpdu(STP_INDEX stp_index, PORT_ID port_number, void *buffer return; } + // Loop Guard: if port is in loop-inconsistent state and receives a BPDU, recover + if (STP_IS_LOOP_PROTECT_CONFIGURED(port_number)) + { + STP_PORT_CLASS *stp_port_lg = GET_STP_PORT_CLASS(stp_class, port_number); + if (stp_port_lg->loop_guard_active) + { + stputil_loop_guard_bpdu_recovery(stp_class, port_number); + } + } + last_bpdu_rx_time = stp_class->last_bpdu_rx_time; current_time = sys_get_seconds(); stp_class->last_bpdu_rx_time = current_time; @@ -898,7 +929,12 @@ void stptimer_sync_port_class(STP_CLASS *stp_class, STP_PORT_CLASS * stp_port) if(timer_value != 0 && stp_port->state == BLOCKING) strcpy(stp_vlan_intf.port_state, "ROOT-INC"); else - strcpy(stp_vlan_intf.port_state, l2_port_state_to_string(stp_port->state, stp_port->port_id.number)); + { + if(stp_port->loop_guard_active && stp_port->state == BLOCKING) + strcpy(stp_vlan_intf.port_state, "LOOP-INC"); + else + strcpy(stp_vlan_intf.port_state, l2_port_state_to_string(stp_port->state, stp_port->port_id.number)); + } if (stp_port->state == DISABLED) { @@ -965,6 +1001,16 @@ void stptimer_sync_port_class(STP_CLASS *stp_class, STP_PORT_CLASS * stp_port) stp_vlan_intf.root_protect_timer = -1; } + if(IS_BIT_SET(stp_port->modified_fields, STP_PORT_CLASS_LOOP_PROTECT_BIT)) + { + stp_vlan_intf.loop_guard_active = stp_port->loop_guard_active ? 1 : 0; + stp_port->loop_guard_synced = true; + } + else + { + stp_vlan_intf.loop_guard_active = -1; + } + if(IS_BIT_SET(stp_port->modified_fields, STP_PORT_CLASS_CLEAR_STATS_BIT)) { stp_vlan_intf.clear_stats = 1; @@ -1120,7 +1166,10 @@ void stputil_sync_port_counters(STP_CLASS *stp_class, STP_PORT_CLASS * stp_port) if (is_timer_active(&stp_port->root_protect_timer)) SET_BIT(stp_port->modified_fields, STP_PORT_CLASS_ROOT_PROTECT_BIT); - + + if (stp_port->loop_guard_active && !stp_port->loop_guard_synced) + SET_BIT(stp_port->modified_fields, STP_PORT_CLASS_LOOP_PROTECT_BIT); + stptimer_sync_port_class(stp_class, stp_port); } @@ -1302,23 +1351,36 @@ void stptimer_update(STP_CLASS *stp_class) (stp_port_class->root_protect_timer.active && !STP_IS_ROOT_PROTECT_CONFIGURED(port_number))) { stp_port_class->root_protect_timer.active = false; - stputil_root_protect_timer_expired(stp_class, port_number); + stputil_root_protect_timer_expired(stp_class, port_number); if (debugGlobal.stp.enabled) { if (STP_DEBUG_VP(stp_class->vlan_id, port_number)) { - STP_LOG_INFO("I:%lu P:%u V:%u Ev:%d",GET_STP_INDEX(stp_class),port_number, + STP_LOG_INFO("I:%lu P:%u V:%u Ev:%d",GET_STP_INDEX(stp_class),port_number, stp_class->vlan_id,STP_RAS_ROOT_PROTECT_TIMER_EXPIRY); } } else { - STP_LOG_INFO("I:%lu P:%u V:%u Ev:%d",GET_STP_INDEX(stp_class),port_number, + STP_LOG_INFO("I:%lu P:%u V:%u Ev:%d",GET_STP_INDEX(stp_class),port_number, stp_class->vlan_id,STP_RAS_ROOT_PROTECT_TIMER_EXPIRY); } } + /* Loop guard config can be removed while the port is already loop-inconsistent. + * In that case, clear loop-guard state and run the same recovery path used on + * message-age expiry so the port can be re-evaluated immediately. */ + if (stp_port_class->loop_guard_active && !STP_IS_LOOP_PROTECT_CONFIGURED(port_number)) + { + stp_port_class->loop_guard_active = false; + stp_port_class->loop_guard_synced = false; + SET_BIT(stp_port_class->modified_fields, STP_PORT_CLASS_LOOP_PROTECT_BIT); + SET_BIT(stp_port_class->modified_fields, STP_PORT_CLASS_MEMBER_PORT_STATE_BIT); + + message_age_timer_expiry(stp_class, port_number); + } + port_number = port_mask_get_next_port(stp_class->enable_mask, port_number); } @@ -1441,4 +1503,3 @@ void sys_assert(int status) { assert(status); } - diff --git a/stpsync/stp_sync.cpp b/stpsync/stp_sync.cpp index 45ccfb0..d02828a 100644 --- a/stpsync/stp_sync.cpp +++ b/stpsync/stp_sync.cpp @@ -387,6 +387,12 @@ void StpSync::updateStpVlanInterfaceInfo(STP_VLAN_PORT_TABLE * stp_vlan_intf) fvVector.push_back(rpt); } + if(stp_vlan_intf->loop_guard_active != 0xFF) + { + FieldValueTuple lga("loop_guard_active", to_string(stp_vlan_intf->loop_guard_active)); + fvVector.push_back(lga); + } + vlan = VLAN_PREFIX + to_string(stp_vlan_intf->vlan_id); key = vlan + ":" + ifName;