#include #include #include #include #include #include "rtl837x_common.h" #include "machine.h" #include "uip.h" #include "rtl837x_regs.h" #include "rtl837x_stp.h" #define NPORTS 9 /* indices 0..8, front-panel 1..9 */ uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; uint16_t uip_len; struct uip_eth_addr uip_ethaddr = { .addr = { 0x1c, 0x2a, 0xa3, 0x1a, 0x72, 0x4e } }; const struct machine machine = { .log_to_phys_port = { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; struct machine_runtime machine_detected = { .isRTL8373 = 1 }; uint8_t sfr_data[4]; bool stp_enabled; uint16_t management_vlan; uint8_t cmd_buffer[CMD_BUF_SIZE]; uint8_t cmd_words_len; uint8_t cmd_words_b[15]; char save_cmd; uint8_t err_status; uint8_t atoi_results_u8; static int verbose; void print_string(const char *s) { if (verbose) fputs(s, stdout); } void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } void print_short(uint16_t v) { if (verbose) printf("%04x", v); } void print_long(uint32_t v) { if (verbose) printf("%08x", v); } void write_char(char c) { if (verbose) putchar(c); } void itoa(uint8_t v) { if (verbose) printf("%u", v); } void print_reg(uint16_t reg) { (void)reg; } uint8_t cmd_compare(uint8_t start, const uint8_t *cmd) { const char *w; size_t n; if (start >= cmd_words_len) return 0; w = (const char *)&cmd_buffer[cmd_words_b[start]]; n = strlen((const char *)cmd); return strncmp(w, (const char *)cmd, n) == 0 && (w[n] == ' ' || w[n] == 0); } uint8_t atoi_byte(uint8_t idx) { unsigned v = 0, n = 0; while (cmd_buffer[idx + n] >= '0' && cmd_buffer[idx + n] <= '9') v = v * 10 + (cmd_buffer[idx + n++] - '0'); atoi_results_u8 = (uint8_t)v; return (uint8_t)n; } uint8_t cmd_parse_port_separator(uint8_t idx) { if (!atoi_byte(idx) || atoi_results_u8 < 1 || atoi_results_u8 > NPORTS) return 0; atoi_results_u8--; return 1; } static void sim_cmd(const char *line) { memset(cmd_buffer, 0, sizeof(cmd_buffer)); strncpy((char *)cmd_buffer, line, sizeof(cmd_buffer) - 1); cmd_words_len = 0; for (uint8_t i = 0; cmd_buffer[i]; i++) if (cmd_buffer[i] != ' ' && (i == 0 || cmd_buffer[i - 1] == ' ')) cmd_words_b[cmd_words_len++] = i; err_status = ERR_OK; stp_parse(); } void execute_config(void) { } int8_t vlan_get(uint16_t vlan) { (void)vlan; return -1; } uint16_t port_pvid_get(uint8_t port) { (void)port; return 1; } static int l2mc_calls; static uint16_t l2mc_pmask; void port_l2mc_set(uint8_t mac_last, uint16_t vid, uint16_t pmask) { (void)mac_last; (void)vid; l2mc_calls++; l2mc_pmask = pmask; } void trap_rma_set(uint16_t reg, uint8_t act) { (void)reg; (void)act; } uint8_t port_ingress_filter_get(uint8_t port) { (void)port; return 0; } static int flush_count[NPORTS]; void port_l2_forget_port(uint8_t port) { if (port < NPORTS) flush_count[port]++; } static uint8_t mstp_reg[4]; static uint16_t sim_lag[4]; uint16_t port_lag_members_get(uint8_t lag) { return sim_lag[lag]; } static uint16_t sim_links; static uint8_t sim_speed[NPORTS]; /* nibble as the ASIC reports it: 2 = 1G, 4 = 10G */ static int tx_frames[NPORTS]; struct sim_pkt_out { uint8_t stp_addr[6]; uint8_t src_addr[6]; struct rtl_tag rtl_tag; uint16_t msg_len; uint8_t dsap; uint8_t ssap; uint8_t ctrl; uint16_t proto; uint8_t version; uint8_t bpdu_type; uint8_t flags; struct bridge root; uint32_t root_path_cost; struct bridge bridge; uint8_t port_prio; uint8_t port_id; uint16_t age; uint16_t age_max; uint16_t hello; uint16_t fwd_delay; uint8_t version1_length; }; static struct sim_pkt_out last_tx[NPORTS]; static int tc_frames[NPORTS]; static int tcn_frames[NPORTS]; void reg_read_m(uint16_t addr) { memset(sfr_data, 0, 4); if (addr == RTL837X_MSTP_STATES) memcpy(sfr_data, mstp_reg, 4); else if (addr == RTL837X_REG_LINKS_STS) { sfr_data[1] = (uint8_t)sim_links; sfr_data[2] = (uint8_t)(sim_links >> 8); } else if (addr == RTL837X_REG_LINKS || addr == RTL837X_REG_LINKS_89) { uint8_t base = (addr == RTL837X_REG_LINKS) ? 0 : 8; for (uint8_t k = 0; k < 8 && base + k < NPORTS; k++) sfr_data[3 - (k >> 1)] |= (k & 1) ? (uint8_t)(sim_speed[base + k] << 4) : sim_speed[base + k]; } } void reg_write_m(uint16_t addr) { if (addr == RTL837X_MSTP_STATES) { memcpy(mstp_reg, sfr_data, 4); if (verbose > 1) printf(" [mstp <- %02x %02x %02x %02x]\n", mstp_reg[0], mstp_reg[1], mstp_reg[2], mstp_reg[3]); } } void tcpip_output(void) { struct sim_pkt_out *f = (struct sim_pkt_out *)&uip_buf[RTL_FRAME_DESC_SIZE]; uint16_t pmask = HTONS(f->rtl_tag.pmask); for (uint8_t p = 0; p < NPORTS; p++) if (pmask & (1 << p)) { tx_frames[p]++; if (f->bpdu_type == 0x80) tcn_frames[p]++; else if (f->flags & 0x01) tc_frames[p]++; memcpy(&last_tx[p], f, sizeof(*f)); } uip_len = 0; } static uint8_t port_state(uint8_t port) { return (mstp_reg[3 - (port >> 2)] >> ((port << 1) & 0x7)) & 0x3; } static const char *state_name(uint8_t s) { static const char *n[] = { "off", "discarding", "learning", "forwarding" }; return n[s & 3]; } static void links_set(uint16_t mask) { sim_links = mask; } static void tick(unsigned n) { while (n--) stp_timers(); } static void secs(unsigned s) { tick(s * STP_HZ); } struct sim_bpdu { uint8_t port; /* ingress port index */ uint8_t root_prio; /* high byte of the root priority */ uint8_t root_mac[6]; uint32_t root_cost; /* root path cost as advertised */ uint8_t br_prio; /* sender's own bridge priority high byte */ uint8_t br_mac[6]; uint8_t port_id; /* sender's port number, 1-based */ uint8_t port_prio; /* 0 = 0x80 */ uint8_t flags; /* 0 = designated, learning + forwarding */ uint8_t age; /* message age, seconds */ uint8_t maxage; /* 0 = 20 s, and hello/fwd default with it */ uint8_t hello; uint8_t fwd; uint8_t legacy; /* 1 = 802.1D Config BPDU, 2 = TCN */ uint8_t msg_len; /* 0 = the right length for the type */ }; struct sim_pkt_in { uint8_t stp_addr[6]; uint8_t src_addr[6]; struct rtl_tag rtl_tag; struct vlan_tag vlan_tag; uint16_t msg_len; uint8_t dsap; uint8_t ssap; uint8_t ctrl; uint16_t proto; uint8_t version; uint8_t bpdu_type; uint8_t flags; struct bridge root; uint32_t root_path_cost; struct bridge bridge; uint8_t port_prio; uint8_t port_id; uint16_t age; uint16_t age_max; uint16_t hello; uint16_t fwd_delay; uint8_t version1_length; }; static uint32_t swap32(uint32_t v) { return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24); } static void bpdu_in(const struct sim_bpdu *b) { struct sim_pkt_in *f = (struct sim_pkt_in *)uip_buf; memset(uip_buf, 0, sizeof(uip_buf)); f->vlan_tag.svlan = HTONS(0x8100); memcpy(f->stp_addr, "\x01\x80\xc2\x00\x00\x00", 6); memcpy(f->src_addr, b->br_mac, 6); f->rtl_tag.pmask = HTONS(b->port); f->msg_len = HTONS(b->msg_len ? b->msg_len : (b->legacy == 2 ? 7 : (b->legacy ? 38 : 39))); f->dsap = 0x42; f->ssap = 0x42; f->ctrl = 0x03; f->proto = 0; f->version = b->legacy ? 0x00 : 0x02; f->bpdu_type = b->legacy == 2 ? 0x80 : (b->legacy ? 0x00 : 0x02); f->flags = b->flags ? b->flags : (b->legacy ? 0x00 : 0x3c); f->root.prio = b->root_prio; f->root.ext = 0; memcpy(f->root.mac, b->root_mac, 6); f->root_path_cost = swap32(b->root_cost); f->bridge.prio = b->br_prio; f->bridge.ext = 0; memcpy(f->bridge.mac, b->br_mac, 6); f->port_prio = b->port_prio ? b->port_prio : 0x80; f->port_id = b->port_id; f->age = b->age; f->age_max = b->maxage ? b->maxage : 20; f->hello = b->maxage ? b->hello : 2; f->fwd_delay = b->maxage ? b->fwd : 15; uip_len = sizeof(struct sim_pkt_in); stp_in(); } static int failures; static void check(int cond, const char *what) { if (!cond) { printf(" FAIL: %s\n", what); failures++; } else if (verbose) printf(" ok: %s\n", what); } static void check_state(uint8_t port, uint8_t want, const char *what) { uint8_t got = port_state(port); if (got != want) { printf(" FAIL: %s: port %u is %s, expected %s\n", what, port + 1, state_name(got), state_name(want)); failures++; } else if (verbose) printf(" ok: %s (port %u %s)\n", what, port + 1, state_name(got)); } static void reset_all(void) { memset(mstp_reg, 0, sizeof(mstp_reg)); memset(sim_lag, 0, sizeof(sim_lag)); memset(flush_count, 0, sizeof(flush_count)); memset(tx_frames, 0, sizeof(tx_frames)); memset(last_tx, 0, sizeof(last_tx)); memset(tc_frames, 0, sizeof(tc_frames)); memset(tcn_frames, 0, sizeof(tcn_frames)); for (uint8_t p = 0; p < NPORTS; p++) sim_speed[p] = 2; stp_enabled = 1; stp_defaults(); links_set(0); stp_setup(); tick(2 * STP_HZ); } static const uint8_t ROOT_MAC[6] = { 0x1c, 0x2a, 0xa3, 0x1e, 0xc2, 0x03 }; static const uint8_t PEER_MAC[6] = { 0x00, 0x82, 0x44, 0x2a, 0x74, 0x82 }; static void scen_edge_ports(void) { printf("1. a port nobody talks STP on becomes an edge port\n"); reset_all(); links_set(1 << 3); secs(1); check_state(3, 1, "port with fresh carrier starts discarding"); secs(4); check_state(3, 3, "silent port goes forwarding as an edge port"); } static void scen_root_port(void) { struct sim_bpdu root_bpdu = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; printf("2. the bridge follows the root heard on port 9\n"); reset_all(); links_set(1 << 8); secs(1); memcpy(root_bpdu.root_mac, ROOT_MAC, 6); memcpy(root_bpdu.br_mac, ROOT_MAC, 6); bpdu_in(&root_bpdu); check(stp_root_port == 8, "port 9 becomes the root port"); secs(20); bpdu_in(&root_bpdu); check_state(8, 3, "root port forwards after the listen period"); } static void ring(void) { struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; reset_all(); links_set((1 << 0) | (1 << 8)); secs(1); memcpy(from_root.root_mac, ROOT_MAC, 6); memcpy(from_root.br_mac, ROOT_MAC, 6); memcpy(from_peer.root_mac, ROOT_MAC, 6); memcpy(from_peer.br_mac, PEER_MAC, 6); for (int i = 0; i < 12; i++) { bpdu_in(&from_root); bpdu_in(&from_peer); secs(2); } check(stp_root_port == 8, "the direct uplink stays the root port"); check_state(8, 3, "root port forwards"); check_state(0, 1, "port 1 is discarding (alternate)"); } static void scen_ring(void) { printf("3. a neighbour with a cheaper path to the root must block us\n"); ring(); } static void scen_ring_clears(void) { struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; printf("4. when the better neighbour goes quiet the port comes back\n"); ring(); memcpy(from_root.root_mac, ROOT_MAC, 6); memcpy(from_root.br_mac, ROOT_MAC, 6); bpdu_in(&from_root); secs(2); bpdu_in(&from_root); secs(2); check_state(0, 1, "port 1 still discards within three of the peer's hellos"); for (int i = 0; i < 18; i++) { bpdu_in(&from_root); secs(2); } check_state(0, 3, "port 1 forwards again once the peer information aged out"); } static void scen_cheaper_path(void) { struct sim_bpdu from_root_p9 = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; struct sim_bpdu from_root_p1; printf("5. the cheaper of two paths to the root becomes the root port\n"); reset_all(); stp_pcost[0] = 2000; links_set((1 << 0) | (1 << 8)); secs(1); memcpy(from_root_p9.root_mac, ROOT_MAC, 6); memcpy(from_root_p9.br_mac, ROOT_MAC, 6); from_root_p1 = from_root_p9; from_root_p1.port = 0; from_root_p1.port_id = 7; for (int i = 0; i < 12; i++) { bpdu_in(&from_root_p9); bpdu_in(&from_root_p1); secs(2); } check(stp_root_port == 0, "port 1 wins on cost and becomes the root port"); check(root_bridge_cost == 2000, "the root path cost follows that port"); check_state(0, 3, "the root port forwards"); check_state(8, 1, "the dearer path to the same root is discarding"); stp_pcost[0] = 0; } static void scen_we_are_better(void) { struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 1, .flags = 0x0c }; printf("6. a neighbour with a worse path does not block us\n"); reset_all(); links_set((1 << 0) | (1 << 8)); secs(1); memcpy(from_root.root_mac, ROOT_MAC, 6); memcpy(from_root.br_mac, ROOT_MAC, 6); memcpy(from_peer.root_mac, ROOT_MAC, 6); memcpy(from_peer.br_mac, PEER_MAC, 6); for (int i = 0; i < 18; i++) { bpdu_in(&from_root); bpdu_in(&from_peer); secs(2); } check(stp_root_port == 8, "the uplink stays the root port"); check_state(0, 3, "port 1 keeps forwarding"); } static void scen_alt_survives_link_bounce(void) { struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; printf("7. a blocked port stays blocked when the carrier returns\n"); ring(); memcpy(from_root.root_mac, ROOT_MAC, 6); memcpy(from_root.br_mac, ROOT_MAC, 6); memcpy(from_peer.root_mac, ROOT_MAC, 6); memcpy(from_peer.br_mac, PEER_MAC, 6); links_set(1 << 8); secs(2); links_set((1 << 0) | (1 << 8)); bpdu_in(&from_peer); for (int i = 0; i < 15; i++) { bpdu_in(&from_root); bpdu_in(&from_peer); secs(2); } check_state(0, 1, "port 1 never reaches forwarding while the peer is better"); check(stp_root_port == 8, "the uplink is still the root port"); } static void scen_speed_cost(void) { struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; struct sim_bpdu slow; printf("8. the faster of two links to one root wins on cost\n"); reset_all(); sim_speed[8] = 4; sim_speed[0] = 2; links_set((1 << 0) | (1 << 8)); secs(1); memcpy(from_root.root_mac, ROOT_MAC, 6); memcpy(from_root.br_mac, ROOT_MAC, 6); slow = from_root; slow.port = 0; slow.port_id = 7; for (int i = 0; i < 12; i++) { bpdu_in(&from_root); bpdu_in(&slow); secs(2); } check(stp_root_port == 8, "the 10G port is the root port"); check(root_bridge_cost == 2000, "its cost is the 10G value, not a flat default"); } static struct sim_bpdu root_on(uint8_t port) { struct sim_bpdu b = { .port = port, .root_prio = 0x40, .root_cost = 0, .br_prio = 0x40, .port_id = 2 }; memcpy(b.root_mac, ROOT_MAC, 6); memcpy(b.br_mac, ROOT_MAC, 6); return b; } static const uint8_t THIRD_MAC[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 }; static void scen_root_times(void) { struct sim_bpdu r; printf("9. the times of the root travel on and time the listen period\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); r.age = 3; r.maxage = 12; r.hello = 1; r.fwd = 8; bpdu_in(&r); check(stp_root_port == 8, "port 9 is the root port"); links_set((1 << 1) | (1 << 8)); for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } check_state(1, 1, "a new designated port starts discarding"); for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(1); } check_state(1, 1, "it still discards short of the root's forward delay"); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } check_state(1, 2, "it learns after the root's 8 s, not our own 15 s"); for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(1); } check_state(1, 3, "and forwards after a second 8 s"); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } check(tx_frames[1] > 0, "the designated port sends BPDUs"); check((uint8_t)last_tx[1].age == 4, "the message age is one more than received"); check((uint8_t)last_tx[1].age_max == 12, "max age is the root's"); check((uint8_t)last_tx[1].fwd_delay == 8, "forward delay is the root's"); check((uint8_t)last_tx[1].hello == 2, "hello time stays our own"); } static void scen_too_old(void) { struct sim_bpdu r; printf("10. information as old as max age is not used\n"); reset_all(); links_set(1 << 8); secs(1); r = root_on(8); r.age = 20; r.maxage = 20; r.hello = 2; r.fwd = 15; bpdu_in(&r); check(stp_root_port == 0xff, "the switch stays its own root"); r.age = 19; bpdu_in(&r); check(stp_root_port == 8, "one second younger it is accepted"); } static void scen_info_expiry(void) { struct sim_bpdu r; printf("11. received information lasts three of the sender's hellos\n"); reset_all(); links_set(1 << 8); secs(1); r = root_on(8); r.maxage = 20; r.hello = 1; r.fwd = 15; bpdu_in(&r); check(stp_root_port == 8, "port 9 is the root port"); tick(2 * STP_HZ + STP_HZ / 2); check(stp_root_port == 8, "still the root port 2.5 s later"); tick(STP_HZ); check(stp_root_port == 0xff, "gone after 3 s with a 1 s hello"); } static void scen_dport_tiebreak(void) { struct sim_bpdu a; struct sim_bpdu b; printf("12. equal cost to one bridge: the lower designated port ID wins\n"); reset_all(); links_set((1 << 1) | (1 << 2)); secs(1); a = root_on(1); a.port_id = 5; b = root_on(2); b.port_id = 2; for (int i = 0; i < 12; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } check(stp_root_port == 2, "port 3, which hears port ID 8002, is the root port"); check_state(1, 1, "port 2, which hears 8005, is discarding"); } static void scen_rxport_tiebreak(void) { struct sim_bpdu a; struct sim_bpdu b; printf("13. same designated port on two of ours: the receiving port ID decides\n"); reset_all(); links_set((1 << 3) | (1 << 4)); secs(1); a = root_on(3); b = root_on(4); for (int i = 0; i < 3; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } check(stp_root_port == 3, "equal port priority: the lower port number, port 4"); stp_pprio[4] = 0x40; for (int i = 0; i < 3; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } check(stp_root_port == 4, "a better priority on port 5 wins"); stp_pprio[4] = 0x80; } static void scen_inferior_info(void) { struct sim_bpdu r; struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; struct sim_bpdu third; printf("14. worse information replaces stored only from the same sender\n"); reset_all(); links_set((1 << 0) | (1 << 8)); secs(1); r = root_on(8); memcpy(peer.root_mac, ROOT_MAC, 6); memcpy(peer.br_mac, PEER_MAC, 6); third = peer; third.root_cost = 400000; memcpy(third.br_mac, THIRD_MAC, 6); for (int i = 0; i < 10; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check_state(0, 1, "port 1 is discarding behind the better peer"); for (int i = 0; i < 2; i++) { bpdu_in(&r); bpdu_in(&peer); bpdu_in(&third); secs(1); } check(stp_dcost[0] == 2000, "a worse bridge on the segment does not overwrite the peer"); check_state(0, 1, "and port 1 keeps discarding"); peer.root_cost = 400000; peer.flags = 0x0c; bpdu_in(&peer); check(stp_dcost[0] == 400000, "the peer's own worse information does replace it"); for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check_state(0, 3, "port 1 is designated and forwards again"); } static void scen_root_role_bpdu(void) { struct sim_bpdu r; struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1, .flags = 0x08 | 0x30 }; printf("15. an RST BPDU sent by a root port is not designated information\n"); reset_all(); links_set((1 << 0) | (1 << 8)); secs(1); r = root_on(8); memcpy(peer.root_mac, ROOT_MAC, 6); memcpy(peer.br_mac, PEER_MAC, 6); for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check_state(0, 3, "port 1 keeps forwarding"); check(stp_root_port == 8, "the uplink stays the root port"); } static void scen_alternate_takes_over(void) { struct sim_bpdu r; struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; printf("16. when the root port's information expires an alternate path takes over\n"); reset_all(); stp_pcost[8] = 2000; links_set((1 << 0) | (1 << 8)); secs(1); r = root_on(8); memcpy(peer.root_mac, ROOT_MAC, 6); memcpy(peer.br_mac, PEER_MAC, 6); for (int i = 0; i < 12; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check(stp_root_port == 8, "the 10G uplink is the root port"); for (int i = 0; i < 4; i++) { bpdu_in(&peer); secs(2); } check(stp_root_port == 0, "port 1 is the root port once port 9 went quiet"); check(memcmp(root_bridge.mac, ROOT_MAC, 6) == 0, "the root is still the same bridge"); check(root_bridge_cost == 22000, "the cost is the peer's plus port 1's"); stp_pcost[8] = 0; } static void scen_dbridge_tiebreak(void) { struct sim_bpdu via_peer = { .port = 1, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; struct sim_bpdu via_third; printf("17. equal cost through two bridges: the lower bridge ID wins\n"); reset_all(); links_set((1 << 1) | (1 << 2)); secs(1); memcpy(via_peer.root_mac, ROOT_MAC, 6); memcpy(via_peer.br_mac, PEER_MAC, 6); via_third = via_peer; via_third.port = 2; memcpy(via_third.br_mac, THIRD_MAC, 6); via_third.br_prio = 0x70; for (int i = 0; i < 12; i++) { bpdu_in(&via_peer); bpdu_in(&via_third); secs(2); } check(stp_root_port == 2, "port 3, behind the bridge with priority 7000, is the root port"); check_state(1, 1, "port 2, behind 8000, is discarding"); } static void scen_times_revert(void) { struct sim_bpdu r; printf("18. back to our own times once we are the root again\n"); reset_all(); links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); r.age = 3; r.maxage = 12; r.hello = 2; r.fwd = 8; for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } check(stp_root_port == 8, "port 9 is the root port"); secs(8); check(stp_root_port == 0xff, "the switch is its own root again"); tx_frames[1] = 0; secs(3); check(tx_frames[1] > 0, "port 2 still sends"); check((uint8_t)last_tx[1].age == 0, "the message age is 0 from the root"); check((uint8_t)last_tx[1].age_max == 20, "max age is our own again"); check((uint8_t)last_tx[1].fwd_delay == 15, "forward delay is our own again"); } static void ring_with_uplink_on_9(struct sim_bpdu *r, struct sim_bpdu *peer) { struct sim_bpdu p = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; reset_all(); stp_pcost[8] = 2000; links_set((1 << 0) | (1 << 8)); secs(1); *r = root_on(8); memcpy(p.root_mac, ROOT_MAC, 6); memcpy(p.br_mac, PEER_MAC, 6); *peer = p; for (int i = 0; i < 12; i++) { bpdu_in(r); bpdu_in(peer); secs(2); } } static void scen_tc_via_root_port(void) { struct sim_bpdu r; struct sim_bpdu peer; int waited = 0; printf("19. a port that takes over as root port tells the root about it\n"); ring_with_uplink_on_9(&r, &peer); check(stp_root_port == 8 && port_state(0) == 1, "port 1 starts as the blocked alternate"); check(tc_frames[0] == 0, "no TC on port 1 so far"); links_set(1 << 0); while (port_state(0) != 3 && waited < 20 * STP_HZ) { bpdu_in(&peer); tick(STP_HZ / 2); waited += STP_HZ / 2; } check(stp_root_port == 0, "port 1 is the root port after port 9 went down"); check_state(0, 3, "and forwards"); tick(2); check(tc_frames[0] > 0, "port 1 sends a BPDU with TC toward the root right away"); check((last_tx[0].flags & 0x0c) == 0x08, "that BPDU carries the root port role"); stp_pcost[8] = 0; } static void scen_tc_window_rstp(void) { struct sim_bpdu r; struct sim_bpdu peer; int waited = 0; int seen; printf("20. in RSTP the TC window lasts hello time plus one second\n"); ring_with_uplink_on_9(&r, &peer); links_set(1 << 0); while (port_state(0) != 3 && waited < 20 * STP_HZ) { bpdu_in(&peer); tick(STP_HZ / 2); waited += STP_HZ / 2; } for (int i = 0; i < 4; i++) { bpdu_in(&peer); secs(1); } seen = tc_frames[0]; for (int i = 0; i < 6; i++) { bpdu_in(&peer); secs(1); } check(seen > 0, "TC was sent"); check(tc_frames[0] == seen, "and stops once hello plus one second is over"); stp_pcost[8] = 0; } static void scen_tc_propagation(void) { struct sim_bpdu r; int tc2; int tc9; struct sim_bpdu rtc; printf("21. a TC from the root side goes out on the other ports, not back\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "designated port 2 forwards"); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } memset(flush_count, 0, sizeof(flush_count)); tc2 = tc_frames[1]; tc9 = tc_frames[8]; rtc = r; rtc.flags = 0x3d; bpdu_in(&rtc); tick(2); check(tc_frames[1] > tc2, "port 2 passes the TC on at once"); check(flush_count[1] > 0, "port 2 forgets what it learned"); check(flush_count[8] == 0, "the port the TC came in on keeps its addresses"); check(tc_frames[8] == tc9, "no TC is echoed back to the root"); } static void scen_tc_legacy_tcn(void) { struct sim_bpdu r; struct sim_bpdu ack; int quiet; int n; printf("22. with 802.1D neighbours the root port sends TCN until acknowledged\n"); reset_all(); stp_rstp = 0; stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); r.legacy = 1; for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check(stp_root_port == 8, "port 9 is the root port"); check(tcn_frames[8] > 0, "port 9 entering forwarding is itself reported with TCN"); ack = r; ack.flags = 0x81; bpdu_in(&ack); quiet = tcn_frames[8]; for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } check(tcn_frames[8] == quiet, "acknowledged: no more TCN while nothing changes"); links_set((1 << 1) | (1 << 8)); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "port 2 came up and forwards"); check(tcn_frames[8] > quiet, "port 9 sends TCN toward the root again"); bpdu_in(&ack); n = tcn_frames[8]; for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } check(tcn_frames[8] == n, "and stops once the root acknowledged it"); } static void scen_alt_forgets(void) { struct sim_bpdu r; struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1 }; printf("23. a port that turns alternate forgets what it learned\n"); reset_all(); links_set((1 << 0) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 10; i++) { bpdu_in(&r); secs(2); } check_state(0, 3, "port 1 forwards as designated"); memset(flush_count, 0, sizeof(flush_count)); memcpy(peer.root_mac, ROOT_MAC, 6); memcpy(peer.br_mac, PEER_MAC, 6); bpdu_in(&peer); check_state(0, 1, "port 1 is blocked behind the better peer"); check(flush_count[0] > 0, "and its addresses are flushed"); } static void scen_edge_no_tc(void) { struct sim_bpdu r; uint16_t tc; int tc9; int tc2; printf("24. an edge port coming up raises no topology change\n"); reset_all(); links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 10; i++) { bpdu_in(&r); secs(2); } for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } tc = stp_tc_count; tc9 = tc_frames[8]; tc2 = tc_frames[1]; links_set((1 << 1) | (1 << 3) | (1 << 8)); for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } check_state(3, 3, "the host port forwards as an edge port"); check(stp_tc_count == tc, "the change counter does not move"); check(tc_frames[8] == tc9 && tc_frames[1] == tc2, "no TC is sent"); } static void scen_link_loss_no_tc(void) { struct sim_bpdu r; int tc3; int tc9; printf("25. losing a designated link flushes it but sends no TC\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; stp_pflags[2] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 2) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "port 2 forwards"); memset(flush_count, 0, sizeof(flush_count)); tc3 = tc_frames[2]; tc9 = tc_frames[8]; links_set((1 << 2) | (1 << 8)); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } check(flush_count[1] > 0, "port 2's addresses are flushed"); check(tc_frames[2] == tc3 && tc_frames[8] == tc9, "no TC goes out"); } static void scen_proposal_agreement(void) { struct sim_bpdu r; struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; printf("26. a new designated port proposes and forwards as soon as the neighbour agrees\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } links_set((1 << 1) | (1 << 8)); tick(STP_HZ + STP_HZ / 2); check_state(1, 1, "port 2 starts discarding"); check((last_tx[1].flags & 0x02) != 0, "and its BPDU carries the proposal flag"); check((last_tx[1].flags & 0x0c) == 0x0c, "with the designated role"); memcpy(agree.root_mac, ROOT_MAC, 6); memcpy(agree.br_mac, PEER_MAC, 6); bpdu_in(&agree); tick(2); check_state(1, 3, "port 2 forwards right after the agreement"); } static void scen_no_agreement(void) { struct sim_bpdu r; printf("27. without an agreement the port waits both forward delays\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; stp_pflags[2] &= ~STP_PF_AUTOEDGE; stp_pp2p[2] = 2; links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } links_set((1 << 1) | (1 << 2) | (1 << 8)); for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } check_state(1, 1, "port 2 still discards after 10 s"); check((last_tx[2].flags & 0x02) == 0, "a port set to p2p off does not propose"); for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } check_state(1, 2, "port 2 learns once the first forward delay is over"); for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "and forwards after the second"); stp_pp2p[2] = 0; } static void scen_sync_on_proposal(void) { struct sim_bpdu r; struct sim_bpdu prop; printf("28. a proposal on the root port syncs the designated ports and is agreed\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 3) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "designated port 2 forwards"); check_state(3, 3, "edge port 4 forwards"); check_state(8, 3, "root port 9 forwards"); prop = r; prop.flags = 0x3e; bpdu_in(&prop); tick(2); check_state(1, 1, "port 2 is put back to discarding for the sync"); check((last_tx[1].flags & 0x02) != 0, "and proposes downstream"); check_state(3, 3, "the edge port is left alone"); check_state(8, 3, "the root port keeps forwarding"); check((last_tx[8].flags & 0x40) != 0, "the root port answers with an agreement"); check((last_tx[8].flags & 0x0c) == 0x08, "in a BPDU with the root role"); } static void scen_rapid_failover(void) { struct sim_bpdu r; struct sim_bpdu peer; printf("29. when the root port goes down the alternate forwards at once\n"); ring_with_uplink_on_9(&r, &peer); check(stp_root_port == 8 && port_state(0) == 1, "port 1 is the blocked alternate"); links_set(1 << 0); for (int i = 0; i < 4; i++) { bpdu_in(&peer); tick(STP_HZ / 2); } check(stp_root_port == 0, "port 1 is the root port within two seconds"); check_state(0, 3, "and already forwards"); stp_pcost[8] = 0; } static void scen_rapid_reroot(void) { struct sim_bpdu r; struct sim_bpdu peer; int both = 0; printf("30. the uplink coming back takes over without both ports forwarding\n"); ring_with_uplink_on_9(&r, &peer); stp_pcost[8] = 2000; links_set(1 << 0); for (int i = 0; i < 6; i++) { bpdu_in(&peer); tick(STP_HZ / 2); } check(stp_root_port == 0 && port_state(0) == 3, "port 1 is the forwarding root port"); links_set((1 << 0) | (1 << 8)); for (int i = 0; i < 12; i++) { struct sim_bpdu p = r; p.flags = 0x3e; bpdu_in(&p); if (port_state(0) == 3 && port_state(8) == 3) both++; bpdu_in(&peer); if (port_state(0) == 3 && port_state(8) == 3) both++; for (int k = 0; k < STP_HZ / 4; k++) { tick(1); if (port_state(0) == 3 && port_state(8) == 3) both++; } } check(stp_root_port == 8, "port 9 is the root port again"); check_state(8, 3, "and forwards within three seconds"); check_state(0, 1, "port 1 is back to discarding"); check(both == 0, "ports 1 and 9 never forwarded at the same time"); stp_pcost[8] = 0; } static void scen_agreement_other_root(void) { struct sim_bpdu r; struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; printf("31. an agreement for a different root does not open a port\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } links_set((1 << 1) | (1 << 8)); tick(STP_HZ + STP_HZ / 2); memcpy(agree.root_mac, THIRD_MAC, 6); memcpy(agree.br_mac, PEER_MAC, 6); bpdu_in(&agree); tick(2); check_state(1, 1, "port 2 keeps discarding"); } static void scen_repeated_proposal(void) { struct sim_bpdu r; struct sim_bpdu prop; struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; int agreements = 0; printf("32. a proposal repeated every hello syncs only once\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "designated port 2 forwards"); prop = r; prop.flags = 0x3e; bpdu_in(&prop); tick(2); check_state(1, 1, "the first proposal syncs port 2"); memcpy(agree.root_mac, ROOT_MAC, 6); memcpy(agree.br_mac, PEER_MAC, 6); bpdu_in(&agree); tick(2); check_state(1, 3, "port 2 forwards again after its own agreement"); for (int i = 0; i < 5; i++) { int before = tx_frames[8]; bpdu_in(&prop); tick(2); if (tx_frames[8] > before && (last_tx[8].flags & 0x40)) agreements++; secs(2); check_state(1, 3, "a repeated proposal does not block port 2 again"); } check(agreements == 5, "every repeated proposal is answered with an agreement"); } static void scen_dispute(void) { struct sim_bpdu r; struct sim_bpdu quiet = { .port = 1, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .flags = 0x0c }; struct sim_bpdu claim; int opened = 0; printf("33. a worse neighbour that claims to forward as designated is disputed\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "designated port 2 forwards"); memcpy(quiet.root_mac, ROOT_MAC, 6); memcpy(quiet.br_mac, PEER_MAC, 6); bpdu_in(&quiet); tick(2); check_state(1, 3, "worse designated info without the learning flag is no dispute"); claim = quiet; claim.flags = 0x3c; bpdu_in(&claim); tick(2); check_state(1, 1, "with the learning flag set port 2 goes back to discarding"); for (int i = 0; i < 12; i++) { bpdu_in(&r); bpdu_in(&claim); for (int k = 0; k < 2 * STP_HZ; k++) { tick(1); if (port_state(1) == 3) opened++; } } check(opened == 0, "and never forwards while the dispute goes on"); for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&quiet); secs(2); } check_state(1, 3, "once the neighbour stops claiming, port 2 forwards again"); } static void scen_learning_state(void) { struct sim_bpdu r; struct sim_bpdu claim = { .port = 1, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .flags = 0x3c }; printf("34. without an agreement a port learns for one forward delay before it forwards\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } links_set((1 << 1) | (1 << 8)); for (int i = 0; i < 6; i++) { bpdu_in(&r); secs(2); } check_state(1, 1, "port 2 discards for the first forward delay"); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } check_state(1, 2, "then learns"); check((last_tx[1].flags & 0x30) == 0x10, "its BPDUs carry learning without forwarding"); memcpy(claim.root_mac, ROOT_MAC, 6); memcpy(claim.br_mac, PEER_MAC, 6); bpdu_in(&claim); tick(2); check_state(1, 1, "a dispute while learning sends it back to discarding"); for (int i = 0; i < 9; i++) { bpdu_in(&r); secs(2); } check_state(1, 2, "after a full forward delay it learns again"); for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "and forwards after the second forward delay"); } static void scen_reply_to_inferior(void) { struct sim_bpdu r; int n; int guard = 0; struct sim_bpdu worse = { .port = 1, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .flags = 0x0c }; struct sim_bpdu same_root_better = { .port = 1, .root_prio = 0x40, .root_cost = 2000, .br_prio = 0x80, .port_id = 1, .flags = 0x0c }; printf("35. a designated port answers worse information at once\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "designated port 2 forwards"); n = tx_frames[1]; while (tx_frames[1] == n && guard++ < 3 * STP_HZ) tick(1); check(tx_frames[1] > n, "port 2 sent its regular hello"); memcpy(worse.root_mac, ROOT_MAC, 6); memcpy(worse.br_mac, PEER_MAC, 6); n = tx_frames[1]; bpdu_in(&worse); tick(2); check(tx_frames[1] > n, "the worse BPDU is answered within two ticks, not at the next hello"); tick(STP_HZ / 2); n = tx_frames[1]; memcpy(same_root_better.root_mac, ROOT_MAC, 6); memcpy(same_root_better.br_mac, PEER_MAC, 6); bpdu_in(&same_root_better); tick(2); check(tx_frames[1] == n, "better information is not answered"); } static void scen_backup_role(void) { struct sim_bpdu r; struct sim_bpdu own = { .port = 2, .root_prio = 0x40, .root_cost = 20000, .br_prio = 0x80, .port_id = 4 }; printf("36. a port hearing another port of this bridge is a blocked backup\n"); reset_all(); stp_pflags[2] &= ~STP_PF_AUTOEDGE; stp_pflags[3] &= ~STP_PF_AUTOEDGE; links_set((1 << 2) | (1 << 3) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(2, 3, "port 3 forwards"); check_state(3, 3, "port 4 forwards"); memcpy(own.root_mac, ROOT_MAC, 6); memcpy(own.br_mac, uip_ethaddr.addr, 6); bpdu_in(&own); tick(2); check_state(3, 1, "port 4, the worse of the two, is blocked"); check(((stp_backup >> 3) & 1) == 1, "and has the backup role"); check(((stp_backup >> 2) & 1) == 0, "port 3 is not a backup"); for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } check_state(3, 3, "once its own BPDUs stop coming back port 4 forwards again"); check(((stp_backup >> 3) & 1) == 0, "and is no longer a backup"); bpdu_in(&own); tick(2); check(((stp_backup >> 3) & 1) == 1, "a new loop makes it a backup again"); links_set((1 << 2) | (1 << 8)); secs(2); check(((stp_backup >> 3) & 1) == 0, "losing the link clears the backup role"); } static void migrate_to_stp(void) { struct sim_bpdu r; struct sim_bpdu old = { .port = 1, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .legacy = 1 }; reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } check(last_tx[1].version == 2, "port 2 starts with RST BPDUs"); memcpy(old.root_mac, ROOT_MAC, 6); memcpy(old.br_mac, PEER_MAC, 6); bpdu_in(&old); tick(2); check(last_tx[1].version == 0 && last_tx[1].bpdu_type == 0, "after the 802.1D BPDU port 2 sends Config BPDUs"); check((last_tx[1].flags & 0x02) == 0, "without a proposal"); check(((stp_legacy >> 1) & 1) == 1 && ((stp_legacy >> 8) & 1) == 0, "only port 2 is switched, not the root port"); } static void scen_migrate_to_stp(void) { printf("37. a port that hears an 802.1D neighbour sends Config BPDUs\n"); migrate_to_stp(); } static void scen_migrate_back(void) { struct sim_bpdu r; struct sim_bpdu old = { .port = 1, .root_prio = 0x40, .root_cost = 200000, .br_prio = 0x80, .port_id = 4, .legacy = 1 }; struct sim_bpdu rst; printf("38. mcheck and an RST neighbour bring the port back to RSTP\n"); migrate_to_stp(); r = root_on(8); secs(4); stp_port_mcheck(1); tick(2); check(last_tx[1].version == 2, "mcheck makes port 2 send RST BPDUs again"); memcpy(old.root_mac, ROOT_MAC, 6); memcpy(old.br_mac, PEER_MAC, 6); bpdu_in(&old); tick(STP_HZ); check(last_tx[1].version == 2, "an 802.1D BPDU within the migrate time does not switch it yet"); for (int i = 0; i < 4; i++) { bpdu_in(&r); tick(STP_HZ / 2); } tick(2 * STP_HZ); check(last_tx[1].version == 0, "once the migrate time is over it falls back to STP"); secs(4); rst = old; rst.legacy = 0; rst.flags = 0x0c; bpdu_in(&rst); tick(2); check(last_tx[1].version == 2, "an RST BPDU after the migrate time switches it back to RSTP"); } static void scen_legacy_root_tcn(void) { struct sim_bpdu r; struct sim_bpdu ack; int tcn; printf("39. behind an 802.1D root port a topology change goes out as TCN\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); r.legacy = 1; for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check(stp_root_port == 8, "port 9 is the root port"); ack = r; ack.flags = 0x81; bpdu_in(&ack); for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } tcn = tcn_frames[8]; links_set((1 << 1) | (1 << 8)); for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } check_state(1, 3, "port 2 came up and forwards"); check(tcn_frames[8] > tcn, "port 9 reports the change with TCN"); check(last_tx[1].version == 2, "port 2, with no 802.1D neighbour, sends RST BPDUs"); } static void scen_alternate_agrees(void) { struct sim_bpdu r; struct sim_bpdu peer; struct sim_bpdu prop; int n; printf("40. an alternate port answers a proposal with an agreement\n"); ring_with_uplink_on_9(&r, &peer); stp_pflags[2] &= ~STP_PF_AUTOEDGE; links_set((1 << 0) | (1 << 2) | (1 << 8)); for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check(stp_root_port == 8 && port_state(0) == 1, "port 1 is the blocked alternate"); check_state(2, 3, "designated port 3 forwards"); prop = peer; prop.flags = 0x3e; n = tx_frames[0]; bpdu_in(&prop); tick(2); check(tx_frames[0] > n, "port 1 answers at once"); check((last_tx[0].flags & 0x40) != 0, "with the agreement flag"); check((last_tx[0].flags & 0x0c) == 0x04, "in a BPDU with the alternate role"); check((last_tx[0].flags & 0x02) == 0, "and without a proposal of its own"); check_state(0, 1, "port 1 stays discarding"); check_state(8, 3, "the root port is not touched by the sync"); check_state(2, 1, "the first proposal syncs designated port 3"); for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check_state(2, 3, "port 3 forwards again"); n = tx_frames[0]; bpdu_in(&prop); tick(2); check(tx_frames[0] > n && (last_tx[0].flags & 0x40), "a repeated proposal is answered again"); check_state(2, 3, "but does not sync port 3 a second time"); n = tx_frames[0]; for (int i = 0; i < 3; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } check(tx_frames[0] == n, "without a proposal the alternate port stays silent"); stp_pcost[8] = 0; } static void scen_agreement_from_alternate(void) { struct sim_bpdu r; struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, .br_prio = 0x80, .port_id = 3, .flags = 0x44 }; printf("41. an agreement from an alternate port opens a designated port\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } links_set((1 << 1) | (1 << 8)); tick(STP_HZ + STP_HZ / 2); check_state(1, 1, "port 2 proposes from discarding"); memcpy(agree.root_mac, ROOT_MAC, 6); memcpy(agree.br_mac, PEER_MAC, 6); bpdu_in(&agree); tick(2); check_state(1, 3, "the agreement in an alternate-role BPDU opens it"); } static void scen_timer_relation(void) { printf("42. bridge times must keep 2*(fwd-1) >= maxage >= 2*(hello+1)\n"); reset_all(); save_cmd = 1; sim_cmd("stp fwd 30"); sim_cmd("stp maxage 40"); check(stp_fwddelay_s == 30 && stp_maxage_s == 40, "fwd 30 then maxage 40 is accepted"); check(err_status == ERR_OK, "an accepted value reports no error, so it is kept in the command history"); sim_cmd("stp fwd 20"); check(stp_fwddelay_s == 30, "fwd 20 under maxage 40 is refused"); check(err_status != ERR_OK, "a refused value reports an error, so it stays out of the command history"); sim_cmd("stp port 3 cost x"); check(err_status != ERR_OK, "a malformed command reports an error too"); sim_cmd("stp maxage 6"); sim_cmd("stp hello 3"); check(stp_maxage_s == 6 && stp_hello_s == 2, "hello 3 under maxage 6 is refused"); sim_cmd("stp fwd 4"); sim_cmd("stp maxage 7"); check(stp_fwddelay_s == 4 && stp_maxage_s == 6, "maxage 7 over fwd 4 is refused"); stp_defaults(); save_cmd = 0; sim_cmd("stp maxage 40"); sim_cmd("stp fwd 30"); check(stp_maxage_s == 40 && stp_fwddelay_s == 30, "a saved config may pass through a broken order"); stp_setup(); check(stp_maxage_s == 40 && stp_fwddelay_s == 30, "and keeps its values when the result is consistent"); sim_cmd("stp fwd 10"); stp_setup(); check(stp_hello_s == 2 && stp_maxage_s == 20 && stp_fwddelay_s == 15, "an inconsistent result falls back to 2/20/15 when STP starts"); save_cmd = 1; } static void scen_recent_root(void) { struct sim_bpdu r9; struct sim_bpdu r3; struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; printf("43. ports that were root a moment ago are held back when a new root port forwards\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; stp_pflags[2] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); secs(1); r9 = root_on(8); for (int i = 0; i < 18; i++) { bpdu_in(&r9); secs(2); } check(stp_root_port == 8, "port 9 is the root port"); check_state(1, 3, "designated port 2 forwards"); stp_rrwhile[1] = 10 * STP_HZ; stp_pcost[2] = 2000; links_set((1 << 1) | (1 << 2) | (1 << 8)); secs(1); r3 = root_on(2); bpdu_in(&r3); tick(2); check(stp_root_port == 2, "port 3, the cheaper path, is the new root port"); check_state(2, 3, "and forwards at once"); check_state(8, 1, "the old root port 9 is discarding"); check_state(1, 1, "port 2, a recent root port too, is put back to discarding"); memcpy(agree.root_mac, ROOT_MAC, 6); memcpy(agree.br_mac, PEER_MAC, 6); bpdu_in(&agree); tick(2); check_state(1, 1, "an agreement does not open it while it is a recent root"); for (int i = 0; i < 6; i++) { bpdu_in(&r3); secs(2); } bpdu_in(&agree); tick(2); check_state(1, 3, "once that time is over the agreement opens it"); stp_pcost[2] = 0; } static void scen_recent_backup(void) { struct sim_bpdu r; struct sim_bpdu r4; printf("44. a port that was a backup a moment ago does not rush into forwarding as root\n"); reset_all(); links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } stp_pflags[3] &= ~STP_PF_AUTOEDGE; stp_pcost[3] = 2000; stp_rbwhile[3] = 3 * STP_HZ; links_set((1 << 3) | (1 << 8)); secs(1); r4 = root_on(3); bpdu_in(&r4); tick(2); check(stp_root_port == 3, "port 4 is the new root port"); check_state(3, 1, "but stays discarding while it is a recent backup"); for (int i = 0; i < 4; i++) { bpdu_in(&r4); secs(1); } check_state(3, 3, "and forwards once that time is over"); stp_pcost[3] = 0; } static void scen_short_bpdu(void) { struct sim_bpdu r; struct sim_bpdu c; int n; struct sim_bpdu tcn; printf("45. BPDUs shorter than their type allows are ignored\n"); reset_all(); links_set(1 << 8); secs(1); r = root_on(8); r.msg_len = 38; bpdu_in(&r); check(stp_root_port == 0xff, "an RST BPDU of 38 bytes is ignored"); r.msg_len = 0; bpdu_in(&r); check(stp_root_port == 8, "at 39 bytes it is taken"); reset_all(); links_set(1 << 8); secs(1); c = root_on(8); c.legacy = 1; c.msg_len = 37; bpdu_in(&c); check(stp_root_port == 0xff, "a Config BPDU of 37 bytes is ignored"); c.msg_len = 0; bpdu_in(&c); check(stp_root_port == 8, "at 38 bytes it is taken"); n = tx_frames[8]; tcn = c; tcn.legacy = 2; tcn.msg_len = 6; bpdu_in(&tcn); check(tx_frames[8] == n, "a TCN of 6 bytes gets no TCA"); tcn.msg_len = 0; bpdu_in(&tcn); check(tx_frames[8] > n, "a TCN of 7 bytes is acknowledged"); } static void scen_port_roles(void) { struct sim_bpdu r; struct sim_bpdu peer; printf("46. the reported role covers every port, a port without link is disabled\n"); ring_with_uplink_on_9(&r, &peer); check(stp_port_role(8) == 1, "port 9 is reported as root"); check(stp_port_role(0) == 3, "port 1 as alternate"); check(stp_port_role(4) == 0, "port 5, which has no link, as disabled"); links_set((1 << 0) | (1 << 4) | (1 << 8)); secs(2); check(stp_port_role(4) == 2, "port 5 is designated once its link is up"); stp_pflags[4] &= ~STP_PF_ENABLED; check(stp_port_role(4) == 0, "and disabled again when STP is off on it"); stp_pflags[4] |= STP_PF_ENABLED; stp_pcost[8] = 0; } static void scen_no_tx_without_link(void) { struct sim_bpdu r; printf("47. no BPDU goes out on a port without link\n"); reset_all(); links_set(1 << 8); secs(1); r = root_on(8); for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } check(tx_frames[1] == 0, "port 2 without link sent nothing in ten seconds"); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(2); } check(tx_frames[1] > 0, "once its link is up it sends"); } static void scen_counters(void) { struct sim_bpdu r; int sent = 1; int same = 1; int any = 0; struct sim_bpdu tcn; int zero; printf("48. per-port BPDU and TC counters, and stp clear\n"); reset_all(); stp_pflags[1] &= ~STP_PF_AUTOEDGE; links_set((1 << 1) | (1 << 8)); r = root_on(8); for (int i = 0; i < 40; i++) { bpdu_in(&r); secs(1); } check(stp_cnt[STP_CNT_RX][8] == 40 && stp_cnt[STP_CNT_RX][1] == 0, "each BPDU taken in is counted on its own port"); check(stp_cnt[STP_CNT_TX][1] > 0 && stp_cnt[STP_CNT_TX][0] == 0, "BPDUs sent are counted, none on a port without link"); for (int p = 0; p < NPORTS; p++) sent &= stp_cnt[STP_CNT_TX][p] == (uint32_t)tx_frames[p]; check(sent, "BPDUs sent match the frames on the wire on every port"); check(stp_cnt[STP_CNT_TCRX][8] == 0, "a BPDU without the TC flag is not counted as TC"); for (int p = 0; p < NPORTS; p++) { same &= stp_cnt[STP_CNT_TCTX][p] == (uint32_t)(tc_frames[p] + tcn_frames[p]); any |= tc_frames[p] + tcn_frames[p]; } check(any && same, "TC sent matches the TC and TCN frames on the wire"); r.flags = 0x3d; bpdu_in(&r); check(stp_cnt[STP_CNT_TCRX][8] == 1, "a BPDU with the TC flag is counted as TC"); tcn = r; tcn.legacy = 2; bpdu_in(&tcn); check(stp_cnt[STP_CNT_TCRX][8] == 2 && stp_cnt[STP_CNT_RX][8] == 42, "and so is a TCN"); sim_cmd("stp clear"); zero = err_status == ERR_OK; for (int k = 0; k < STP_CNT_N; k++) for (int p = 0; p < NPORTS; p++) zero &= stp_cnt[k][p] == 0; check(zero, "stp clear zeroes every counter on every port"); } static void scen_path_cost_method(void) { struct sim_bpdu r; printf("49. short path costs follow 802.1D-1998\n"); reset_all(); links_set(1 << 8); r = root_on(8); for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } check(root_bridge_cost == 20000, "long method: a 1G root port costs 20000"); sim_cmd("stp pathcost short"); check(err_status == ERR_OK && stp_pcost_short == 1, "stp pathcost short is accepted"); for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } check(root_bridge_cost == 4, "short method: the same port costs 4"); sim_speed[8] = 4; for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } check(root_bridge_cost == 2, "and a 10G port costs 2"); sim_cmd("stp pathcost medium"); check(err_status != ERR_OK && stp_pcost_short == 1, "an unknown method is refused"); sim_cmd("stp pathcost long"); sim_speed[8] = 2; } static void scen_bpdu_handling(void) { printf("50. BPDU handling while STP is off: flood or filter\n"); reset_all(); stp_enabled = 0; l2mc_calls = 0; stp_off(); check(l2mc_calls && (l2mc_pmask & PMASK_9) == PMASK_9, "by default BPDUs are flooded when STP stops"); l2mc_calls = 0; sim_cmd("stp bpdu filter"); check(err_status == ERR_OK && l2mc_calls && l2mc_pmask == PMASK_CPU, "filter keeps them on the CPU, at once while STP is off"); stp_enabled = 1; stp_setup(); l2mc_calls = 0; sim_cmd("stp bpdu flood"); check(l2mc_calls == 0, "while STP runs the setting only waits"); stp_enabled = 0; stp_off(); check((l2mc_pmask & PMASK_9) == PMASK_9, "and applies when STP stops"); sim_cmd("stp bpdu drop"); check(err_status != ERR_OK, "an unknown handling is refused"); stp_enabled = 1; } static void scen_last_tc(void) { struct sim_bpdu tcn; uint16_t before; printf("51. time since the last topology change\n"); reset_all(); links_set(1 << 8); tcn = root_on(8); tcn.legacy = 2; secs(4); before = stp_tc_count; bpdu_in(&tcn); check(stp_tc_count != before, "a TCN moves the topology change counter"); secs(5); check(stp_tc_secs >= 4 && stp_tc_secs <= 5, "the clock counts the seconds since then"); bpdu_in(&tcn); secs(1); check(stp_tc_secs <= 1, "and starts again at the next change"); } static void scen_lag(void) { struct sim_bpdu before; struct sim_bpdu b; struct sim_bpdu c; struct sim_bpdu t; printf("52. a link aggregation group is one port of the tree\n"); reset_all(); links_set((1 << 1) | (1 << 2) | (1 << 8)); secs(1); before = root_on(2); bpdu_in(&before); check(stp_root_port == 2 && stp_info_while[2], "port 3 hears the root before it joins a group"); sim_lag[0] = (1 << 1) | (1 << 2); secs(1); check(stp_info_while[2] == 0, "joining the group drops what the port heard on its own"); check(stp_ent_id(STP_LAG_BASE) && !stp_ent_id(1) && !stp_ent_id(2), "the group replaces its member ports"); check(port_state(1) == 1 && port_state(2) == 1, "both members start discarding together"); b = root_on(2); bpdu_in(&b); check(stp_root_port == STP_LAG_BASE, "a BPDU on either member reaches the group"); c = root_on(1); c.root_cost = 4; bpdu_in(&c); check(stp_root_port == STP_LAG_BASE, "and the other member feeds the same port"); secs(31); bpdu_in(&b); check(port_state(1) == 3 && port_state(2) == 3, "both members forward as the root port"); sim_cmd("stp port 2 cost 5"); check(err_status != ERR_OK, "a member port cannot be set on its own"); sim_cmd("stp lag 1 cost 5"); check(err_status == ERR_OK && stp_pcost[STP_LAG_BASE] == 5, "the group takes the setting"); sim_cmd("stp lag 5 cost 5"); check(err_status != ERR_OK, "a group number past the last one is refused"); memset(tx_frames, 0, sizeof(tx_frames)); tcn_frames[1] = 0; t = root_on(2); t.legacy = 2; bpdu_in(&t); check(tx_frames[1] == 1 && tx_frames[2] == 0, "a reply leaves through the lowest member"); check(last_tx[1].port_id == STP_LAG_BASE + 1, "with the group's own port id"); memset(flush_count, 0, sizeof(flush_count)); links_set((1 << 2) | (1 << 8)); secs(1); check(port_state(2) == 3 && stp_root_port == STP_LAG_BASE, "losing one member keeps the group forwarding"); check(flush_count[2] == 0, "and is no topology change"); memset(tx_frames, 0, sizeof(tx_frames)); bpdu_in(&t); check(tx_frames[1] == 0 && tx_frames[2] == 1, "replies move to the member that still has a link"); sim_lag[0] = 0; links_set((1 << 1) | (1 << 2) | (1 << 8)); secs(1); check(!stp_ent_id(STP_LAG_BASE) && stp_ent_id(1) && stp_ent_id(2), "dissolving the group gives the ports back"); check(stp_root_port != STP_LAG_BASE, "and the group stops being the root port"); 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"); } static void scen_root_port_needs_link(void) { struct sim_bpdu r; printf("54. a BPDU credited to a port without carrier does not pick the root port\n"); reset_all(); links_set(1 << 2); secs(1); r = root_on(0); bpdu_in(&r); tick(2); check(stp_root_port == 0xff, "the bridge stays its own root"); links_set((1 << 0) | (1 << 2)); secs(1); check(stp_root_port == 0, "once the carrier is seen the port becomes the root port"); check_state(0, 3, "and forwards"); } int main(int argc, char **argv) { verbose = argc > 1 && argv[1][0] == '-' ? (argv[1][1] == 'd' ? 2 : 1) : 0; scen_edge_ports(); scen_root_port(); scen_ring(); scen_ring_clears(); scen_cheaper_path(); scen_we_are_better(); scen_alt_survives_link_bounce(); scen_speed_cost(); scen_root_times(); scen_too_old(); scen_info_expiry(); scen_dport_tiebreak(); scen_rxport_tiebreak(); scen_inferior_info(); scen_root_role_bpdu(); scen_alternate_takes_over(); scen_dbridge_tiebreak(); scen_times_revert(); scen_tc_via_root_port(); scen_tc_window_rstp(); scen_tc_propagation(); scen_tc_legacy_tcn(); scen_alt_forgets(); scen_edge_no_tc(); scen_link_loss_no_tc(); scen_proposal_agreement(); scen_no_agreement(); scen_sync_on_proposal(); scen_rapid_failover(); scen_rapid_reroot(); scen_agreement_other_root(); scen_repeated_proposal(); scen_dispute(); scen_learning_state(); scen_reply_to_inferior(); scen_backup_role(); scen_migrate_to_stp(); scen_migrate_back(); scen_legacy_root_tcn(); scen_alternate_agrees(); scen_agreement_from_alternate(); scen_timer_relation(); scen_recent_root(); scen_recent_backup(); scen_short_bpdu(); scen_port_roles(); scen_no_tx_without_link(); scen_counters(); scen_path_cost_method(); scen_bpdu_handling(); scen_last_tc(); scen_lag(); scen_tc_needs_forwarding(); scen_root_port_needs_link(); if (failures) { printf("\n%d check(s) failed\n", failures); return 1; } printf("\nall scenarios passed\n"); return 0; }