From: Justin Pettit Date: Tue, 20 Sep 2011 22:08:05 +0000 (-0700) Subject: Add back 802.1D Spanning Tree Protocol (STP) library code. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=829a7d0283a25d61cc5607e5015103373e100e21;p=openvswitch Add back 802.1D Spanning Tree Protocol (STP) library code. At one point, the OVS distribution contained an IEEE 802.1D Spanning Tree Protocol (STP) library written by Ben Pfaff and based on the 802.1D-1998 reference code. It was never integrated into ovs-vswitchd, so it was removed as part of commit ba18611 (Remove vestigial support for Spanning Tree Protocol.) This commit reintroduces the library, cleans up a few spots, and makes it build cleanly against new code. A future commit will have ovs-vswitchd use this library. --- diff --git a/lib/automake.mk b/lib/automake.mk index df307112..1320e312 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -141,6 +141,8 @@ lib_libopenvswitch_a_SOURCES = \ lib/sort.h \ lib/sset.c \ lib/sset.h \ + lib/stp.c \ + lib/stp.h \ lib/stream-fd.c \ lib/stream-fd.h \ lib/stream-provider.h \ diff --git a/lib/stp.c b/lib/stp.c new file mode 100644 index 00000000..edd37b32 --- /dev/null +++ b/lib/stp.c @@ -0,0 +1,1231 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Based on sample implementation in 802.1D-1998. Above copyright and license + * applies to all modifications. */ + +#include + +#include "stp.h" +#include +#include +#include +#include +#include +#include +#include "byte-order.h" +#include "ofpbuf.h" +#include "packets.h" +#include "util.h" +#include "vlog.h" + +VLOG_DEFINE_THIS_MODULE(stp); + +#define STP_PROTOCOL_ID 0x0000 +#define STP_PROTOCOL_VERSION 0x00 +#define STP_TYPE_CONFIG 0x00 +#define STP_TYPE_TCN 0x80 + +struct stp_bpdu_header { + ovs_be16 protocol_id; /* STP_PROTOCOL_ID. */ + uint8_t protocol_version; /* STP_PROTOCOL_VERSION. */ + uint8_t bpdu_type; /* One of STP_TYPE_*. */ +} __attribute__((packed)); +BUILD_ASSERT_DECL(sizeof(struct stp_bpdu_header) == 4); + +enum stp_config_bpdu_flags { + STP_CONFIG_TOPOLOGY_CHANGE_ACK = 0x80, + STP_CONFIG_TOPOLOGY_CHANGE = 0x01 +}; + +struct stp_config_bpdu { + struct stp_bpdu_header header; /* Type STP_TYPE_CONFIG. */ + uint8_t flags; /* STP_CONFIG_* flags. */ + ovs_be64 root_id; /* 8.5.1.1: Bridge believed to be root. */ + ovs_be32 root_path_cost; /* 8.5.1.2: Cost of path to root. */ + ovs_be64 bridge_id; /* 8.5.1.3: ID of transmitting bridge. */ + ovs_be16 port_id; /* 8.5.1.4: Port transmitting the BPDU. */ + ovs_be16 message_age; /* 8.5.1.5: Age of BPDU at tx time. */ + ovs_be16 max_age; /* 8.5.1.6: Timeout for received data. */ + ovs_be16 hello_time; /* 8.5.1.7: Time between BPDU generation. */ + ovs_be16 forward_delay; /* 8.5.1.8: State progression delay. */ +} __attribute__((packed)); +BUILD_ASSERT_DECL(sizeof(struct stp_config_bpdu) == 35); + +struct stp_tcn_bpdu { + struct stp_bpdu_header header; /* Type STP_TYPE_TCN. */ +} __attribute__((packed)); +BUILD_ASSERT_DECL(sizeof(struct stp_tcn_bpdu) == 4); + +struct stp_timer { + bool active; /* Timer in use? */ + int value; /* Current value of timer, counting up. */ +}; + +struct stp_port { + struct stp *stp; + int port_id; /* 8.5.5.1: Unique port identifier. */ + enum stp_state state; /* 8.5.5.2: Current state. */ + int path_cost; /* 8.5.5.3: Cost of tx/rx on this port. */ + stp_identifier designated_root; /* 8.5.5.4. */ + int designated_cost; /* 8.5.5.5: Path cost to root on port. */ + stp_identifier designated_bridge; /* 8.5.5.6. */ + int designated_port; /* 8.5.5.7: Port to send config msgs on. */ + bool topology_change_ack; /* 8.5.5.8: Flag for next config BPDU. */ + bool config_pending; /* 8.5.5.9: Send BPDU when hold expires? */ + bool change_detection_enabled; /* 8.5.5.10: Detect topology changes? */ + + struct stp_timer message_age_timer; /* 8.5.6.1: Age of received info. */ + struct stp_timer forward_delay_timer; /* 8.5.6.2: State change timer. */ + struct stp_timer hold_timer; /* 8.5.6.3: BPDU rate limit timer. */ + + bool state_changed; +}; + +struct stp { + /* Static bridge data. */ + char *name; /* Human-readable name for log messages. */ + stp_identifier bridge_id; /* 8.5.3.7: This bridge. */ + int max_age; /* 8.5.3.4: Time to drop received data. */ + int hello_time; /* 8.5.3.5: Time between sending BPDUs. */ + int forward_delay; /* 8.5.3.6: Delay between state changes. */ + int bridge_max_age; /* 8.5.3.8: max_age when we're root. */ + int bridge_hello_time; /* 8.5.3.9: hello_time as root. */ + int bridge_forward_delay; /* 8.5.3.10: forward_delay as root. */ + int rq_max_age; /* User-requested max age, in ms. */ + int rq_hello_time; /* User-requested hello time, in ms. */ + int rq_forward_delay; /* User-requested forward delay, in ms. */ + int elapsed_remainder; /* Left-over msecs from last stp_tick(). */ + + /* Dynamic bridge data. */ + stp_identifier designated_root; /* 8.5.3.1: Bridge believed to be root. */ + unsigned int root_path_cost; /* 8.5.3.2: Cost of path to root. */ + struct stp_port *root_port; /* 8.5.3.3: Lowest cost port to root. */ + bool topology_change_detected; /* 8.5.3.11: Detected a topology change? */ + bool topology_change; /* 8.5.3.12: Received topology change? */ + + /* Bridge timers. */ + struct stp_timer hello_timer; /* 8.5.4.1: Hello timer. */ + struct stp_timer tcn_timer; /* 8.5.4.2: Topology change timer. */ + struct stp_timer topology_change_timer; /* 8.5.4.3. */ + + /* Ports. */ + struct stp_port ports[STP_MAX_PORTS]; + + /* Interface to client. */ + struct stp_port *first_changed_port; + void (*send_bpdu)(struct ofpbuf *bpdu, int port_no, void *aux); + void *aux; +}; + +#define FOR_EACH_ENABLED_PORT(PORT, STP) \ + for ((PORT) = stp_next_enabled_port((STP), (STP)->ports); \ + (PORT); \ + (PORT) = stp_next_enabled_port((STP), (PORT) + 1)) +static struct stp_port * +stp_next_enabled_port(const struct stp *stp, const struct stp_port *port) +{ + for (; port < &stp->ports[ARRAY_SIZE(stp->ports)]; port++) { + if (port->state != STP_DISABLED) { + return (struct stp_port *) port; + } + } + return NULL; +} + +#define MESSAGE_AGE_INCREMENT 1 + +static void stp_transmit_config(struct stp_port *); +static bool stp_supersedes_port_info(const struct stp_port *, + const struct stp_config_bpdu *); +static void stp_record_config_information(struct stp_port *, + const struct stp_config_bpdu *); +static void stp_record_config_timeout_values(struct stp *, + const struct stp_config_bpdu *); +static bool stp_is_designated_port(const struct stp_port *); +static void stp_config_bpdu_generation(struct stp *); +static void stp_transmit_tcn(struct stp *); +static void stp_configuration_update(struct stp *); +static bool stp_supersedes_root(const struct stp_port *root, + const struct stp_port *); +static void stp_root_selection(struct stp *); +static void stp_designated_port_selection(struct stp *); +static void stp_become_designated_port(struct stp_port *); +static void stp_port_state_selection(struct stp *); +static void stp_make_forwarding(struct stp_port *); +static void stp_make_blocking(struct stp_port *); +static void stp_set_port_state(struct stp_port *, enum stp_state); +static void stp_topology_change_detection(struct stp *); +static void stp_topology_change_acknowledged(struct stp *); +static void stp_acknowledge_topology_change(struct stp_port *); +static void stp_received_config_bpdu(struct stp *, struct stp_port *, + const struct stp_config_bpdu *); +static void stp_received_tcn_bpdu(struct stp *, struct stp_port *); +static void stp_hello_timer_expiry(struct stp *); +static void stp_message_age_timer_expiry(struct stp_port *); +static bool stp_is_designated_for_some_port(const struct stp *); +static void stp_forward_delay_timer_expiry(struct stp_port *); +static void stp_tcn_timer_expiry(struct stp *); +static void stp_topology_change_timer_expiry(struct stp *); +static void stp_hold_timer_expiry(struct stp_port *); +static void stp_initialize_port(struct stp_port *, enum stp_state); +static void stp_become_root_bridge(struct stp *); +static void stp_update_bridge_timers(struct stp *); + +static int clamp(int x, int min, int max); +static int ms_to_timer(int ms); +static int ms_to_timer_remainder(int ms); +static int timer_to_ms(int timer); +static void stp_start_timer(struct stp_timer *, int value); +static void stp_stop_timer(struct stp_timer *); +static bool stp_timer_expired(struct stp_timer *, int elapsed, int timeout); + +static void stp_send_bpdu(struct stp_port *, const void *, size_t); + +/* Creates and returns a new STP instance that initially has no ports enabled. + * + * 'bridge_id' should be a 48-bit MAC address as returned by + * eth_addr_to_uint64(). 'bridge_id' may also have a priority value in its top + * 16 bits; if those bits are set to 0, STP_DEFAULT_BRIDGE_PRIORITY is used. + * (This priority may be changed with stp_set_bridge_priority().) + * + * When the bridge needs to send out a BPDU, it calls 'send_bpdu'. This + * callback may be called from stp_tick() or stp_received_bpdu(). The + * arguments to 'send_bpdu' are an STP BPDU encapsulated in 'bpdu', + * the spanning tree port number 'port_no' that should transmit the + * packet, and auxiliary data to be passed to the callback in 'aux'. + */ +struct stp * +stp_create(const char *name, stp_identifier bridge_id, + void (*send_bpdu)(struct ofpbuf *bpdu, int port_no, void *aux), + void *aux) +{ + struct stp *stp; + struct stp_port *p; + + stp = xzalloc(sizeof *stp); + stp->name = xstrdup(name); + stp->bridge_id = bridge_id; + if (!(stp->bridge_id >> 48)) { + stp->bridge_id |= (uint64_t) STP_DEFAULT_BRIDGE_PRIORITY << 48; + } + + stp->rq_max_age = 6000; + stp->rq_hello_time = 2000; + stp->rq_forward_delay = 4000; + stp_update_bridge_timers(stp); + stp->max_age = stp->bridge_max_age; + stp->hello_time = stp->bridge_hello_time; + stp->forward_delay = stp->bridge_forward_delay; + + stp->designated_root = stp->bridge_id; + stp->root_path_cost = 0; + stp->root_port = NULL; + stp->topology_change_detected = false; + stp->topology_change = false; + + stp_stop_timer(&stp->tcn_timer); + stp_stop_timer(&stp->topology_change_timer); + stp_start_timer(&stp->hello_timer, 0); + + stp->send_bpdu = send_bpdu; + stp->aux = aux; + + stp->first_changed_port = &stp->ports[ARRAY_SIZE(stp->ports)]; + for (p = stp->ports; p < &stp->ports[ARRAY_SIZE(stp->ports)]; p++) { + p->stp = stp; + p->port_id = (stp_port_no(p) + 1) | (STP_DEFAULT_PORT_PRIORITY << 8); + p->path_cost = 19; /* Recommended default for 100 Mb/s link. */ + stp_initialize_port(p, STP_DISABLED); + } + return stp; +} + +/* Destroys 'stp'. */ +void +stp_destroy(struct stp *stp) +{ + if (stp) { + free(stp->name); + free(stp); + } +} + +/* Runs 'stp' given that 'ms' milliseconds have passed. */ +void +stp_tick(struct stp *stp, int ms) +{ + struct stp_port *p; + int elapsed; + + /* Convert 'ms' to STP timer ticks. Preserve any leftover milliseconds + * from previous stp_tick() calls so that we don't lose STP ticks when we + * are called too frequently. */ + ms = clamp(ms, 0, INT_MAX - 1000) + stp->elapsed_remainder; + elapsed = ms_to_timer(ms); + stp->elapsed_remainder = ms_to_timer_remainder(ms); + if (!elapsed) { + return; + } + + if (stp_timer_expired(&stp->hello_timer, elapsed, stp->hello_time)) { + stp_hello_timer_expiry(stp); + } + if (stp_timer_expired(&stp->tcn_timer, elapsed, stp->bridge_hello_time)) { + stp_tcn_timer_expiry(stp); + } + if (stp_timer_expired(&stp->topology_change_timer, elapsed, + stp->max_age + stp->forward_delay)) { + stp_topology_change_timer_expiry(stp); + } + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_timer_expired(&p->message_age_timer, elapsed, stp->max_age)) { + stp_message_age_timer_expiry(p); + } + } + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_timer_expired(&p->forward_delay_timer, elapsed, + stp->forward_delay)) { + stp_forward_delay_timer_expiry(p); + } + if (stp_timer_expired(&p->hold_timer, elapsed, ms_to_timer(1000))) { + stp_hold_timer_expiry(p); + } + } +} + +static void +set_bridge_id(struct stp *stp, stp_identifier new_bridge_id) +{ + if (new_bridge_id != stp->bridge_id) { + bool root; + struct stp_port *p; + + root = stp_is_root_bridge(stp); + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_is_designated_port(p)) { + p->designated_bridge = new_bridge_id; + } + } + stp->bridge_id = new_bridge_id; + stp_configuration_update(stp); + stp_port_state_selection(stp); + if (stp_is_root_bridge(stp) && !root) { + stp_become_root_bridge(stp); + } + } +} + +void +stp_set_bridge_id(struct stp *stp, stp_identifier bridge_id) +{ + const uint64_t mac_bits = (UINT64_C(1) << 48) - 1; + const uint64_t pri_bits = ~mac_bits; + set_bridge_id(stp, (stp->bridge_id & pri_bits) | (bridge_id & mac_bits)); +} + +void +stp_set_bridge_priority(struct stp *stp, uint16_t new_priority) +{ + const uint64_t mac_bits = (UINT64_C(1) << 48) - 1; + set_bridge_id(stp, ((stp->bridge_id & mac_bits) + | ((uint64_t) new_priority << 48))); +} + +/* Sets the desired hello time for 'stp' to 'ms', in milliseconds. The actual + * hello time is clamped to the range of 1 to 10 seconds and subject to the + * relationship (bridge_max_age >= 2 * (bridge_hello_time + 1 s)). The bridge + * hello time is only used when 'stp' is the root bridge. */ +void +stp_set_hello_time(struct stp *stp, int ms) +{ + stp->rq_hello_time = ms; + stp_update_bridge_timers(stp); +} + +/* Sets the desired max age for 'stp' to 'ms', in milliseconds. The actual max + * age is clamped to the range of 6 to 40 seconds and subject to the + * relationships (2 * (bridge_forward_delay - 1 s) >= bridge_max_age) and + * (bridge_max_age >= 2 * (bridge_hello_time + 1 s)). The bridge max age is + * only used when 'stp' is the root bridge. */ +void +stp_set_max_age(struct stp *stp, int ms) +{ + stp->rq_max_age = ms; + stp_update_bridge_timers(stp); +} + +/* Sets the desired forward delay for 'stp' to 'ms', in milliseconds. The + * actual forward delay is clamped to the range of 4 to 30 seconds and subject + * to the relationship (2 * (bridge_forward_delay - 1 s) >= bridge_max_age). + * The bridge forward delay is only used when 'stp' is the root bridge. */ +void +stp_set_forward_delay(struct stp *stp, int ms) +{ + stp->rq_forward_delay = ms; + stp_update_bridge_timers(stp); +} + +/* Returns the name given to 'stp' in the call to stp_create(). */ +const char * +stp_get_name(const struct stp *stp) +{ + return stp->name; +} + +/* Returns the bridge ID for 'stp'. */ +stp_identifier +stp_get_bridge_id(const struct stp *stp) +{ + return stp->bridge_id; +} + +/* Returns the bridge ID of the bridge currently believed to be the root. */ +stp_identifier +stp_get_designated_root(const struct stp *stp) +{ + return stp->designated_root; +} + +/* Returns true if 'stp' believes itself to the be root of the spanning tree, + * false otherwise. */ +bool +stp_is_root_bridge(const struct stp *stp) +{ + return stp->bridge_id == stp->designated_root; +} + +/* Returns the cost of the path from 'stp' to the root of the spanning tree. */ +int +stp_get_root_path_cost(const struct stp *stp) +{ + return stp->root_path_cost; +} + +/* Returns the bridge hello time, in ms. The returned value is not necessarily + * the value passed to stp_set_hello_time(): it is clamped to the valid range + * and quantized to the STP timer resolution. */ +int +stp_get_hello_time(const struct stp *stp) +{ + return timer_to_ms(stp->bridge_hello_time); +} + +/* Returns the bridge max age, in ms. The returned value is not necessarily + * the value passed to stp_set_max_age(): it is clamped to the valid range, + * quantized to the STP timer resolution, and adjusted to match the constraints + * due to the hello time. */ +int +stp_get_max_age(const struct stp *stp) +{ + return timer_to_ms(stp->bridge_max_age); +} + +/* Returns the bridge forward delay, in ms. The returned value is not + * necessarily the value passed to stp_set_forward_delay(): it is clamped to + * the valid range, quantized to the STP timer resolution, and adjusted to + * match the constraints due to the forward delay. */ +int +stp_get_forward_delay(const struct stp *stp) +{ + return timer_to_ms(stp->bridge_forward_delay); +} + +/* Returns the port in 'stp' with index 'port_no', which must be between 0 and + * STP_MAX_PORTS. */ +struct stp_port * +stp_get_port(struct stp *stp, int port_no) +{ + assert(port_no >= 0 && port_no < ARRAY_SIZE(stp->ports)); + return &stp->ports[port_no]; +} + +/* Returns the port connecting 'stp' to the root bridge, or a null pointer if + * there is no such port. */ +struct stp_port * +stp_get_root_port(struct stp *stp) +{ + return stp->root_port; +} + +/* Finds a port whose state has changed. If successful, stores the port whose + * state changed in '*portp' and returns true. If no port has changed, stores + * NULL in '*portp' and returns false. */ +bool +stp_get_changed_port(struct stp *stp, struct stp_port **portp) +{ + struct stp_port *end = &stp->ports[ARRAY_SIZE(stp->ports)]; + struct stp_port *p; + + for (p = stp->first_changed_port; p < end; p++) { + if (p->state_changed) { + p->state_changed = false; + stp->first_changed_port = p + 1; + *portp = p; + return true; + } + } + stp->first_changed_port = end; + *portp = NULL; + return false; +} + +/* Returns the name for the given 'state' (for use in debugging and log + * messages). */ +const char * +stp_state_name(enum stp_state state) +{ + switch (state) { + case STP_DISABLED: + return "disabled"; + case STP_LISTENING: + return "listening"; + case STP_LEARNING: + return "learning"; + case STP_FORWARDING: + return "forwarding"; + case STP_BLOCKING: + return "blocking"; + default: + NOT_REACHED(); + } +} + +/* Returns true if 'state' is one in which packets received on a port should + * be forwarded, false otherwise. + * + * Returns true if 'state' is STP_DISABLED, since presumably in that case the + * port should still work, just not have STP applied to it. */ +bool +stp_forward_in_state(enum stp_state state) +{ + return (state & (STP_DISABLED | STP_FORWARDING)) != 0; +} + +/* Returns true if 'state' is one in which MAC learning should be done on + * packets received on a port, false otherwise. + * + * Returns true if 'state' is STP_DISABLED, since presumably in that case the + * port should still work, just not have STP applied to it. */ +bool +stp_learn_in_state(enum stp_state state) +{ + return (state & (STP_DISABLED | STP_LEARNING | STP_FORWARDING)) != 0; +} + +/* Notifies the STP entity that bridge protocol data unit 'bpdu', which is + * 'bpdu_size' bytes in length, was received on port 'p'. + * + * This function may call the 'send_bpdu' function provided to stp_create(). */ +void +stp_received_bpdu(struct stp_port *p, const void *bpdu, size_t bpdu_size) +{ + struct stp *stp = p->stp; + const struct stp_bpdu_header *header; + + if (p->state == STP_DISABLED) { + return; + } + + if (bpdu_size < sizeof(struct stp_bpdu_header)) { + VLOG_WARN("%s: received runt %zu-byte BPDU", stp->name, bpdu_size); + return; + } + + header = bpdu; + if (header->protocol_id != htons(STP_PROTOCOL_ID)) { + VLOG_WARN("%s: received BPDU with unexpected protocol ID %"PRIu16, + stp->name, ntohs(header->protocol_id)); + return; + } + if (header->protocol_version != STP_PROTOCOL_VERSION) { + VLOG_DBG("%s: received BPDU with unexpected protocol version %"PRIu8, + stp->name, header->protocol_version); + } + + switch (header->bpdu_type) { + case STP_TYPE_CONFIG: + if (bpdu_size < sizeof(struct stp_config_bpdu)) { + VLOG_WARN("%s: received config BPDU with invalid size %zu", + stp->name, bpdu_size); + return; + } + stp_received_config_bpdu(stp, p, bpdu); + break; + + case STP_TYPE_TCN: + if (bpdu_size != sizeof(struct stp_tcn_bpdu)) { + VLOG_WARN("%s: received TCN BPDU with invalid size %zu", + stp->name, bpdu_size); + return; + } + stp_received_tcn_bpdu(stp, p); + break; + + default: + VLOG_WARN("%s: received BPDU of unexpected type %"PRIu8, + stp->name, header->bpdu_type); + return; + } +} + +/* Returns the STP entity in which 'p' is nested. */ +struct stp * +stp_port_get_stp(struct stp_port *p) +{ + return p->stp; +} + +/* Returns the index of port 'p' within its bridge. */ +int +stp_port_no(const struct stp_port *p) +{ + struct stp *stp = p->stp; + assert(p >= stp->ports && p < &stp->ports[ARRAY_SIZE(stp->ports)]); + return p - stp->ports; +} + +/* Returns the state of port 'p'. */ +enum stp_state +stp_port_get_state(const struct stp_port *p) +{ + return p->state; +} + +/* Disables STP on port 'p'. */ +void +stp_port_disable(struct stp_port *p) +{ + struct stp *stp = p->stp; + if (p->state != STP_DISABLED) { + bool root = stp_is_root_bridge(stp); + stp_become_designated_port(p); + stp_set_port_state(p, STP_DISABLED); + p->topology_change_ack = false; + p->config_pending = false; + stp_stop_timer(&p->message_age_timer); + stp_stop_timer(&p->forward_delay_timer); + stp_configuration_update(stp); + stp_port_state_selection(stp); + if (stp_is_root_bridge(stp) && !root) { + stp_become_root_bridge(stp); + } + } +} + +/* Enables STP on port 'p'. The port will initially be in "blocking" state. */ +void +stp_port_enable(struct stp_port *p) +{ + if (p->state == STP_DISABLED) { + stp_initialize_port(p, STP_BLOCKING); + stp_port_state_selection(p->stp); + } +} + +/* Sets the priority of port 'p' to 'new_priority'. Lower numerical values + * are interpreted as higher priorities. */ +void +stp_port_set_priority(struct stp_port *p, uint8_t new_priority) +{ + uint16_t new_port_id = (p->port_id & 0xff) | (new_priority << 8); + if (p->port_id != new_port_id) { + struct stp *stp = p->stp; + if (stp_is_designated_port(p)) { + p->designated_port = new_port_id; + } + p->port_id = new_port_id; + if (stp->bridge_id == p->designated_bridge + && p->port_id < p->designated_port) { + stp_become_designated_port(p); + stp_port_state_selection(stp); + } + } +} + +/* Sets the path cost of port 'p' to 'path_cost'. Lower values are generally + * used to indicate faster links. Use stp_port_set_speed() to automatically + * generate a default path cost from a link speed. */ +void +stp_port_set_path_cost(struct stp_port *p, uint16_t path_cost) +{ + if (p->path_cost != path_cost) { + struct stp *stp = p->stp; + p->path_cost = path_cost; + stp_configuration_update(stp); + stp_port_state_selection(stp); + } +} + +/* Sets the path cost of port 'p' based on 'speed' (measured in Mb/s). */ +void +stp_port_set_speed(struct stp_port *p, unsigned int speed) +{ + stp_port_set_path_cost(p, (speed >= 10000 ? 2 /* 10 Gb/s. */ + : speed >= 1000 ? 4 /* 1 Gb/s. */ + : speed >= 100 ? 19 /* 100 Mb/s. */ + : speed >= 16 ? 62 /* 16 Mb/s. */ + : speed >= 10 ? 100 /* 10 Mb/s. */ + : speed >= 4 ? 250 /* 4 Mb/s. */ + : 19)); /* 100 Mb/s (guess). */ +} + +/* Enables topology change detection on port 'p'. */ +void +stp_port_enable_change_detection(struct stp_port *p) +{ + p->change_detection_enabled = true; +} + +/* Disables topology change detection on port 'p'. */ +void +stp_port_disable_change_detection(struct stp_port *p) +{ + p->change_detection_enabled = false; +} + +static void +stp_transmit_config(struct stp_port *p) +{ + struct stp *stp = p->stp; + bool root = stp_is_root_bridge(stp); + if (!root && !stp->root_port) { + return; + } + if (p->hold_timer.active) { + p->config_pending = true; + } else { + struct stp_config_bpdu config; + memset(&config, 0, sizeof config); + config.header.protocol_id = htons(STP_PROTOCOL_ID); + config.header.protocol_version = STP_PROTOCOL_VERSION; + config.header.bpdu_type = STP_TYPE_CONFIG; + config.flags = 0; + if (p->topology_change_ack) { + config.flags |= htons(STP_CONFIG_TOPOLOGY_CHANGE_ACK); + } + if (stp->topology_change) { + config.flags |= htons(STP_CONFIG_TOPOLOGY_CHANGE); + } + config.root_id = htonll(stp->designated_root); + config.root_path_cost = htonl(stp->root_path_cost); + config.bridge_id = htonll(stp->bridge_id); + config.port_id = htons(p->port_id); + if (root) { + config.message_age = htons(0); + } else { + config.message_age = htons(stp->root_port->message_age_timer.value + + MESSAGE_AGE_INCREMENT); + } + config.max_age = htons(stp->max_age); + config.hello_time = htons(stp->hello_time); + config.forward_delay = htons(stp->forward_delay); + if (ntohs(config.message_age) < stp->max_age) { + p->topology_change_ack = false; + p->config_pending = false; + stp_send_bpdu(p, &config, sizeof config); + stp_start_timer(&p->hold_timer, 0); + } + } +} + +static bool +stp_supersedes_port_info(const struct stp_port *p, + const struct stp_config_bpdu *config) +{ + if (ntohll(config->root_id) != p->designated_root) { + return ntohll(config->root_id) < p->designated_root; + } else if (ntohl(config->root_path_cost) != p->designated_cost) { + return ntohl(config->root_path_cost) < p->designated_cost; + } else if (ntohll(config->bridge_id) != p->designated_bridge) { + return ntohll(config->bridge_id) < p->designated_bridge; + } else { + return (ntohll(config->bridge_id) != p->stp->bridge_id + || ntohs(config->port_id) <= p->designated_port); + } +} + +static void +stp_record_config_information(struct stp_port *p, + const struct stp_config_bpdu *config) +{ + p->designated_root = ntohll(config->root_id); + p->designated_cost = ntohl(config->root_path_cost); + p->designated_bridge = ntohll(config->bridge_id); + p->designated_port = ntohs(config->port_id); + stp_start_timer(&p->message_age_timer, ntohs(config->message_age)); +} + +static void +stp_record_config_timeout_values(struct stp *stp, + const struct stp_config_bpdu *config) +{ + stp->max_age = ntohs(config->max_age); + stp->hello_time = ntohs(config->hello_time); + stp->forward_delay = ntohs(config->forward_delay); + stp->topology_change = config->flags & htons(STP_CONFIG_TOPOLOGY_CHANGE); +} + +static bool +stp_is_designated_port(const struct stp_port *p) +{ + return (p->designated_bridge == p->stp->bridge_id + && p->designated_port == p->port_id); +} + +static void +stp_config_bpdu_generation(struct stp *stp) +{ + struct stp_port *p; + + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_is_designated_port(p)) { + stp_transmit_config(p); + } + } +} + +static void +stp_transmit_tcn(struct stp *stp) +{ + struct stp_port *p = stp->root_port; + struct stp_tcn_bpdu tcn_bpdu; + if (!p) { + return; + } + tcn_bpdu.header.protocol_id = htons(STP_PROTOCOL_ID); + tcn_bpdu.header.protocol_version = STP_PROTOCOL_VERSION; + tcn_bpdu.header.bpdu_type = STP_TYPE_TCN; + stp_send_bpdu(p, &tcn_bpdu, sizeof tcn_bpdu); +} + +static void +stp_configuration_update(struct stp *stp) +{ + stp_root_selection(stp); + stp_designated_port_selection(stp); +} + +static bool +stp_supersedes_root(const struct stp_port *root, const struct stp_port *p) +{ + int p_cost = p->designated_cost + p->path_cost; + int root_cost = root->designated_cost + root->path_cost; + + if (p->designated_root != root->designated_root) { + return p->designated_root < root->designated_root; + } else if (p_cost != root_cost) { + return p_cost < root_cost; + } else if (p->designated_bridge != root->designated_bridge) { + return p->designated_bridge < root->designated_bridge; + } else if (p->designated_port != root->designated_port) { + return p->designated_port < root->designated_port; + } else { + return p->port_id < root->port_id; + } +} + +static void +stp_root_selection(struct stp *stp) +{ + struct stp_port *p, *root; + + root = NULL; + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_is_designated_port(p) + || p->designated_root >= stp->bridge_id) { + continue; + } + if (root && !stp_supersedes_root(root, p)) { + continue; + } + root = p; + } + stp->root_port = root; + if (!root) { + stp->designated_root = stp->bridge_id; + stp->root_path_cost = 0; + } else { + stp->designated_root = root->designated_root; + stp->root_path_cost = root->designated_cost + root->path_cost; + } +} + +static void +stp_designated_port_selection(struct stp *stp) +{ + struct stp_port *p; + + FOR_EACH_ENABLED_PORT (p, stp) { + if (stp_is_designated_port(p) + || p->designated_root != stp->designated_root + || stp->root_path_cost < p->designated_cost + || (stp->root_path_cost == p->designated_cost + && (stp->bridge_id < p->designated_bridge + || (stp->bridge_id == p->designated_bridge + && p->port_id <= p->designated_port)))) + { + stp_become_designated_port(p); + } + } +} + +static void +stp_become_designated_port(struct stp_port *p) +{ + struct stp *stp = p->stp; + p->designated_root = stp->designated_root; + p->designated_cost = stp->root_path_cost; + p->designated_bridge = stp->bridge_id; + p->designated_port = p->port_id; +} + +static void +stp_port_state_selection(struct stp *stp) +{ + struct stp_port *p; + + FOR_EACH_ENABLED_PORT (p, stp) { + if (p == stp->root_port) { + p->config_pending = false; + p->topology_change_ack = false; + stp_make_forwarding(p); + } else if (stp_is_designated_port(p)) { + stp_stop_timer(&p->message_age_timer); + stp_make_forwarding(p); + } else { + p->config_pending = false; + p->topology_change_ack = false; + stp_make_blocking(p); + } + } +} + +static void +stp_make_forwarding(struct stp_port *p) +{ + if (p->state == STP_BLOCKING) { + stp_set_port_state(p, STP_LISTENING); + stp_start_timer(&p->forward_delay_timer, 0); + } +} + +static void +stp_make_blocking(struct stp_port *p) +{ + if (!(p->state & (STP_DISABLED | STP_BLOCKING))) { + if (p->state & (STP_FORWARDING | STP_LEARNING)) { + if (p->change_detection_enabled) { + stp_topology_change_detection(p->stp); + } + } + stp_set_port_state(p, STP_BLOCKING); + stp_stop_timer(&p->forward_delay_timer); + } +} + +static void +stp_set_port_state(struct stp_port *p, enum stp_state state) +{ + if (state != p->state && !p->state_changed) { + p->state_changed = true; + if (p < p->stp->first_changed_port) { + p->stp->first_changed_port = p; + } + } + p->state = state; +} + +static void +stp_topology_change_detection(struct stp *stp) +{ + if (stp_is_root_bridge(stp)) { + stp->topology_change = true; + stp_start_timer(&stp->topology_change_timer, 0); + } else if (!stp->topology_change_detected) { + stp_transmit_tcn(stp); + stp_start_timer(&stp->tcn_timer, 0); + } + stp->topology_change_detected = true; +} + +static void +stp_topology_change_acknowledged(struct stp *stp) +{ + stp->topology_change_detected = false; + stp_stop_timer(&stp->tcn_timer); +} + +static void +stp_acknowledge_topology_change(struct stp_port *p) +{ + p->topology_change_ack = true; + stp_transmit_config(p); +} + +static void +stp_received_config_bpdu(struct stp *stp, struct stp_port *p, + const struct stp_config_bpdu *config) +{ + if (ntohs(config->message_age) >= ntohs(config->max_age)) { + VLOG_WARN("%s: received config BPDU with message age (%u) greater " + "than max age (%u)", + stp->name, + ntohs(config->message_age), ntohs(config->max_age)); + return; + } + if (p->state != STP_DISABLED) { + bool root = stp_is_root_bridge(stp); + if (stp_supersedes_port_info(p, config)) { + stp_record_config_information(p, config); + stp_configuration_update(stp); + stp_port_state_selection(stp); + if (!stp_is_root_bridge(stp) && root) { + stp_stop_timer(&stp->hello_timer); + if (stp->topology_change_detected) { + stp_stop_timer(&stp->topology_change_timer); + stp_transmit_tcn(stp); + stp_start_timer(&stp->tcn_timer, 0); + } + } + if (p == stp->root_port) { + stp_record_config_timeout_values(stp, config); + stp_config_bpdu_generation(stp); + if (config->flags & htons(STP_CONFIG_TOPOLOGY_CHANGE_ACK)) { + stp_topology_change_acknowledged(stp); + } + } + } else if (stp_is_designated_port(p)) { + stp_transmit_config(p); + } + } +} + +static void +stp_received_tcn_bpdu(struct stp *stp, struct stp_port *p) +{ + if (p->state != STP_DISABLED) { + if (stp_is_designated_port(p)) { + stp_topology_change_detection(stp); + stp_acknowledge_topology_change(p); + } + } +} + +static void +stp_hello_timer_expiry(struct stp *stp) +{ + stp_config_bpdu_generation(stp); + stp_start_timer(&stp->hello_timer, 0); +} + +static void +stp_message_age_timer_expiry(struct stp_port *p) +{ + struct stp *stp = p->stp; + bool root = stp_is_root_bridge(stp); + stp_become_designated_port(p); + stp_configuration_update(stp); + stp_port_state_selection(stp); + if (stp_is_root_bridge(stp) && !root) { + stp->max_age = stp->bridge_max_age; + stp->hello_time = stp->bridge_hello_time; + stp->forward_delay = stp->bridge_forward_delay; + stp_topology_change_detection(stp); + stp_stop_timer(&stp->tcn_timer); + stp_config_bpdu_generation(stp); + stp_start_timer(&stp->hello_timer, 0); + } +} + +static bool +stp_is_designated_for_some_port(const struct stp *stp) +{ + const struct stp_port *p; + + FOR_EACH_ENABLED_PORT (p, stp) { + if (p->designated_bridge == stp->bridge_id) { + return true; + } + } + return false; +} + +static void +stp_forward_delay_timer_expiry(struct stp_port *p) +{ + if (p->state == STP_LISTENING) { + stp_set_port_state(p, STP_LEARNING); + stp_start_timer(&p->forward_delay_timer, 0); + } else if (p->state == STP_LEARNING) { + stp_set_port_state(p, STP_FORWARDING); + if (stp_is_designated_for_some_port(p->stp)) { + if (p->change_detection_enabled) { + stp_topology_change_detection(p->stp); + } + } + } +} + +static void +stp_tcn_timer_expiry(struct stp *stp) +{ + stp_transmit_tcn(stp); + stp_start_timer(&stp->tcn_timer, 0); +} + +static void +stp_topology_change_timer_expiry(struct stp *stp) +{ + stp->topology_change_detected = false; + stp->topology_change = false; +} + +static void +stp_hold_timer_expiry(struct stp_port *p) +{ + if (p->config_pending) { + stp_transmit_config(p); + } +} + +static void +stp_initialize_port(struct stp_port *p, enum stp_state state) +{ + assert(state & (STP_DISABLED | STP_BLOCKING)); + stp_become_designated_port(p); + stp_set_port_state(p, state); + p->topology_change_ack = false; + p->config_pending = false; + p->change_detection_enabled = true; + stp_stop_timer(&p->message_age_timer); + stp_stop_timer(&p->forward_delay_timer); + stp_stop_timer(&p->hold_timer); +} + +static void +stp_become_root_bridge(struct stp *stp) +{ + stp->max_age = stp->bridge_max_age; + stp->hello_time = stp->bridge_hello_time; + stp->forward_delay = stp->bridge_forward_delay; + stp_topology_change_detection(stp); + stp_stop_timer(&stp->tcn_timer); + stp_config_bpdu_generation(stp); + stp_start_timer(&stp->hello_timer, 0); +} + +static void +stp_start_timer(struct stp_timer *timer, int value) +{ + timer->value = value; + timer->active = true; +} + +static void +stp_stop_timer(struct stp_timer *timer) +{ + timer->active = false; +} + +static bool +stp_timer_expired(struct stp_timer *timer, int elapsed, int timeout) +{ + if (timer->active) { + timer->value += elapsed; + if (timer->value >= timeout) { + timer->active = false; + return true; + } + } + return false; +} + +/* Returns the number of whole STP timer ticks in 'ms' milliseconds. There + * are 256 STP timer ticks per second. */ +static int +ms_to_timer(int ms) +{ + return ms * 0x100 / 1000; +} + +/* Returns the number of leftover milliseconds when 'ms' is converted to STP + * timer ticks. */ +static int +ms_to_timer_remainder(int ms) +{ + return ms * 0x100 % 1000; +} + +/* Returns the number of whole milliseconds in 'timer' STP timer ticks. There + * are 256 STP timer ticks per second. */ +static int +timer_to_ms(int timer) +{ + return timer * 1000 / 0x100; +} + +static int +clamp(int x, int min, int max) +{ + return x < min ? min : x > max ? max : x; +} + +static void +stp_update_bridge_timers(struct stp *stp) +{ + int ht, ma, fd; + + ht = clamp(stp->rq_hello_time, 1000, 10000); + ma = clamp(stp->rq_max_age, MAX(2 * (ht + 1000), 6000), 40000); + fd = clamp(stp->rq_forward_delay, ma / 2 + 1000, 30000); + + stp->bridge_hello_time = ms_to_timer(ht); + stp->bridge_max_age = ms_to_timer(ma); + stp->bridge_forward_delay = ms_to_timer(fd); + + if (stp_is_root_bridge(stp)) { + stp->max_age = stp->bridge_max_age; + stp->hello_time = stp->bridge_hello_time; + stp->forward_delay = stp->bridge_forward_delay; + } +} + +static void +stp_send_bpdu(struct stp_port *p, const void *bpdu, size_t bpdu_size) +{ + struct eth_header *eth; + struct llc_header *llc; + struct ofpbuf *pkt; + + /* Skeleton. */ + pkt = ofpbuf_new(ETH_HEADER_LEN + LLC_HEADER_LEN + bpdu_size); + pkt->l2 = eth = ofpbuf_put_zeros(pkt, sizeof *eth); + llc = ofpbuf_put_zeros(pkt, sizeof *llc); + pkt->l3 = ofpbuf_put(pkt, bpdu, bpdu_size); + + /* 802.2 header. */ + memcpy(eth->eth_dst, eth_addr_stp, ETH_ADDR_LEN); + /* p->stp->send_bpdu() must fill in source address. */ + eth->eth_type = htons(pkt->size - ETH_HEADER_LEN); + + /* LLC header. */ + llc->llc_dsap = STP_LLC_DSAP; + llc->llc_ssap = STP_LLC_SSAP; + llc->llc_cntl = STP_LLC_CNTL; + + p->stp->send_bpdu(pkt, stp_port_no(p), p->stp->aux); +} diff --git a/lib/stp.h b/lib/stp.h new file mode 100644 index 00000000..7eb2cb96 --- /dev/null +++ b/lib/stp.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2008, 2011 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STP_H +#define STP_H 1 + +/* This is an implementation of Spanning Tree Protocol as described in IEEE + * 802.1D-1998, clauses 8 and 9. Section numbers refer to this standard. */ + +#include +#include +#include "compiler.h" +#include "util.h" + +struct ofpbuf; + +/* LLC field values used for STP frames. */ +#define STP_LLC_SSAP 0x42 +#define STP_LLC_DSAP 0x42 +#define STP_LLC_CNTL 0x03 + +/* Bridge and port priorities that should be used by default. */ +#define STP_DEFAULT_BRIDGE_PRIORITY 32768 +#define STP_DEFAULT_PORT_PRIORITY 128 + +/* Bridge identifier. Top 16 bits are a priority value (numerically lower + * values are higher priorities). Bottom 48 bits are MAC address of bridge. */ +typedef uint64_t stp_identifier; + +/* Basic STP functionality. */ +#define STP_MAX_PORTS 255 +struct stp *stp_create(const char *name, stp_identifier bridge_id, + void (*send_bpdu)(struct ofpbuf *bpdu, int port_no, + void *aux), + void *aux); +void stp_destroy(struct stp *); +void stp_tick(struct stp *, int ms); +void stp_set_bridge_id(struct stp *, stp_identifier bridge_id); +void stp_set_bridge_priority(struct stp *, uint16_t new_priority); +void stp_set_hello_time(struct stp *, int ms); +void stp_set_max_age(struct stp *, int ms); +void stp_set_forward_delay(struct stp *, int ms); + +/* STP properties. */ +const char *stp_get_name(const struct stp *); +stp_identifier stp_get_bridge_id(const struct stp *); +stp_identifier stp_get_designated_root(const struct stp *); +bool stp_is_root_bridge(const struct stp *); +int stp_get_root_path_cost(const struct stp *); +int stp_get_hello_time(const struct stp *); +int stp_get_max_age(const struct stp *); +int stp_get_forward_delay(const struct stp *); + +/* Obtaining STP ports. */ +struct stp_port *stp_get_port(struct stp *, int port_no); +struct stp_port *stp_get_root_port(struct stp *); +bool stp_get_changed_port(struct stp *, struct stp_port **portp); + +/* State of an STP port. + * + * A port is in exactly one state at any given time, but distinct bits are used + * for states to allow testing for more than one state with a bit mask. */ +enum stp_state { + STP_DISABLED = 1 << 0, /* 8.4.5: Disabled by management. */ + STP_LISTENING = 1 << 1, /* 8.4.2: Not learning or relaying frames. */ + STP_LEARNING = 1 << 2, /* 8.4.3: Learning but not relaying frames. */ + STP_FORWARDING = 1 << 3, /* 8.4.4: Learning and relaying frames. */ + STP_BLOCKING = 1 << 4 /* 8.4.1: Initial boot state. */ +}; +const char *stp_state_name(enum stp_state); +bool stp_forward_in_state(enum stp_state); +bool stp_learn_in_state(enum stp_state); + +void stp_received_bpdu(struct stp_port *, const void *bpdu, size_t bpdu_size); + +struct stp *stp_port_get_stp(struct stp_port *); +int stp_port_no(const struct stp_port *); +enum stp_state stp_port_get_state(const struct stp_port *); +void stp_port_enable(struct stp_port *); +void stp_port_disable(struct stp_port *); +void stp_port_set_priority(struct stp_port *, uint8_t new_priority); +void stp_port_set_path_cost(struct stp_port *, uint16_t path_cost); +void stp_port_set_speed(struct stp_port *, unsigned int speed); +void stp_port_enable_change_detection(struct stp_port *); +void stp_port_disable_change_detection(struct stp_port *); + +#endif /* stp.h */ diff --git a/tests/.gitignore b/tests/.gitignore index 1454dac4..11248a62 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -29,9 +29,10 @@ /test-packets /test-random /test-reconnect +/test-sha1 +/test-stp /test-strtok_r /test-timeval -/test-sha1 /test-type-props /test-unix-socket /test-util diff --git a/tests/automake.mk b/tests/automake.mk index dcd7f97a..455fc8bb 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -53,6 +53,7 @@ TESTSUITE_AT = \ tests/ovs-vsctl.at \ tests/ovs-monitor-ipsec.at \ tests/ovs-xapi-sync.at \ + tests/stp.at \ tests/interface-reconfigure.at \ tests/vlog.at TESTSUITE = $(srcdir)/tests/testsuite @@ -92,6 +93,7 @@ lcov_wrappers = \ tests/lcov/test-random \ tests/lcov/test-reconnect \ tests/lcov/test-sha1 \ + tests/lcov/test-stp \ tests/lcov/test-timeval \ tests/lcov/test-type-props \ tests/lcov/test-unix-socket \ @@ -147,6 +149,7 @@ valgrind_wrappers = \ tests/valgrind/test-random \ tests/valgrind/test-reconnect \ tests/valgrind/test-sha1 \ + tests/valgrind/test-stp \ tests/valgrind/test-timeval \ tests/valgrind/test-type-props \ tests/valgrind/test-unix-socket \ @@ -256,7 +259,6 @@ tests_test_openflowd_LDADD = \ lib/libopenvswitch.a \ $(SSL_LIBS) - noinst_PROGRAMS += tests/test-packets tests_test_packets_SOURCES = tests/test-packets.c tests_test_packets_LDADD = lib/libopenvswitch.a @@ -265,6 +267,10 @@ noinst_PROGRAMS += tests/test-random tests_test_random_SOURCES = tests/test-random.c tests_test_random_LDADD = lib/libopenvswitch.a +noinst_PROGRAMS += tests/test-stp +tests_test_stp_SOURCES = tests/test-stp.c +tests_test_stp_LDADD = lib/libopenvswitch.a + noinst_PROGRAMS += tests/test-unix-socket tests_test_unix_socket_SOURCES = tests/test-unix-socket.c tests_test_unix_socket_LDADD = lib/libopenvswitch.a diff --git a/tests/stp.at b/tests/stp.at new file mode 100644 index 00000000..ba19837e --- /dev/null +++ b/tests/stp.at @@ -0,0 +1,302 @@ +AT_BANNER([Spanning Tree Protocol unit tests]) + +AT_SETUP([STP example from IEEE 802.1D-1998]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-ieee802.1d-1998], +[bridge 0 0x42 = a b +bridge 1 0x97 = c:5 a d:5 +bridge 2 0x45 = b e +bridge 3 0x57 = b:5 e:5 +bridge 4 0x83 = a:5 e:5 +run 1000 +check 0 = root +check 1 = F F:10 F +check 2 = F:10 B +check 3 = F:5 F +check 4 = F:5 B +]) +AT_CHECK([test-stp test-stp-ieee802.1d-1998]) +AT_CLEANUP + +AT_SETUP([STP example from IEEE 802.1D-2004 figures 17.4 and 17.5]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-ieee802.1d-2004-fig17.4], +[bridge 0 0x111 = a b e c +bridge 1 0x222 = a b d f +bridge 2 0x333 = c d l j h g +bridge 3 0x444 = e f n m k i +bridge 4 0x555 = g i 0 0 +bridge 5 0x666 = h k 0 0 +bridge 6 0x777 = j m 0 0 +bridge 7 0x888 = l n 0 0 +run 1000 +check 0 = root +check 1 = F:10 B F F +check 2 = F:10 B F F F F +check 3 = F:10 B F F F F +check 4 = F:20 B F F +check 5 = F:20 B F F +check 6 = F:20 B F F +check 7 = F:20 B F F + +# Now connect two ports of bridge 7 to the same LAN. +bridge 7 = l n o o +# Same results except for bridge 7: +run 1000 +check 0 = root +check 1 = F:10 B F F +check 2 = F:10 B F F F F +check 3 = F:10 B F F F F +check 4 = F:20 B F F +check 5 = F:20 B F F +check 6 = F:20 B F F +check 7 = F:20 B F B +]) +AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.4]) +AT_CLEANUP + +AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.6]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-ieee802.1d-2004-fig17.6], +[bridge 0 0x111 = a b l +bridge 1 0x222 = b c d +bridge 2 0x333 = d e f +bridge 3 0x444 = f g h +bridge 4 0x555 = j h i +bridge 5 0x666 = l j k +run 1000 +check 0 = root +check 1 = F:10 F F +check 2 = F:20 F F +check 3 = F:30 F B +check 4 = F:20 F F +check 5 = F:10 F F +]) +AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.6]) +AT_CLEANUP + +AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.7]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-ieee802.1d-2004-fig17.7], +[bridge 0 0xaa = b +bridge 1 0x111 = a b d f h g e c +bridge 2 0x222 = g h j l n m k i +run 1000 +check 0 = root +check 1 = F F:10 F F F F F F +check 2 = B F:20 F F F F F F + +# This is not the port priority change described in that figure, +# but I don't understand what port priority change would cause +# that change. +bridge 2 = g X j l n m k i +run 1000 +check 0 = root +check 1 = F F:10 F F F F F F +check 2 = F:20 D F F F F F F +]) +AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.7]) +AT_CLEANUP + +AT_SETUP([STP.io.1.1: Link Failure]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-io-1.1], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Interoperability Test Suite +# Version 1.5": +# +# STP.io.1.1: Link Failure +bridge 0 0x111 = a b c +bridge 1 0x222 = a b c +run 1000 +check 0 = root +check 1 = F:10 B B +bridge 1 = 0 _ _ +run 1000 +check 0 = root +check 1 = F F:10 B +bridge 1 = X _ _ +run 1000 +check 0 = root +check 1 = D F:10 B +bridge 1 = _ 0 _ +run 1000 +check 0 = root +check 1 = D F F:10 +bridge 1 = _ X _ +run 1000 +check 0 = root +check 1 = D D F:10 +]) +AT_CHECK([test-stp test-stp-iol-io-1.1]) +AT_CLEANUP + +AT_SETUP([STP.io.1.2: Repeated Network]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-io-1.2], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Interoperability Test Suite +# Version 1.5": +# STP.io.1.2: Repeated Network +bridge 0 0x111 = a a +bridge 1 0x222 = a a +run 1000 +check 0 = rootid:0x111 F B +check 1 = rootid:0x111 F:10 B +bridge 1 = a^0x90 _ +run 1000 +check 0 = rootid:0x111 F B +check 1 = rootid:0x111 B F:10 +]) +AT_CHECK([test-stp test-stp-iol-io-1.2]) +AT_CLEANUP + +AT_SETUP([STP.io.1.4: Network Initialization]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-io-1.4], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Interoperability Test Suite +# Version 1.5": +# STP.io.1.4: Network Initialization +bridge 0 0x111 = a b c +bridge 1 0x222 = b d e +bridge 2 0x333 = a d f +bridge 3 0x444 = c e f +run 1000 +check 0 = root +check 1 = F:10 F F +check 2 = F:10 B F +check 3 = F:10 B B +]) +AT_CHECK([test-stp test-stp-iol-io-1.4]) +AT_CLEANUP + +AT_SETUP([STP.io.1.5: Topology Change]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-io-1.5], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Interoperability Test Suite +# Version 1.5": +# STP.io.1.5: Topology Change +bridge 0 0x111 = a b d c +bridge 1 0x222 = a b f e +bridge 2 0x333 = c d g h +bridge 3 0x444 = e f g h +run 1000 +check 0 = root +check 1 = F:10 B F F +check 2 = B F:10 F F +check 3 = B F:20 B B +bridge 1^0x7000 +run 1000 +check 0 = F:10 B F F +check 1 = root +check 2 = B F:20 B B +check 3 = B F:10 F F +bridge 2^0x6000 +run 1000 +check 0 = F F B F:10 +check 1 = F:20 B B B +check 2 = root +check 3 = F F F:10 B +bridge 3^0x5000 +run 1000 +check 0 = B B B F:20 +check 1 = F F B F:10 +check 2 = F F F:10 B +check 3 = root +bridge 0^0x4000 +bridge 1^0x4001 +bridge 2^0x4002 +bridge 3^0x4003 +run 1000 +check 0 = root +check 1 = F:10 B F F +check 2 = B F:10 F F +check 3 = B F:20 B B +]) +AT_CHECK([test-stp test-stp-iol-io-1.5]) +AT_CLEANUP + +AT_SETUP([STP.op.1.1 and STP.op.1.2]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-op-1.1], +[# This test file approximates the following tests from "Bridge +# Functions Consortium Spanning Tree Protocol Operations Test Suite +# Version 2.3": +# Test STP.op.1.1: Root ID Initialized to Bridge ID +# Test STP.op.1.2: Root Path Cost Initialized to Zero +bridge 0 0x123 = +check 0 = root +]) +AT_CHECK([test-stp test-stp-iol-op-1.1]) +AT_CLEANUP + +AT_SETUP([STP.op.1.4: All Ports Initialized to Designated Ports]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-op-1.4], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Protocol Operations Test Suite +# Version 2.3": +# Test STP.op.1.4: All Ports Initialized to Designated Ports +bridge 0 0x123 = a b c d e f +check 0 = Li Li Li Li Li Li +run 1000 +check 0 = F F F F F F +]) +AT_CHECK([test-stp test-stp-iol-op-1.4]) +AT_CLEANUP + +AT_SETUP([STP.op.3.1: Root Bridge Selection: Root ID Values]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-op-3.1], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Protocol Operations Test Suite +# Version 2.3": +# Test STP.op.3.1: Root Bridge Selection: Root ID Values +bridge 0 0x111 = a +bridge 1 0x222 = a +check 0 = rootid:0x111 Li +check 1 = rootid:0x222 Li +run 1000 +check 0 = rootid:0x111 root +check 1 = rootid:0x111 F:10 +]) +AT_CHECK([test-stp test-stp-iol-op-3.1]) +AT_CLEANUP + +AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-op-3.3], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Protocol Operations Test Suite +# Version 2.3": +# Test STP.op.3.3: Root Bridge Selection: Bridge ID Values +bridge 0 0x333^0x6000 = a +bridge 1 0x222^0x7000 = b +bridge 2 0x111 = a b +run 1000 +check 0 = rootid:0x333^0x6000 root +check 1 = rootid:0x333^0x6000 F:20 +check 2 = rootid:0x333^0x6000 F:10 F +]) +AT_CHECK([test-stp test-stp-iol-op-3.3]) +AT_CLEANUP + +AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values]) +AT_KEYWORDS([STP]) +AT_DATA([test-stp-iol-op-3.4], +[# This test file approximates the following test from "Bridge +# Functions Consortium Spanning Tree Protocol Operations Test Suite +# Version 2.3": +# Test STP.op.3.3: Root Bridge Selection: Bridge ID Values +bridge 0 0x333^0x6000 = a +bridge 1 0x222^0x7000 = b +bridge 2 0x111 = a b +run 1000 +check 0 = rootid:0x333^0x6000 root +check 1 = rootid:0x333^0x6000 F:20 +check 2 = rootid:0x333^0x6000 F:10 F +]) +AT_CHECK([test-stp test-stp-iol-op-3.4]) +AT_CLEANUP diff --git a/tests/test-stp.c b/tests/test-stp.c new file mode 100644 index 00000000..fecada71 --- /dev/null +++ b/tests/test-stp.c @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "stp.h" +#include +#include +#include +#include +#include +#include +#include "ofpbuf.h" +#include "packets.h" + +struct bpdu { + int port_no; + void *data; + size_t size; +}; + +struct bridge { + struct test_case *tc; + int id; + bool reached; + + struct stp *stp; + + struct lan *ports[STP_MAX_PORTS]; + int n_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 = pkt->l3; + 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]; + b->tc = tc; + b->id = id; + snprintf(name, sizeof name, "stp%x", id); + b->stp = stp_create(name, id, send_bpdu, b); + assert(tc->n_bridges < ARRAY_SIZE(tc->bridges)); + b->n_ports = 0; + 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, int path_cost) +{ + int port_no = b->n_ports++; + struct stp_port *p = stp_get_port(b->stp, port_no); + assert(port_no < ARRAY_SIZE(b->ports)); + b->ports[port_no] = NULL; + stp_port_set_path_cost(p, path_cost); + stp_port_enable(p); + 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 stp *stp = b->stp; + int j; + + printf("%s:", stp_get_name(stp)); + if (stp_is_root_bridge(stp)) { + printf(" root"); + } + printf("\n"); + for (j = 0; j < b->n_ports; j++) { + struct stp_port *p = stp_get_port(stp, j); + enum stp_state state = stp_port_get_state(p); + + printf("\tport %d", j); + if (b->ports[j]) { + printf(" (lan %s)", b->ports[j]->name); + } else { + printf(" (disconnected)"); + } + printf(": %s", stp_state_name(state)); + if (p == stp_get_root_port(stp)) { + printf(" (root port, root_path_cost=%u)", stp_get_root_path_cost(stp)); + } + 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", stp_get_name(b->stp)); + for (i = 0; i < b->n_ports; i++) { + struct lan *lan = b->ports[i]; + struct stp_port *p = stp_get_port(b->stp, i); + if (stp_port_get_state(p) == STP_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 stp *stp = b->stp; + if (stp_is_root_bridge(stp)) { + dump_bridge_tree(tc, b, 0); + } + } +} + +static void +simulate(struct test_case *tc, int granularity) +{ + int time; + + for (time = 0; time < 1000 * 180; time += granularity) { + int round_trips; + int i; + + for (i = 0; i < tc->n_bridges; i++) { + stp_tick(tc->bridges[i]->stp, granularity); + } + 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]; + stp_received_bpdu(stp_get_port(b->stp, 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:%td: ", 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); + } +} + +int +main(int argc, char *argv[]) +{ + struct test_case *tc; + FILE *input_file; + int i; + + if (argc != 2) { + ovs_fatal(0, "usage: test-stp INPUT.STP\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("^")) { + stp_set_bridge_priority(bridge->stp, must_get_int()); + } + + if (match("=")) { + for (port_no = 0; port_no < STP_MAX_PORTS; port_no++) { + struct stp_port *p = stp_get_port(bridge->stp, port_no); + if (!token || match("X")) { + stp_port_disable(p); + } else if (match("_")) { + /* Nothing to do. */ + } else { + struct lan *lan; + int 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() : 10; + if (port_no < bridge->n_ports) { + stp_port_set_path_cost(p, path_cost); + stp_port_enable(p); + reconnect_port(bridge, port_no, lan); + } else if (port_no == bridge->n_ports) { + new_port(bridge, lan, path_cost); + } else { + err("ports must be numbered consecutively"); + } + if (match("^")) { + stp_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 stp *stp; + int bridge_no, port_no; + + bridge_no = must_get_int(); + if (bridge_no >= tc->n_bridges) { + err("no bridge numbered %d", bridge_no); + } + b = tc->bridges[bridge_no]; + stp = b->stp; + + 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 (stp_get_designated_root(stp) != rootid) { + warn("%s: root %"PRIx64", not %"PRIx64, + stp_get_name(stp), stp_get_designated_root(stp), + rootid); + } + } + + if (match("root")) { + if (stp_get_root_path_cost(stp)) { + warn("%s: root path cost of root is %u but should be 0", + stp_get_name(stp), stp_get_root_path_cost(stp)); + } + if (!stp_is_root_bridge(stp)) { + warn("%s: root is %"PRIx64", not %"PRIx64, + stp_get_name(stp), + stp_get_designated_root(stp), stp_get_bridge_id(stp)); + } + for (port_no = 0; port_no < b->n_ports; port_no++) { + struct stp_port *p = stp_get_port(stp, port_no); + enum stp_state state = stp_port_get_state(p); + if (!(state & (STP_DISABLED | STP_FORWARDING))) { + warn("%s: root port %d in state %s", + stp_get_name(b->stp), port_no, + stp_state_name(state)); + } + } + } else { + for (port_no = 0; port_no < STP_MAX_PORTS; port_no++) { + struct stp_port *p = stp_get_port(stp, port_no); + enum stp_state state; + if (token == NULL || match("D")) { + state = STP_DISABLED; + } else if (match("B")) { + state = STP_BLOCKING; + } else if (match("Li")) { + state = STP_LISTENING; + } else if (match("Le")) { + state = STP_LEARNING; + } else if (match("F")) { + state = STP_FORWARDING; + } else if (match("_")) { + continue; + } else { + err("unknown port state %s", token); + } + if (stp_port_get_state(p) != state) { + warn("%s port %d: state is %s but should be %s", + stp_get_name(stp), port_no, + stp_state_name(stp_port_get_state(p)), + stp_state_name(state)); + } + if (state == STP_FORWARDING) { + struct stp_port *root_port = stp_get_root_port(stp); + if (match(":")) { + int root_path_cost = must_get_int(); + if (p != root_port) { + warn("%s: port %d is not the root port", + stp_get_name(stp), port_no); + if (!root_port) { + warn("%s: (there is no root port)", + stp_get_name(stp)); + } else { + warn("%s: (port %d is the root port)", + stp_get_name(stp), + stp_port_no(root_port)); + } + } else if (root_path_cost + != stp_get_root_path_cost(stp)) { + warn("%s: root path cost is %u, should be %d", + stp_get_name(stp), + stp_get_root_path_cost(stp), + root_path_cost); + } + } else if (p == root_port) { + warn("%s: port %d is the root port but " + "not expected to be", + stp_get_name(stp), port_no); + } + } + } + } + if (n_warnings) { + exit(EXIT_FAILURE); + } + } + if (get_token()) { + err("trailing garbage on line"); + } + } + free(token); + + for (i = 0; i < tc->n_lans; i++) { + struct lan *lan = tc->lans[i]; + free((char *) lan->name); + free(lan); + } + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *bridge = tc->bridges[i]; + stp_destroy(bridge->stp); + free(bridge); + } + free(tc); + + return 0; +} diff --git a/tests/testsuite.at b/tests/testsuite.at index b6fe7c33..481742ea 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -67,4 +67,5 @@ m4_include([tests/ovs-vsctl.at]) m4_include([tests/ovs-monitor-ipsec.at]) m4_include([tests/ovs-xapi-sync.at]) m4_include([tests/interface-reconfigure.at]) +m4_include([tests/stp.at]) m4_include([tests/vlog.at])