diff --git a/doc/stp.md b/doc/stp.md index dd5ae002..c3cc2408 100644 --- a/doc/stp.md +++ b/doc/stp.md @@ -154,9 +154,10 @@ BPDUs toward the root, so the change reaches the rest of the network. With `stp version stp` the root port sends TCN instead, until a Config BPDU with TCA comes back. -A TC flag received on a root or designated port is passed on the same way to -the other ports, not back to the port it came in on. A legacy TCN is -acknowledged with TCA and then treated like a received TC. A port that turns +A TC flag received on a forwarding root or designated port is passed on the +same way to the other ports, not back to the port it came in on; a port that +is still discarding or learning drops it. A legacy TCN is acknowledged with +TCA and then treated like a received TC. A port that turns alternate, loses its link, or is blocked for a loop flushes its own addresses without raising a topology change, and an edge port never raises one. diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 8316c34a..2976206e 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -1018,6 +1018,8 @@ void stp_in(void) __banked return; if (STP_I->bpdu_type == BPDU_TYPE_TCN) { + if (stp_state_get(port) != 0b11) + return; stp_tx_flags_extra = BPDU_FLAG_TCACK; stp_cnf_send(port); uip_len = 0; @@ -1063,7 +1065,7 @@ void stp_in(void) __banked if ((STP_I->flags & BPDU_FLAG_TCACK) && port == stp_root_port) stp_tcwhile[port] = 0; - if ((STP_I->flags & BPDU_FLAG_TC) && !((stp_alt >> port) & 1) && stp_tc_prop(port)) + if ((STP_I->flags & BPDU_FLAG_TC) && stp_state_get(port) == 0b11 && stp_tc_prop(port)) stp_tc_count++; if ((stp_pflags[port] & STP_PF_ROOTGUARD) diff --git a/test/stp/stp_sim.c b/test/stp/stp_sim.c index db95ec2f..34149c2b 100644 --- a/test/stp/stp_sim.c +++ b/test/stp/stp_sim.c @@ -1988,7 +1988,7 @@ static void scen_last_tc(void) links_set(1 << 8); tcn = root_on(8); tcn.legacy = 2; - secs(3); + secs(4); before = stp_tc_count; bpdu_in(&tcn); check(stp_tc_count != before, "a TCN moves the topology change counter"); @@ -2062,6 +2062,57 @@ static void scen_lag(void) check(port_state(1) == 1 && port_state(2) == 1, "the ports start over discarding"); } +static void scen_tc_needs_forwarding(void) +{ + struct sim_bpdu r; + struct sim_bpdu worse = { + .port = 2, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .flags = 0x3d + }; + struct sim_bpdu tcn; + int tc; + + printf("53. a topology change heard on a port that does not forward yet is not passed on\n"); + reset_all(); + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + links_set((1 << 0) | (1 << 2)); + secs(1); + r = root_on(0); + for (int i = 0; i < 3; i++) { + bpdu_in(&r); + secs(2); + } + check_state(0, 3, "root port 1 forwards"); + check_state(2, 1, "port 3 is still discarding"); + memcpy(worse.root_mac, ROOT_MAC, 6); + memcpy(worse.br_mac, PEER_MAC, 6); + memset(flush_count, 0, sizeof(flush_count)); + tc = stp_tc_count; + bpdu_in(&worse); + tick(2); + check(stp_tc_count == tc, "a TC flag on the discarding port raises no topology change"); + check(flush_count[0] == 0, "and the root port keeps its addresses"); + tcn = worse; + tcn.legacy = 2; + memset(last_tx, 0, sizeof(last_tx)); + bpdu_in(&tcn); + tick(2); + check(stp_tc_count == tc, "a TCN on the discarding port raises none either"); + check((last_tx[2].flags & 0x80) == 0, "and is not acknowledged"); + for (int i = 0; i < 16; i++) { + bpdu_in(&r); + secs(2); + } + check_state(2, 3, "port 3 forwards after both forward delays"); + for (int i = 0; i < 2; i++) { + bpdu_in(&r); + secs(2); + } + tc = stp_tc_count; + bpdu_in(&worse); + tick(2); + check(stp_tc_count == tc + 1 && flush_count[0] > 0, "now the same TC is passed on"); +} + int main(int argc, char **argv) { verbose = argc > 1 && argv[1][0] == '-' ? (argv[1][1] == 'd' ? 2 : 1) : 0; @@ -2117,6 +2168,7 @@ int main(int argc, char **argv) scen_bpdu_handling(); scen_last_tc(); scen_lag(); + scen_tc_needs_forwarding(); if (failures) { printf("\n%d check(s) failed\n", failures); return 1;