diff options
Diffstat (limited to 'tests/test-rstp.c')
-rw-r--r-- | tests/test-rstp.c | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/tests/test-rstp.c b/tests/test-rstp.c new file mode 100644 index 000000000..ad71f044e --- /dev/null +++ b/tests/test-rstp.c @@ -0,0 +1,684 @@ +#include <config.h> + +#include "rstp.h" +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdlib.h> +#include "ofpbuf.h" +#include "ovstest.h" +#include "packets.h" +#include "vlog.h" + +#define MAX_PORTS 10 + +struct bpdu { + int port_no; + void *data; + size_t size; +}; + +struct bridge { + struct test_case *tc; + int id; + bool reached; + + struct rstp *rstp; + + struct lan *ports[RSTP_MAX_PORTS]; + int n_ports; + int n_active_ports; + +#define RXQ_SIZE 16 + struct bpdu rxq[RXQ_SIZE]; + int rxq_head, rxq_tail; +}; + +struct lan_conn { + struct bridge *bridge; + int port_no; +}; + +struct lan { + struct test_case *tc; + const char *name; + bool reached; + struct lan_conn conns[16]; + int n_conns; +}; + +struct test_case { + struct bridge *bridges[16]; + int n_bridges; + struct lan *lans[26]; + int n_lans; +}; + +static const char *file_name; +static int line_number; +static char line[128]; +static char *pos, *token; +static int n_warnings; + +static struct test_case * +new_test_case(void) +{ + struct test_case *tc = xmalloc(sizeof *tc); + tc->n_bridges = 0; + tc->n_lans = 0; + return tc; +} + +static void +send_bpdu(struct ofpbuf *pkt, int port_no, void *b_) +{ + struct bridge *b = b_; + struct lan *lan; + + assert(port_no < b->n_ports); + lan = b->ports[port_no]; + if (lan) { + const void *data = ofpbuf_l3(pkt); + size_t size = (char *) ofpbuf_tail(pkt) - (char *) data; + int i; + + for (i = 0; i < lan->n_conns; i++) { + struct lan_conn *conn = &lan->conns[i]; + if (conn->bridge != b || conn->port_no != port_no) { + struct bridge *dst = conn->bridge; + struct bpdu *bpdu = &dst->rxq[dst->rxq_head++ % RXQ_SIZE]; + assert(dst->rxq_head - dst->rxq_tail <= RXQ_SIZE); + bpdu->data = xmemdup(data, size); + bpdu->size = size; + bpdu->port_no = conn->port_no; + } + } + } + ofpbuf_delete(pkt); +} + +static struct bridge * +new_bridge(struct test_case *tc, int id) +{ + struct bridge *b = xmalloc(sizeof *b); + char name[16]; + struct rstp_port *p; + int i; + + b->tc = tc; + b->id = id; + snprintf(name, sizeof name, "rstp%x", id); + b->rstp = rstp_create(name, id, send_bpdu, b); + for (i = 1; i < MAX_PORTS; i++) { + p = rstp_add_port(b->rstp); + rstp_port_set_aux(p, b); + rstp_port_set_state(p, RSTP_DISABLED); + rstp_port_set_mac_operational(p, true); + } + + assert(tc->n_bridges < ARRAY_SIZE(tc->bridges)); + b->n_ports = 1; + b->n_active_ports = 1; + b->rxq_head = b->rxq_tail = 0; + tc->bridges[tc->n_bridges++] = b; + return b; +} + +static struct lan * +new_lan(struct test_case *tc, const char *name) +{ + struct lan *lan = xmalloc(sizeof *lan); + lan->tc = tc; + lan->name = xstrdup(name); + lan->n_conns = 0; + assert(tc->n_lans < ARRAY_SIZE(tc->lans)); + tc->lans[tc->n_lans++] = lan; + return lan; +} + +static void +reconnect_port(struct bridge *b, int port_no, struct lan *new_lan) +{ + struct lan *old_lan; + int j; + + assert(port_no < b->n_ports); + old_lan = b->ports[port_no]; + if (old_lan == new_lan) { + return; + } + + /* Disconnect from old_lan. */ + if (old_lan) { + for (j = 0; j < old_lan->n_conns; j++) { + struct lan_conn *c = &old_lan->conns[j]; + if (c->bridge == b && c->port_no == port_no) { + memmove(c, c + 1, sizeof *c * (old_lan->n_conns - j - 1)); + old_lan->n_conns--; + break; + } + } + } + + /* Connect to new_lan. */ + b->ports[port_no] = new_lan; + if (new_lan) { + int conn_no = new_lan->n_conns++; + assert(conn_no < ARRAY_SIZE(new_lan->conns)); + new_lan->conns[conn_no].bridge = b; + new_lan->conns[conn_no].port_no = port_no; + } +} + +static void +new_port(struct bridge *b, struct lan *lan, uint32_t path_cost) +{ + int port_no = b->n_ports++; + struct rstp_port *p = rstp_get_port(b->rstp, port_no); + + assert(port_no < ARRAY_SIZE(b->ports)); + b->ports[port_no] = NULL; + /* Enable port. */ + reinitialize_port(p); + rstp_port_set_path_cost(p, path_cost); + rstp_port_set_state(p, RSTP_DISCARDING); + rstp_port_set_mac_operational(p, true); + reconnect_port(b, port_no, lan); +} + +static void +dump(struct test_case *tc) +{ + int i; + + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + struct rstp *rstp = b->rstp; + int j; + + printf("%s:", rstp_get_name(rstp)); + if (rstp_is_root_bridge(rstp)) { + printf(" root"); + } + printf("\n"); + for (j = 0; j < b->n_ports; j++) { + struct rstp_port *p = rstp_get_port(rstp, j); + enum rstp_state state = rstp_port_get_state(p); + + printf("\tport %d", j); + if (b->ports[j]) { + printf(" (lan %s)", b->ports[j]->name); + } else { + printf(" (disconnected)"); + } + printf(": %s", rstp_state_name(state)); + if (p == rstp_get_root_port(rstp)) { + printf(" (root port, root_path_cost=%u)", + rstp_get_root_path_cost(rstp)); + } + printf("\n"); + } + } +} + +static void dump_lan_tree(struct test_case *, struct lan *, int level); + +static void +dump_bridge_tree(struct test_case *tc, struct bridge *b, int level) +{ + int i; + + if (b->reached) { + return; + } + b->reached = true; + for (i = 0; i < level; i++) { + printf("\t"); + } + printf("%s\n", rstp_get_name(b->rstp)); + for (i = 0; i < b->n_ports; i++) { + struct lan *lan = b->ports[i]; + struct rstp_port *p = rstp_get_port(b->rstp, i); + if (rstp_port_get_state(p) == RSTP_FORWARDING && lan) { + dump_lan_tree(tc, lan, level + 1); + } + } +} + +static void +dump_lan_tree(struct test_case *tc, struct lan *lan, int level) +{ + int i; + + if (lan->reached) { + return; + } + lan->reached = true; + for (i = 0; i < level; i++) { + printf("\t"); + } + printf("%s\n", lan->name); + for (i = 0; i < lan->n_conns; i++) { + struct bridge *b = lan->conns[i].bridge; + dump_bridge_tree(tc, b, level + 1); + } +} + +static void +tree(struct test_case *tc) +{ + int i; + + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + b->reached = false; + } + for (i = 0; i < tc->n_lans; i++) { + struct lan *lan = tc->lans[i]; + lan->reached = false; + } + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + struct rstp *rstp = b->rstp; + if (rstp_is_root_bridge(rstp)) { + dump_bridge_tree(tc, b, 0); + } + } +} + +static void +simulate(struct test_case *tc, int granularity) +{ + int time, i, round_trips; + for (time = 0; time < 1000 * 180; time += granularity) { + + for (i = 0; i < tc->n_bridges; i++) { + rstp_tick_timers(tc->bridges[i]->rstp); + } + for (round_trips = 0; round_trips < granularity; round_trips++) { + bool any = false; + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + for (; b->rxq_tail != b->rxq_head; b->rxq_tail++) { + struct bpdu *bpdu = &b->rxq[b->rxq_tail % RXQ_SIZE]; + rstp_received_bpdu(rstp_get_port(b->rstp, bpdu->port_no), + bpdu->data, bpdu->size); + free(bpdu->data); + any = true; + } + } + if (!any) { + break; + } + } + } +} + +static void +err(const char *message, ...) + PRINTF_FORMAT(1, 2) + NO_RETURN; + +static void +err(const char *message, ...) +{ + va_list args; + + fprintf(stderr, "%s:%d:%"PRIdPTR": ", file_name, line_number, pos - line); + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + putc('\n', stderr); + + exit(EXIT_FAILURE); +} + +static void +warn(const char *message, ...) + PRINTF_FORMAT(1, 2); + +static void +warn(const char *message, ...) +{ + va_list args; + + fprintf(stderr, "%s:%d: ", file_name, line_number); + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + putc('\n', stderr); + + n_warnings++; +} + +static bool +get_token(void) +{ + char *start; + + while (isspace((unsigned char) *pos)) { + pos++; + } + if (*pos == '\0') { + free(token); + token = NULL; + return false; + } + + start = pos; + if (isalpha((unsigned char) *pos)) { + while (isalpha((unsigned char) *++pos)) { + continue; + } + } else if (isdigit((unsigned char) *pos)) { + if (*pos == '0' && (pos[1] == 'x' || pos[1] == 'X')) { + pos += 2; + while (isxdigit((unsigned char) *pos)) { + pos++; + } + } else { + while (isdigit((unsigned char) *++pos)) { + continue; + } + } + } else { + pos++; + } + + free(token); + token = xmemdup0(start, pos - start); + return true; +} + +static bool +get_int(int *intp) +{ + char *save_pos = pos; + if (token && isdigit((unsigned char) *token)) { + *intp = strtol(token, NULL, 0); + get_token(); + return true; + } else { + pos = save_pos; + return false; + } +} + +static bool +match(const char *want) +{ + if (token && !strcmp(want, token)) { + get_token(); + return true; + } else { + return false; + } +} + +static int +must_get_int(void) +{ + int x; + if (!get_int(&x)) { + err("expected integer"); + } + return x; +} + +static void +must_match(const char *want) +{ + if (!match(want)) { + err("expected \"%s\"", want); + } +} + +static void +test_rstp_main(int argc, char *argv[]) +{ + struct test_case *tc; + FILE *input_file; + int i; + + vlog_set_pattern(VLF_CONSOLE, "%c|%p|%m"); + vlog_set_levels(NULL, VLF_SYSLOG, VLL_OFF); + + if (argc != 2) { + ovs_fatal(0, "usage: test-rstp INPUT.RSTP\n"); + } + file_name = argv[1]; + + input_file = fopen(file_name, "r"); + if (!input_file) { + ovs_fatal(errno, "error opening \"%s\"", file_name); + } + + tc = new_test_case(); + for (i = 0; i < 26; i++) { + char name[2]; + name[0] = 'a' + i; + name[1] = '\0'; + new_lan(tc, name); + } + + for (line_number = 1; fgets(line, sizeof line, input_file); + line_number++) + { + char *newline, *hash; + + newline = strchr(line, '\n'); + if (newline) { + *newline = '\0'; + } + hash = strchr(line, '#'); + if (hash) { + *hash = '\0'; + } + + pos = line; + if (!get_token()) { + continue; + } + if (match("bridge")) { + struct bridge *bridge; + int bridge_no, port_no; + + bridge_no = must_get_int(); + if (bridge_no < tc->n_bridges) { + bridge = tc->bridges[bridge_no]; + } else if (bridge_no == tc->n_bridges) { + bridge = new_bridge(tc, must_get_int()); + } else { + err("bridges must be numbered consecutively from 0"); + } + if (match("^")) { + rstp_set_bridge_priority(bridge->rstp, must_get_int()); + } + if (match("=")) { + for (port_no = 1; port_no < MAX_PORTS; port_no++) { + struct rstp_port *p = rstp_get_port(bridge->rstp, port_no); + if (!token || match("X")) { + /* Disable port. */ + reinitialize_port(p); + rstp_port_set_state(p, RSTP_DISABLED); + rstp_port_set_mac_operational(p, false); + } else if (match("_")) { + /* Nothing to do. */ + } else { + struct lan *lan; + uint32_t path_cost; + + if (!strcmp(token, "0")) { + lan = NULL; + } else if (strlen(token) == 1 + && islower((unsigned char)*token)) { + lan = tc->lans[*token - 'a']; + } else { + err("%s is not a valid LAN name " + "(0 or a lowercase letter)", token); + } + get_token(); + + path_cost = match(":") ? must_get_int() : + RSTP_DEFAULT_PORT_PATH_COST; + if (port_no < bridge->n_ports) { + /* Enable port. */ + reinitialize_port(p); + rstp_port_set_path_cost(p, path_cost); + rstp_port_set_state(p, RSTP_DISCARDING); + rstp_port_set_mac_operational(p, true); + reconnect_port(bridge, port_no, lan); + } else if (port_no == bridge->n_ports) { + new_port(bridge, lan, path_cost); + bridge->n_active_ports++; + } else { + err("ports must be numbered consecutively"); + } + if (match("^")) { + rstp_port_set_priority(p, must_get_int()); + } + } + } + } + } else if (match("run")) { + simulate(tc, must_get_int()); + } else if (match("dump")) { + dump(tc); + } else if (match("tree")) { + tree(tc); + } else if (match("check")) { + struct bridge *b; + struct rstp *rstp; + int bridge_no, port_no; + uint32_t cost_value; + + bridge_no = must_get_int(); + if (bridge_no >= tc->n_bridges) { + err("no bridge numbered %d", bridge_no); + } + b = tc->bridges[bridge_no]; + rstp = b->rstp; + + must_match("="); + + if (match("rootid")) { + uint64_t rootid; + must_match(":"); + rootid = must_get_int(); + if (match("^")) { + rootid |= (uint64_t) must_get_int() << 48; + } else { + rootid |= UINT64_C(0x8000) << 48; + } + if (rstp_get_designated_root(rstp) != rootid) { + warn("%s: root "RSTP_ID_FMT", not %"PRIx64, + rstp_get_name(rstp), + RSTP_ID_ARGS(rstp_get_designated_root(rstp)), + rootid); + } + } + cost_value = rstp_get_root_path_cost(rstp); + if (match("root")) { + if (cost_value != 0) { + warn("%s: root path cost of root is %d instead of 0 \n", + rstp_get_name(rstp), cost_value); + } + if (!rstp_is_root_bridge(rstp)) { + warn("%s: root is "RSTP_ID_FMT", not "RSTP_ID_FMT"", + rstp_get_name(rstp), + RSTP_ID_ARGS(rstp_get_designated_root(rstp)), + RSTP_ID_ARGS(rstp_get_bridge_id(rstp))); + } + for (port_no = 1; port_no < b->n_active_ports; port_no++) { + struct rstp_port *p = rstp_get_port(rstp, port_no); + enum rstp_state state = rstp_port_get_state(p); + if (state != RSTP_DISABLED && state != RSTP_FORWARDING) { + warn("%s: root port %d in state %s", + rstp_get_name(b->rstp), port_no, + rstp_state_name(state)); + } + } + } else { + for (port_no = 1; port_no < b->n_active_ports; port_no++) { + struct rstp_port *p = rstp_get_port(rstp, port_no); + enum rstp_state state; + if (token == NULL || match("D")) { + state = RSTP_DISABLED; + } else if (match("Di")) { + state = RSTP_DISCARDING; + } else if (match("Le")) { + state = RSTP_LEARNING; + } else if (match("F")) { + state = RSTP_FORWARDING; + } else if (match("_")) { + continue; + } else { + err("unknown port state %s", token); + } + if (rstp_port_get_state(p) != state) { + warn("%s port %d: state is %s but should be %s", + rstp_get_name(rstp), port_no, + rstp_state_name(rstp_port_get_state(p)), + rstp_state_name(state)); + } + if (state == RSTP_FORWARDING) { + struct rstp_port *root_port = rstp_get_root_port(rstp); + if (match(":")) { + int root_path_cost = must_get_int(); + if (p != root_port) { + warn("%s: port %d is not the root port", + rstp_get_name(rstp), port_no); + if (!root_port) { + warn("%s: (there is no root port)", + rstp_get_name(rstp)); + } else { + warn("%s: (port %d is the root port)", + rstp_get_name(rstp), + rstp_port_number(root_port)); + } + } else if (cost_value != root_path_cost) { + warn("%s: root path cost is %d, should be %d", + rstp_get_name(rstp), + cost_value, + root_path_cost); + } + } else if (p == root_port) { + warn("%s: port %d is the root port but " + "not expected to be", + rstp_get_name(rstp), port_no); + } + } + } + } + if (n_warnings) { + printf("failing because of %d warnings\n", n_warnings); + exit(EXIT_FAILURE); + } + } + if (get_token()) { + printf("failing because of errors\n"); + err("trailing garbage on line"); + } + } + free(token); + + for (i = 0; i < tc->n_lans; i++) { + struct lan *lan = tc->lans[i]; + free(CONST_CAST(char *, lan->name)); + free(lan); + } + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *bridge = tc->bridges[i]; + int j; + for (j = 1; j < MAX_PORTS; j++) { + rstp_delete_port(rstp_get_port(bridge->rstp, j)); + } + rstp_unref(bridge->rstp); + free(bridge); + } + free(tc); +} + +OVSTEST_REGISTER("test-rstp", test_rstp_main); |