From fd93aaef9ba99188e30731651c589a2ab9bfc421 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 19 Aug 2021 08:59:56 +0200 Subject: [PATCH] Initial import Signed-off-by: Felix Fietkau --- .gitignore | 6 + CMakeLists.txt | 17 + bridge_ctl.h | 91 + bridge_track.c | 722 +++++++ bridge_track.h | 30 + brmon.c | 224 ++ config.c | 68 + config.h | 32 + driver.h | 67 + hmac_md5.c | 101 + libnetlink.c | 675 ++++++ libnetlink.h | 67 + log.h | 88 + main.c | 135 ++ mstp.c | 5153 ++++++++++++++++++++++++++++++++++++++++++++ mstp.h | 803 +++++++ netif_utils.c | 170 ++ netif_utils.h | 43 + packet.c | 217 ++ packet.h | 34 + scripts/bridge-stp | 16 + ubus.c | 238 ++ ubus.h | 20 + worker.c | 135 ++ worker.h | 39 + 25 files changed, 9191 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 bridge_ctl.h create mode 100644 bridge_track.c create mode 100644 bridge_track.h create mode 100644 brmon.c create mode 100644 config.c create mode 100644 config.h create mode 100644 driver.h create mode 100644 hmac_md5.c create mode 100644 libnetlink.c create mode 100644 libnetlink.h create mode 100644 log.h create mode 100644 main.c create mode 100644 mstp.c create mode 100644 mstp.h create mode 100644 netif_utils.c create mode 100644 netif_utils.h create mode 100644 packet.c create mode 100644 packet.h create mode 100755 scripts/bridge-stp create mode 100644 ubus.c create mode 100644 ubus.h create mode 100644 worker.c create mode 100644 worker.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c9da2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/Makefile +CMakeCache.txt +CMakeFiles +*.cmake +install_manifest.txt +/ustpd diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bb96cab --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.12) + +PROJECT(ustp C) + +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations -Wno-error=missing-declarations -I${CMAKE_SOURCE_DIR}) + +ADD_EXECUTABLE(ustpd bridge_track.c brmon.c hmac_md5.c libnetlink.c mstp.c netif_utils.c packet.c worker.c config.c main.c ubus.c) +TARGET_LINK_LIBRARIES(ustpd ubox ubus) + +SET(CMAKE_INSTALL_PREFIX /) + +INSTALL(TARGETS ustpd + RUNTIME DESTINATION sbin +) +INSTALL(FILES scripts/bridge-stp + DESTINATION sbin +) diff --git a/bridge_ctl.h b/bridge_ctl.h new file mode 100644 index 0000000..3cd1b7f --- /dev/null +++ b/bridge_ctl.h @@ -0,0 +1,91 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ + +#ifndef BRIDGE_CTL_H +#define BRIDGE_CTL_H + +#include +#include +#include + +typedef struct +{ + int if_index; + __u8 macaddr[ETH_ALEN]; + char name[IFNAMSIZ]; + + bool up; +} sysdep_br_data_t; + +typedef struct +{ + int if_index; + __u8 macaddr[ETH_ALEN]; + char name[IFNAMSIZ]; + + bool up; + int speed, duplex; +} sysdep_if_data_t; + +#define GET_PORT_SPEED(port) ((port)->sysdeps.speed) +#define GET_PORT_DUPLEX(port) ((port)->sysdeps.duplex) + +/* Logging macros for mstp.c - they use system dependent info */ +#define ERROR_BRNAME(_br, _fmt, _args...) ERROR("%s " _fmt, \ + _br->sysdeps.name, ##_args) +#define INFO_BRNAME(_br, _fmt, _args...) INFO("%s " _fmt, \ + _br->sysdeps.name, ##_args) +#define LOG_BRNAME(_br, _fmt, _args...) LOG("%s " _fmt, \ + _br->sysdeps.name, ##_args) +#define ERROR_PRTNAME(_br, _prt, _fmt, _args...) ERROR("%s:%s " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, ##_args) +#define INFO_PRTNAME(_br, _prt, _fmt, _args...) INFO("%s:%s " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, ##_args) +#define LOG_PRTNAME(_br, _prt, _fmt, _args...) LOG("%s:%s " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, ##_args) +#define ERROR_MSTINAME(_br,_prt,_ptp,_fmt,_args...) ERROR("%s:%s:%hu " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) +#define INFO_MSTINAME(_br,_prt,_ptp,_fmt,_args...) INFO("%s:%s:%hu " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) +#define LOG_MSTINAME(_br,_prt,_ptp,_fmt,_args...) LOG("%s:%s:%hu " _fmt, \ + _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) +#define SMLOG_MSTINAME(_ptp, _fmt, _args...) \ + PRINT(LOG_LEVEL_STATE_MACHINE_TRANSITION, "%s: %s:%s:%hu " _fmt, \ + __PRETTY_FUNCTION__, _ptp->port->bridge->sysdeps.name, \ + _ptp->port->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) + +extern struct rtnl_handle rth_state; + +int init_bridge_ops(void); +void bridge_event_handler(void); + +int bridge_notify(int br_index, int if_index, bool newlink, unsigned flags); + +void bridge_bpdu_rcv(int ifindex, const unsigned char *data, int len); + +void bridge_one_second(void); + +#endif /* BRIDGE_CTL_H */ diff --git a/bridge_track.c b/bridge_track.c new file mode 100644 index 0000000..9b9f02f --- /dev/null +++ b/bridge_track.c @@ -0,0 +1,722 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bridge_ctl.h" +#include "bridge_track.h" +#include "netif_utils.h" +#include "packet.h" +#include "log.h" +#include "mstp.h" +#include "driver.h" +#include "libnetlink.h" + +#ifndef SYSFS_CLASS_NET +#define SYSFS_CLASS_NET "/sys/class/net" +#endif + +static LIST_HEAD(bridges); + +static bridge_t * create_br(int if_index) +{ + bridge_t *br; + TST((br = calloc(1, sizeof(*br))) != NULL, NULL); + + /* Init system dependent info */ + br->sysdeps.if_index = if_index; + if (!if_indextoname(if_index, br->sysdeps.name)) + goto err; + if (get_hwaddr(br->sysdeps.name, br->sysdeps.macaddr)) + goto err; + + INFO("Add bridge %s", br->sysdeps.name); + if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr)) + goto err; + + list_add_tail(&br->list, &bridges); + return br; +err: + free(br); + return NULL; +} + +static bridge_t * find_br(int if_index) +{ + bridge_t *br; + list_for_each_entry(br, &bridges, list) + { + if(br->sysdeps.if_index == if_index) + return br; + } + return NULL; +} + +static port_t * create_if(bridge_t * br, int if_index) +{ + port_t *prt; + TST((prt = calloc(1, sizeof(*prt))) != NULL, NULL); + + /* Init system dependent info */ + prt->sysdeps.if_index = if_index; + if (!if_indextoname(if_index, prt->sysdeps.name)) + goto err; + if (get_hwaddr(prt->sysdeps.name, prt->sysdeps.macaddr)) + goto err; + + int portno; + if(0 > (portno = get_bridge_portno(prt->sysdeps.name))) + { + ERROR("Couldn't get port number for %s", prt->sysdeps.name); + goto err; + } + if((0 == portno) || (portno > MAX_PORT_NUMBER)) + { + ERROR("Port number for %s is invalid (%d)", prt->sysdeps.name, portno); + goto err; + } + + INFO("Add iface %s as port#%d to bridge %s", prt->sysdeps.name, + portno, br->sysdeps.name); + prt->bridge = br; + if(!MSTP_IN_port_create_and_add_tail(prt, portno)) + goto err; + + return prt; +err: + free(prt); + return NULL; +} + +static port_t * find_if(bridge_t * br, int if_index) +{ + port_t *prt; + list_for_each_entry(prt, &br->ports, br_list) + { + if(prt->sysdeps.if_index == if_index) + return prt; + } + return NULL; +} + +static inline void delete_if(port_t *prt) +{ + MSTP_IN_delete_port(prt); + free(prt); +} + +static inline bool delete_if_byindex(bridge_t * br, int if_index) +{ + port_t *prt; + if(!(prt = find_if(br, if_index))) + return false; + delete_if(prt); + return true; +} + +static bool delete_br_byindex(int if_index) +{ + bridge_t *br; + if(!(br = find_br(if_index))) + return false; + + INFO("Delete bridge %s (%d)", br->sysdeps.name, if_index); + + list_del(&br->list); + MSTP_IN_delete_bridge(br); + free(br); + return true; +} + +void bridge_one_second(void) +{ + bridge_t *br; + list_for_each_entry(br, &bridges, list) + MSTP_IN_one_second(br); +} + +/* New MAC address is stored in addr, which also holds the old value on entry. + Return true if the address changed */ +static bool check_mac_address(char *name, __u8 *addr) +{ + __u8 temp_addr[ETH_ALEN]; + if(get_hwaddr(name, temp_addr)) + { + LOG("Error getting hw address: %s", name); + /* Error. Ignore the new value */ + return false; + } + if(memcmp(addr, temp_addr, sizeof(temp_addr)) == 0) + return false; + else + { + memcpy(addr, temp_addr, sizeof(temp_addr)); + return true; + } +} + +static void set_br_up(bridge_t * br, bool up) +{ + bool changed = false; + + if(up != br->sysdeps.up) + { + INFO("%s was %s. Set %s", br->sysdeps.name, + br->sysdeps.up ? "up" : "down", up ? "up" : "down"); + br->sysdeps.up = up; + changed = true; + } + + if(check_mac_address(br->sysdeps.name, br->sysdeps.macaddr)) + { + /* MAC address changed */ + /* Notify bridge address change */ + MSTP_IN_set_bridge_address(br, br->sysdeps.macaddr); + } + + if(changed) + MSTP_IN_set_bridge_enable(br, br->sysdeps.up); +} + +static void set_if_up(port_t *prt, bool up) +{ + INFO("Port %s : %s", prt->sysdeps.name, (up ? "up" : "down")); + int speed = -1; + int duplex = -1; + bool changed = false; + + if(check_mac_address(prt->sysdeps.name, prt->sysdeps.macaddr)) + { + /* MAC address changed */ + if(check_mac_address(prt->bridge->sysdeps.name, + prt->bridge->sysdeps.macaddr)) + { + /* Notify bridge address change */ + MSTP_IN_set_bridge_address(prt->bridge, + prt->bridge->sysdeps.macaddr); + } + } + + if(!up) + { /* Down */ + if(prt->sysdeps.up) + { + prt->sysdeps.up = false; + changed = true; + } + } + else + { /* Up */ + int r = ethtool_get_speed_duplex(prt->sysdeps.name, &speed, &duplex); + if((r < 0) || (speed < 0)) + speed = 10; + if((r < 0) || (duplex < 0)) + duplex = 1; /* Assume full duplex */ + + if(speed != prt->sysdeps.speed) + { + prt->sysdeps.speed = speed; + changed = true; + } + if(duplex != prt->sysdeps.duplex) + { + prt->sysdeps.duplex = duplex; + changed = true; + } + if(!prt->sysdeps.up) + { + prt->sysdeps.up = true; + changed = true; + } + } + if(changed) + MSTP_IN_set_port_enable(prt, prt->sysdeps.up, prt->sysdeps.speed, + prt->sysdeps.duplex); +} + +/* br_index == if_index means: interface is bridge master */ +int bridge_notify(int br_index, int if_index, bool newlink, unsigned flags) +{ + port_t *prt; + bridge_t *br = NULL, *other_br; + bool up = !!(flags & IFF_UP); + bool running = up && (flags & IFF_RUNNING); + + LOG("br_index %d, if_index %d, newlink %d, up %d, running %d", + br_index, if_index, newlink, up, running); + + if((br_index >= 0) && (br_index != if_index)) + { + if(!(br = find_br(br_index))) + return -2; /* bridge not in list */ + int br_flags = get_flags(br->sysdeps.name); + if(br_flags >= 0) + set_br_up(br, !!(br_flags & IFF_UP)); + } + + if(br) + { + if(!(prt = find_if(br, if_index))) + { + if(!newlink) + { + INFO("Got DELLINK for unknown port %d on " + "bridge %d", if_index, br_index); + return -1; + } + /* Check if this interface is slave of another bridge */ + list_for_each_entry(other_br, &bridges, list) + { + if(other_br != br) + if(delete_if_byindex(other_br, if_index)) + { + INFO("Device %d has come to bridge %d. " + "Missed notify for deletion from bridge %d", + if_index, br_index, other_br->sysdeps.if_index); + break; + } + } + prt = create_if(br, if_index); + } + if(!prt) + { + ERROR("Couldn't create data for interface %d (master %d)", + if_index, br_index); + return -1; + } + if(!newlink) + { + delete_if(prt); + return 0; + } + set_if_up(prt, running); /* And speed and duplex */ + } + else + { /* Interface is not a bridge slave */ + if(!newlink) + { + /* DELLINK not from bridge means interface unregistered. */ + /* Cleanup removed bridge or removed bridge slave */ + if(!delete_br_byindex(if_index)) + list_for_each_entry(br, &bridges, list) + { + if(delete_if_byindex(br, if_index)) + break; + } + return 0; + } + else + { /* This may be a new link */ + if(br_index == if_index) + { + if(!(br = find_br(br_index))) + return -2; /* bridge not in list */ + set_br_up(br, up); + } + } + } + return 0; +} + +struct llc_header +{ + __u8 dest_addr[ETH_ALEN]; + __u8 src_addr[ETH_ALEN]; + __be16 len8023; + __u8 d_sap; + __u8 s_sap; + __u8 llc_ctrl; +} __attribute__((packed)); + +/* LLC_PDU_xxx defines snitched from linux/net/llc_pdu.h */ +#define LLC_PDU_LEN_U 3 /* header and 1 control byte */ +#define LLC_PDU_TYPE_U 3 /* first two bits */ + +/* 7.12.3 of 802.1D */ +#define LLC_SAP_BSPAN 0x42 +static const __u8 bridge_group_address[ETH_ALEN] = +{ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 +}; + +void bridge_bpdu_rcv(int if_index, const unsigned char *data, int len) +{ + port_t *prt = NULL; + bridge_t *br; + + LOG("ifindex %d, len %d", if_index, len); + + list_for_each_entry(br, &bridges, list) + { + if((prt = find_if(br, if_index))) + break; + } + if(!prt) + return; + + /* sanity checks */ + TSTM(br == prt->bridge,, "Bridge mismatch. This bridge is '%s' but port " + "'%s' belongs to bridge '%s'", br->sysdeps.name, prt->bridge->sysdeps.name); + TSTM(prt->sysdeps.up,, "Port '%s' should be up", prt->sysdeps.name); + + /* Validate Ethernet and LLC header, + * maybe we can skip this check thanks to Berkeley filter in packet socket? + */ + struct llc_header *h; + unsigned int l; + TST(len > sizeof(struct llc_header),); + h = (struct llc_header *)data; + TST(0 == memcmp(h->dest_addr, bridge_group_address, ETH_ALEN), + INFO("ifindex %d, len %d, %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", + if_index, len, + h->dest_addr[0], h->dest_addr[1], h->dest_addr[2], + h->dest_addr[3], h->dest_addr[4], h->dest_addr[5]) + ); + l = __be16_to_cpu(h->len8023); + TST(l <= ETH_DATA_LEN && l <= len - ETH_HLEN && l >= LLC_PDU_LEN_U, ); + TST(h->d_sap == LLC_SAP_BSPAN && h->s_sap == LLC_SAP_BSPAN && (h->llc_ctrl & 0x3) == LLC_PDU_TYPE_U,); + + MSTP_IN_rx_bpdu(prt, + /* Don't include LLC header */ + (bpdu_t *)(data + sizeof(*h)), l - LLC_PDU_LEN_U); +} + +static int br_set_state(struct rtnl_handle *rth, unsigned ifindex, __u8 state) +{ + struct + { + struct nlmsghdr n; + struct ifinfomsg ifi; + char buf[256]; + } req; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE; + req.n.nlmsg_type = RTM_SETLINK; + req.ifi.ifi_family = AF_BRIDGE; + req.ifi.ifi_index = ifindex; + + addattr8(&req.n, sizeof(req.buf), IFLA_PROTINFO, state); + + return rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL); +} + +static int br_flush_port(char *ifname) +{ + char fname[128]; + snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/brport/flush", ifname); + int fd = open(fname, O_WRONLY); + TSTM(0 <= fd, -1, "Couldn't open flush file %s for write: %m", fname); + int write_result = write(fd, "1", 1); + close(fd); + TST(1 == write_result, -1); + return 0; +} + +static int br_set_ageing_time(char *brname, unsigned int ageing_time) +{ + char fname[128], str_time[32]; + snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/bridge/ageing_time", + brname); + int fd = open(fname, O_WRONLY); + TSTM(0 <= fd, -1, "Couldn't open file %s for write: %m", fname); + int len = sprintf(str_time, "%u", ageing_time * HZ); + int write_result = write(fd, str_time, len); + close(fd); + TST(len == write_result, -1); + return 0; +} + +/* External actions for MSTP protocol */ + +void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state) +{ + char * state_name; + port_t *prt = ptp->port; + bridge_t *br = prt->bridge; + + if(ptp->state == new_state) + return; + ptp->state = driver_set_new_state(ptp, new_state); + + switch(ptp->state) + { + case BR_STATE_LISTENING: + state_name = "listening"; + break; + case BR_STATE_LEARNING: + state_name = "learning"; + break; + case BR_STATE_FORWARDING: + state_name = "forwarding"; + ++(prt->num_trans_fwd); + break; + case BR_STATE_BLOCKING: + state_name = "blocking"; + ++(prt->num_trans_blk); + break; + default: + case BR_STATE_DISABLED: + state_name = "disabled"; + break; + } + INFO_MSTINAME(br, prt, ptp, "entering %s state", state_name); + + /* Translate new CIST state to the kernel bridge code */ + if(0 == ptp->MSTID) + { /* CIST */ + if(0 > br_set_state(&rth_state, prt->sysdeps.if_index, ptp->state)) + INFO_PRTNAME(br, prt, "Couldn't set kernel bridge state %s", + state_name); + } +} + +/* This function initiates process of flushing + * all entries for the given port in all FIDs for the + * given tree. + * When this process finishes, implementation should signal + * this by calling MSTP_IN_all_fids_flushed(per_tree_port_t *ptp) + */ +void MSTP_OUT_flush_all_fids(per_tree_port_t * ptp) +{ + port_t *prt = ptp->port; + bridge_t *br = prt->bridge; + + /* Translate CIST flushing to the kernel bridge code */ + if(0 == ptp->MSTID) + { /* CIST */ + if(0 > br_flush_port(prt->sysdeps.name)) + ERROR_PRTNAME(br, prt, + "Couldn't flush kernel bridge forwarding database"); + } + /* Completion signal MSTP_IN_all_fids_flushed will be called by driver */ + INFO_MSTINAME(br, prt, ptp, "Flushing forwarding database"); + driver_flush_all_fids(ptp); +} + +void MSTP_OUT_set_ageing_time(port_t *prt, unsigned int ageingTime) +{ + unsigned int actual_ageing_time; + bridge_t *br = prt->bridge; + + actual_ageing_time = driver_set_ageing_time(prt, ageingTime); + INFO_PRTNAME(br, prt, "Setting new ageing time to %u", actual_ageing_time); + + /* + * Translate new ageing time to the kernel bridge code. + * Kernel bridging code does not support per-port ageing time, + * so set ageing time for the whole bridge. + */ + if(0 > br_set_ageing_time(br->sysdeps.name, actual_ageing_time)) + ERROR_BRNAME(br, "Couldn't set new ageing time in kernel bridge"); +} + +void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t * bpdu, int size) +{ + char *bpdu_type, *tcflag; + bridge_t *br = prt->bridge; + + switch(bpdu->protocolVersion) + { + case protoSTP: + switch(bpdu->bpduType) + { + case bpduTypeConfig: + bpdu_type = "STP-Config"; + break; + case bpduTypeTCN: + bpdu_type = "STP-TCN"; + break; + default: + bpdu_type = "STP-UnknownType"; + } + break; + case protoRSTP: + bpdu_type = "RST"; + break; + case protoMSTP: + bpdu_type = "MST"; + break; + default: + bpdu_type = "UnknownProto"; + } + + ++(prt->num_tx_bpdu); + if((protoSTP == bpdu->protocolVersion) && (bpduTypeTCN == bpdu->bpduType)) + { + ++(prt->num_tx_tcn); + LOG_PRTNAME(br, prt, "sending %s BPDU", bpdu_type); + } + else + { + tcflag = ""; + if(bpdu->flags & (1 << offsetTc)) + { + ++(prt->num_tx_tcn); + tcflag = ", tcFlag"; + } + LOG_PRTNAME(br, prt, "sending %s BPDU%s", bpdu_type, tcflag); + } + + struct llc_header h; + memcpy(h.dest_addr, bridge_group_address, ETH_ALEN); + memcpy(h.src_addr, prt->sysdeps.macaddr, ETH_ALEN); + h.len8023 = __cpu_to_be16(size + LLC_PDU_LEN_U); + h.d_sap = h.s_sap = LLC_SAP_BSPAN; + h.llc_ctrl = LLC_PDU_TYPE_U; + + struct iovec iov[2] = + { + { .iov_base = &h, .iov_len = sizeof(h) }, + { .iov_base = bpdu, .iov_len = size } + }; + + packet_send(prt->sysdeps.if_index, iov, 2, sizeof(h) + size); +} + +void MSTP_OUT_shutdown_port(port_t *prt) +{ + if(0 > if_shutdown(prt->sysdeps.name)) + ERROR_PRTNAME(prt->bridge, prt, "Couldn't shutdown port"); +} + +static int not_dot_dotdot(const struct dirent *entry) +{ + const char *n = entry->d_name; + + return strcmp(n, ".") || strcmp(n, ".."); +} + +static int get_port_list(const char *br_ifname, struct dirent ***namelist) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), SYSFS_CLASS_NET "/%.230s/brif", br_ifname); + + return scandir(buf, namelist, not_dot_dotdot, versionsort); +} + +int bridge_create(int bridge_idx, CIST_BridgeConfig *cfg) +{ + struct dirent **namelist; + int *port_list, n_ports; + bridge_t *br, *other_br; + port_t *port, *tmp; + int flags; + bool found; + int i; + + br = find_br(bridge_idx); + if (!br) + br = create_br(bridge_idx); + if (!br) + return -1; + + MSTP_IN_set_cist_bridge_config(br, cfg); + + flags = get_flags(br->sysdeps.name); + if (flags >= 0) + set_br_up(br, !!(flags & IFF_UP)); + + n_ports = get_port_list(br->sysdeps.name, &namelist); + port_list = alloca(n_ports * sizeof(*port_list)); + + for (i = 0; i < n_ports; i++) { + port_list[i] = if_nametoindex(namelist[i]->d_name); + free(namelist[i]); + } + free(namelist); + + list_for_each_entry_safe(port, tmp, &br->ports, br_list) { + found = false; + for (i = 0; i < n_ports; i++) { + if (port->sysdeps.if_index != port_list[i]) + continue; + found = true; + break; + } + + if (found) + continue; + + delete_if(port); + } + + for (i = 0; i < n_ports; i++) { + port = find_if(br, port_list[i]); + if (port) + continue; + + list_for_each_entry(other_br, &bridges, list) { + if (br == other_br) + continue; + + delete_if_byindex(other_br, port_list[i]); + } + + port = find_if(br, port_list[i]); + if (!port) + port = create_if(br, port_list[i]); + if (!port) + continue; + + flags = get_flags(port->sysdeps.name); + if (flags < 0) + continue; + + set_if_up(port, !(~flags & (IFF_UP | IFF_RUNNING))); + } + + return 0; +} + +void bridge_delete(int index) +{ + delete_br_byindex(index); +} + +int bridge_track_fini(void) +{ + INFO("Stopping all bridges"); + bridge_t *br; + list_for_each_entry(br, &bridges, list) + { + set_br_up(br, false); + } + return 0; +} diff --git a/bridge_track.h b/bridge_track.h new file mode 100644 index 0000000..6c0bd4c --- /dev/null +++ b/bridge_track.h @@ -0,0 +1,30 @@ +/***************************************************************************** + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + +******************************************************************************/ + +#ifndef MSTPD_BRIDGE_TRACK_H +#define MSTPD_BRIDGE_TRACK_H + +#include "mstp.h" + +int bridge_create(int bridge_idx, CIST_BridgeConfig *cfg); +void bridge_delete(int bridge_idx); +int bridge_track_fini(void); + +#endif diff --git a/brmon.c b/brmon.c new file mode 100644 index 0000000..d25dae4 --- /dev/null +++ b/brmon.c @@ -0,0 +1,224 @@ +/* + * brmon.c RTnetlink listener. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Stephen Hemminger + * Modified by Srinivas Aji + * for use in RSTP daemon. - 2006-09-01 + * Modified by Vitalii Demianets + * for use in MSTP daemon. - 2011-07-18 + */ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "libnetlink.h" +#include "bridge_ctl.h" +#include "netif_utils.h" +#include "worker.h" + +/* RFC 2863 operational status */ +enum +{ + IF_OPER_UNKNOWN, + IF_OPER_NOTPRESENT, + IF_OPER_DOWN, + IF_OPER_LOWERLAYERDOWN, + IF_OPER_TESTING, + IF_OPER_DORMANT, + IF_OPER_UP, +}; + +/* link modes */ +enum +{ + IF_LINK_MODE_DEFAULT, + IF_LINK_MODE_DORMANT, /* limit upward transition to dormant */ +}; + +static const char *port_states[] = +{ + [BR_STATE_DISABLED] = "disabled", + [BR_STATE_LISTENING] = "listening", + [BR_STATE_LEARNING] = "learning", + [BR_STATE_FORWARDING] = "forwarding", + [BR_STATE_BLOCKING] = "blocking", +}; + +static struct rtnl_handle rth; +static struct uloop_fd ufd; + +struct rtnl_handle rth_state; + +static int dump_msg(const struct sockaddr_nl *who, struct nlmsghdr *n, + void *arg) +{ + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX + 1]; + int len = n->nlmsg_len; + char b1[IFNAMSIZ]; + int af_family; + bool newlink; + int br_index; + + if(n->nlmsg_type == NLMSG_DONE) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if(len < 0) + { + return -1; + } + + af_family = ifi->ifi_family; + + if(af_family != AF_BRIDGE && af_family != AF_UNSPEC) + return 0; + + if(n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) + return 0; + + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + + /* Check if we got this from bonding */ + if(tb[IFLA_MASTER] && af_family != AF_BRIDGE) + return 0; + + if(tb[IFLA_IFNAME] == NULL) + { + ERROR("BUG: nil ifname\n"); + return -1; + } + + if(n->nlmsg_type == RTM_DELLINK) + LOG("Deleted "); + + LOG("%d: %s ", ifi->ifi_index, (char*)RTA_DATA(tb[IFLA_IFNAME])); + + if(tb[IFLA_OPERSTATE]) + { + __u8 state = *(__u8*)RTA_DATA(tb[IFLA_OPERSTATE]); + switch (state) + { + case IF_OPER_UNKNOWN: + LOG("Unknown "); + break; + case IF_OPER_NOTPRESENT: + LOG("Not Present "); + break; + case IF_OPER_DOWN: + LOG("Down "); + break; + case IF_OPER_LOWERLAYERDOWN: + LOG("Lowerlayerdown "); + break; + case IF_OPER_TESTING: + LOG("Testing "); + break; + case IF_OPER_DORMANT: + LOG("Dormant "); + break; + case IF_OPER_UP: + LOG("Up "); + break; + default: + LOG("State(%d) ", state); + } + } + + if(tb[IFLA_MTU]) + LOG("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + + if(tb[IFLA_MASTER]) + { + LOG("master %s ", + if_indextoname(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); + } + + if(tb[IFLA_PROTINFO]) + { + uint8_t state = *(uint8_t *)RTA_DATA(tb[IFLA_PROTINFO]); + if(state <= BR_STATE_BLOCKING) + LOG("state %s", port_states[state]); + else + LOG("state (%d)", state); + } + + newlink = (n->nlmsg_type == RTM_NEWLINK); + + if(tb[IFLA_MASTER]) + br_index = *(int*)RTA_DATA(tb[IFLA_MASTER]); + else if(is_bridge((char*)RTA_DATA(tb[IFLA_IFNAME]))) + br_index = ifi->ifi_index; + else + br_index = -1; + + bridge_notify(br_index, ifi->ifi_index, newlink, ifi->ifi_flags); + + return 0; +} + +void bridge_event_handler(void) +{ + if(rtnl_listen(&rth, dump_msg, stdout) < 0) + { + ERROR("Error on bridge monitoring socket\n"); + } +} + +static void bridge_event_cb(struct uloop_fd *fd, unsigned int events) +{ + struct worker_event ev = { + .type = WORKER_EV_BRIDGE_EVENT + }; + + worker_queue_event(&ev); +} + +int init_bridge_ops(void) +{ + if(rtnl_open(&rth, RTMGRP_LINK) < 0) + { + ERROR("Couldn't open rtnl socket for monitoring\n"); + return -1; + } + + if(rtnl_open(&rth_state, 0) < 0) + { + ERROR("Couldn't open rtnl socket for setting state\n"); + return -1; + } + + if(rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETLINK) < 0) + { + ERROR("Cannot send dump request: %m\n"); + return -1; + } + + if(rtnl_dump_filter(&rth, dump_msg, stdout, NULL, NULL) < 0) + { + ERROR("Dump terminated\n"); + return -1; + } + + if(fcntl(rth.fd, F_SETFL, O_NONBLOCK) < 0) + { + ERROR("Error setting O_NONBLOCK: %m\n"); + return -1; + } + + ufd.fd = rth.fd; + ufd.cb = bridge_event_cb; + uloop_fd_add(&ufd, ULOOP_READ | ULOOP_EDGE_TRIGGER); + + return 0; +} diff --git a/config.c b/config.c new file mode 100644 index 0000000..e424318 --- /dev/null +++ b/config.c @@ -0,0 +1,68 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include + +#include +#include + +#include "config.h" + +AVL_TREE(bridge_config, avl_strcmp, false, NULL); + +static uint32_t bridge_config_timestamp(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return ts.tv_sec; +} + +struct bridge_config * +bridge_config_get(const char *name, bool create) +{ + struct bridge_config *cfg; + char *name_buf; + + cfg = avl_find_element(&bridge_config, name, cfg, node); + if (cfg) + goto out; + + if (!create) + return NULL; + + cfg = calloc_a(sizeof(*cfg), &name_buf, strlen(name) + 1); + cfg->node.key = strcpy(name_buf, name); + avl_insert(&bridge_config, &cfg->node); + +out: + cfg->timestamp = bridge_config_timestamp(); + + return cfg; +} + +void bridge_config_expire(void) +{ + struct bridge_config *cfg, *tmp; + uint32_t ts; + + ts = bridge_config_timestamp(); + avl_for_each_element_safe(&bridge_config, cfg, node, tmp) { + if (ts - cfg->timestamp < 60) + continue; + + avl_delete(&bridge_config, &cfg->node); + free(cfg); + } +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..e28b463 --- /dev/null +++ b/config.h @@ -0,0 +1,32 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __CONFIG_H +#define __CONFIG_H + +#include +#include +#include "mstp.h" + +extern struct avl_tree bridge_config; + +struct bridge_config { + struct avl_node node; + uint32_t timestamp; + CIST_BridgeConfig config; +}; + +struct bridge_config *bridge_config_get(const char *name, bool create); +void bridge_config_expire(void); + +#endif diff --git a/driver.h b/driver.h new file mode 100644 index 0000000..fbc641e --- /dev/null +++ b/driver.h @@ -0,0 +1,67 @@ +/* + * driver.h Driver-specific code. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Vitalii Demianets + */ + +#ifndef _MSTP_DRIVER_H +#define _MSTP_DRIVER_H + +#include "mstp.h" + +static inline int +driver_set_new_state(per_tree_port_t *ptp, int new_state) +{ + return new_state; +} + +static inline void +driver_flush_all_fids(per_tree_port_t *ptp) +{ + MSTP_IN_all_fids_flushed(ptp); +} + +static inline unsigned int +driver_set_ageing_time(port_t *prt, unsigned int ageingTime) +{ + return ageingTime; +} + +static inline bool +driver_create_msti(bridge_t *br, __u16 mstid) +{ + return true; +} + +static inline bool +driver_delete_msti(bridge_t *br, __u16 mstid) +{ + return true; +} + +static inline bool +driver_create_bridge(bridge_t *br, __u8 *macaddr) +{ + return true; +} + +static inline bool +driver_create_port(port_t *prt, __u16 portno) +{ + return true; +} + +static inline void driver_delete_bridge(bridge_t *br) +{ +} + +static inline void driver_delete_port(port_t *prt) +{ +} + +#endif /* _MSTP_DRIVER_H */ diff --git a/hmac_md5.c b/hmac_md5.c new file mode 100644 index 0000000..a799407 --- /dev/null +++ b/hmac_md5.c @@ -0,0 +1,101 @@ +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include +#include +#include + +#include "mstp.h" + +/* +** Function: hmac_md5 from RFC-2104 +*/ +void hmac_md5(text, text_len, key, key_len, digest) +unsigned char* text; /* pointer to data stream */ +int text_len; /* length of data stream */ +unsigned char* key; /* pointer to authentication key */ +int key_len; /* length of authentication key */ +caddr_t digest; /* caller digest to be filled in */ +{ + md5_ctx_t context; + unsigned char k_ipad[65]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[65]; /* outer padding - + * key XORd with opad + */ + unsigned char tk[16]; + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if(key_len > 64) + { + md5_ctx_t tctx; + + md5_begin(&tctx); + md5_hash(key, key_len, &tctx); + md5_end(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero(k_ipad, sizeof k_ipad); + bzero(k_opad, sizeof k_opad); + bcopy(key, k_ipad, key_len); + bcopy( key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for(i = 0; i < 64; ++i) + { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + md5_begin(&context); /* init context for 1st + * pass */ + md5_hash(k_ipad, 64, &context); /* start with inner pad */ + md5_hash(text, text_len, &context); /* then text of datagram */ + md5_end(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + md5_begin(&context); /* init context for 2nd + * pass */ + md5_hash(k_opad, 64, &context); /* start with outer pad */ + md5_hash(digest, 16, &context); /* then results of 1st + * hash */ + md5_end(digest, &context); /* finish up 2nd pass */ +} diff --git a/libnetlink.c b/libnetlink.c new file mode 100644 index 0000000..95162ec --- /dev/null +++ b/libnetlink.c @@ -0,0 +1,675 @@ +/* + * libnetlink.c RTnetlink service routines. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "libnetlink.h" + +#ifndef DEFAULT_RTNL_BUFSIZE +#define DEFAULT_RTNL_BUFSIZE 256 * 1024 +#endif + +#ifndef RTNL_SND_BUFSIZE +#define RTNL_SND_BUFSIZE DEFAULT_RTNL_BUFSIZE +#endif +#ifndef RTNL_RCV_BUFSIZE +#define RTNL_RCV_BUFSIZE DEFAULT_RTNL_BUFSIZE +#endif + +void rtnl_close(struct rtnl_handle *rth) +{ + close(rth->fd); +} + +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol) +{ + socklen_t addr_len; + int sndbuf = RTNL_SND_BUFSIZE; + int rcvbuf = RTNL_RCV_BUFSIZE; + + memset(rth, 0, sizeof(*rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + ERROR("Cannot open netlink socket"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) + < 0) { + ERROR("SO_SNDBUF"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) + < 0) { + ERROR("SO_RCVBUF"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < + 0) { + ERROR("Cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { + ERROR("Cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + ERROR("Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + ERROR("Wrong address family %d\n", + rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void *)&req, sizeof(req), 0, + (struct sockaddr *)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr *)&nladdr, + sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { + {.iov_base = &nlh,.iov_len = sizeof(nlh)} + , + {.iov_base = req,.iov_len = len} + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 2, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + rtnl_filter_t filter, + void *arg1, rtnl_filter_t junk, void *arg2) +{ + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + iov.iov_base = buf; + while (1) { + int status; + struct nlmsghdr *h; + + iov.iov_len = sizeof(buf); + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + ERROR("OVERRUN"); + continue; + } + + if (status == 0) { + ERROR("EOF on netlink\n"); + return -1; + } + + h = (struct nlmsghdr *)buf; + while (NLMSG_OK(h, status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) + return err; + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) + return 0; + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = + (struct nlmsgerr *)NLMSG_DATA(h); + if (h->nlmsg_len < + NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + ERROR("ERROR truncated\n"); + } else { + errno = -err->error; + INFO("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) + return err; + + skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + ERROR("Message truncated\n"); + continue; + } + if (status) { + ERROR("!!!Remnant of size %d\n", status); + return -1; + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { + .iov_base = (void *)n, + .iov_len = n->nlmsg_len + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + ERROR("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf, 0, sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + ERROR("OVERRUN"); + continue; + } + if (status == 0) { + ERROR("EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + ERROR("sender address length == %d\n", + msg.msg_namelen); + return -1; + } + for (h = (struct nlmsghdr *)buf; status >= sizeof(*h);) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + ERROR("Truncated message\n"); + return -1; + } + ERROR( + "!!!malformed message: len=%d\n", len); + return -1; + } + + if (nladdr.nl_pid != peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + err = junk(&nladdr, h, jarg); + if (err < 0) + return err; + } + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = + (struct nlmsgerr *)NLMSG_DATA(h); + if (l < sizeof(struct nlmsgerr)) { + ERROR("ERROR truncated\n"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, + h->nlmsg_len); + return 0; + } + ERROR("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + ERROR("Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + ERROR("Message truncated\n"); + continue; + } + if (status) { + ERROR("!!!Remnant of size %d\n", status); + return -1; + } + } +} + +int rtnl_listen(struct rtnl_handle *rtnl, rtnl_filter_t handler, void *jarg) +{ + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[8192]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + iov.iov_base = buf; + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + return 0; + ERROR("OVERRUN: recvmsg(): error %d : %s\n", errno, strerror(errno)); + return -1; + } + if (status == 0) { + ERROR("EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + ERROR("Sender address length == %d\n", + msg.msg_namelen); + return -1; + } + for (h = (struct nlmsghdr *)buf; status >= sizeof(*h);) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + ERROR("Truncated message\n"); + return -1; + } + ERROR( + "!!!malformed message: len=%d\n", len); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) { + ERROR("Handler returned %d\n", err); + return err; + } + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + ERROR("Message truncated\n"); + continue; + } + if (status) { + ERROR("!!!Remnant of size %d\n", status); + return -1; + } + } +} + +int rtnl_from_file(FILE * rtnl, rtnl_filter_t handler, void *jarg) +{ + int status; + struct sockaddr_nl nladdr; + char buf[8192]; + struct nlmsghdr *h = (void *)buf; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + int err, len; + int l; + + status = fread(&buf, 1, sizeof(*h), rtnl); + + if (status < 0) { + if (errno == EINTR) + continue; + ERROR("rtnl_from_file: fread"); + return -1; + } + if (status == 0) + return 0; + + len = h->nlmsg_len; + l = len - sizeof(*h); + + if (l < 0 || len > sizeof(buf)) { + ERROR("!!!malformed message: len=%d @%lu\n", + len, ftell(rtnl)); + return -1; + } + + status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); + + if (status < 0) { + ERROR("rtnl_from_file: fread"); + return -1; + } + if (status < l) { + ERROR("rtnl-from_file: truncated message\n"); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + ERROR( + "addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data) +{ + int len = RTA_LENGTH(1); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + ERROR( + "addattr8: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 1); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + ERROR( + "addattr_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) { + ERROR( + "addraw_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((void *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + ERROR( + "rta_addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { + ERROR( + "rta_addattr_l: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data) +{ + return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u8)); +} + +int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data) +{ + return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u16)); +} + +int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data) +{ + return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u64)); +} + +struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type) +{ + struct rtattr *nest = RTA_TAIL(rta); + + rta_addattr_l(rta, maxlen, type, NULL, 0); + nest->rta_type |= NLA_F_NESTED; + + return nest; +} + +int rta_nest_end(struct rtattr *rta, struct rtattr *nest) +{ + nest->rta_len = (void *)RTA_TAIL(rta) - (void *)nest; + + return rta->rta_len; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + ERROR("!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, + int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + ERROR("!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return i; +} diff --git a/libnetlink.h b/libnetlink.h new file mode 100644 index 0000000..7621596 --- /dev/null +++ b/libnetlink.h @@ -0,0 +1,67 @@ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ + +#include +#include +#include +#include +#include + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol); +void rtnl_close(struct rtnl_handle *rth); +int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); + +typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, struct nlmsghdr *n, + void *); +int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, + void *arg1, rtnl_filter_t junk, void *arg2); +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk, + void *jarg); +int rtnl_send(struct rtnl_handle *rth, const char *buf, int); + +int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data); +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen); +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); +int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data); +int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data); +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data); +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen); + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, + int len); + +struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type); +int rta_nest_end(struct rtattr *rta, struct rtattr *nest); + +#define RTA_TAIL(rta) \ + ((struct rtattr *) (((void *) (rta)) + \ + RTA_ALIGN((rta)->rta_len))) + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, void *jarg); +int rtnl_from_file(FILE *, rtnl_filter_t handler, void *jarg); + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#endif /* __LIBNETLINK_H__ */ diff --git a/log.h b/log.h new file mode 100644 index 0000000..143f330 --- /dev/null +++ b/log.h @@ -0,0 +1,88 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ + +#ifndef LOG_H +#define LOG_H + +#include +#include + +#define LOG_LEVEL_NONE 0 +#define LOG_LEVEL_ERROR 1 +#define LOG_LEVEL_INFO 2 +#define LOG_LEVEL_DEBUG 3 +#define LOG_LEVEL_STATE_MACHINE_TRANSITION 4 + +#ifdef DEBUG +#define LOG_LEVEL_MAX 100 +#else +#define LOG_LEVEL_MAX LOG_LEVEL_INFO +#endif + +#define LOG_LEVEL_DEFAULT LOG_LEVEL_ERROR + +extern void Dprintf(int level, const char *fmt, ...); +extern int log_level; + +#define PRINT(_level, _fmt, _args...) \ + ({ \ + if ((_level) <= LOG_LEVEL_MAX) \ + Dprintf(_level, _fmt, ##_args); \ + }) + +#define TSTM(x, y, _fmt, _args...) \ + do if(!(x)) \ + { \ + PRINT(LOG_LEVEL_ERROR, "Error in %s at %s:%d verifying %s. " _fmt, \ + __PRETTY_FUNCTION__, __FILE__, __LINE__, #x, ##_args); \ + return y; \ + } while (0) + +#define TST(x, y) TSTM(x, y, "") + +#define LOG(_fmt, _args...) \ + PRINT(LOG_LEVEL_DEBUG, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) + +#define INFO(_fmt, _args...) \ + PRINT(LOG_LEVEL_INFO, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) + +#define ERROR(_fmt, _args...) \ + PRINT(LOG_LEVEL_ERROR, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) + +static inline void dump_hex(void *b, int l) +{ + unsigned char *buf = b; + char logbuf[80]; + int i, j; + for (i = 0; i < l; i += 16) { + for (j = 0; j < 16 && i + j < l; ++j) + sprintf(logbuf + j * 3, " %02x", buf[i + j]); + PRINT(LOG_LEVEL_INFO, "%s", logbuf); + } + PRINT(LOG_LEVEL_INFO, "\n"); +} + +#endif /* LOG_H */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..a83a85d --- /dev/null +++ b/main.c @@ -0,0 +1,135 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ + +/* #define MISC_TEST_FUNCS */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bridge_ctl.h" +#include "netif_utils.h" +#include "packet.h" +#include "log.h" +#include "mstp.h" +#include "driver.h" +#include "bridge_track.h" +#include "worker.h" +#include "ubus.h" + +#define APP_NAME "ustpd" + +static int print_to_syslog = 1; +int log_level = LOG_LEVEL_DEFAULT; + + +int main(int argc, char *argv[]) +{ + int c; + + while((c = getopt(argc, argv, "sv:")) != -1) { + switch (c) { + case 's': + print_to_syslog = 0; + break; + case 'v': { + char *end; + long l; + l = strtoul(optarg, &end, 0); + if(*optarg == 0 || *end != 0 || l > LOG_LEVEL_MAX) { + ERROR("Invalid loglevel %s", optarg); + exit(1); + } + log_level = l; + break; + } + default: + return -1; + } + } + + if (print_to_syslog) + openlog(APP_NAME, LOG_PID, LOG_DAEMON); + + uloop_init(); + + TST(worker_init() == 0, -1); + TST(packet_sock_init() == 0, -1); + TST(netsock_init() == 0, -1); + TST(init_bridge_ops() == 0, -1); + ustp_ubus_init(); + + uloop_run(); + bridge_track_fini(); + worker_cleanup(); + ustp_ubus_exit(); + uloop_done(); + + return 0; +} + +/*********************** Logging *********************/ + +#include +#include + +static void vDprintf(int level, const char *fmt, va_list ap) +{ + if(level > log_level) + return; + + if(!print_to_syslog) + { + char logbuf[256]; + logbuf[255] = 0; + time_t clock; + struct tm *local_tm; + time(&clock); + local_tm = localtime(&clock); + int l = strftime(logbuf, sizeof(logbuf) - 1, "%F %T ", local_tm); + vsnprintf(logbuf + l, sizeof(logbuf) - l - 1, fmt, ap); + printf("%s\n", logbuf); + fflush(stdout); + } + else + { + vsyslog((level <= LOG_LEVEL_INFO) ? LOG_INFO : LOG_DEBUG, fmt, ap); + } +} + +void Dprintf(int level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vDprintf(level, fmt, ap); + va_end(ap); +} diff --git a/mstp.c b/mstp.c new file mode 100644 index 0000000..916538f --- /dev/null +++ b/mstp.c @@ -0,0 +1,5153 @@ +/* + * mstp.c State machines from IEEE 802.1Q-2005 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Vitalii Demianets + */ + +/* NOTE: The standard messes up Hello_Time timer management. + * The portTimes and msgTimes structures have it, while + * designatedTimes, rootTimes and BridgeTimes do not! + * And there are places, where standard says: + * "portTimes = designatedTimes" (13.32) + * "rootTimes = portTimes" (13.26.23) + * ---- Bad IEEE! ---- + * For now I decide: All structures will hold Hello_Time, + * because in 802.1D they do. + * Besides, it is necessary for compatibility with old STP implementations. + */ + +/* 802.1Q-2005 does not define but widely use variable name newInfoXst. + * From the 802.1s I can guess that it means: + * - "newInfo" when tree is CIST; + * - "newInfoMsti" when tree is not CIST (is MSTI). + * But that is only a guess and I could be wrong here ;) + */ + +/* Important coding convention: + * All functions that have dry_run argument must follow the + * return value convention: + * They should return true if state change detected during dry run. + * Otherwise (!dry_run || !state_change) they return false. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "mstp.h" +#include "log.h" +#include "driver.h" + +static void PTSM_tick(port_t *prt); +static bool TCSM_run(per_tree_port_t *ptp, bool dry_run); +static void BDSM_begin(port_t *prt); +static void br_state_machines_begin(bridge_t *br); +static void prt_state_machines_begin(port_t *prt); +static void tree_state_machines_begin(tree_t *tree); +static void br_state_machines_run(bridge_t *br); +static void updtbrAssuRcvdInfoWhile(port_t *prt); + +#define FOREACH_PORT_IN_BRIDGE(port, bridge) \ + list_for_each_entry((port), &(bridge)->ports, br_list) +#define FOREACH_TREE_IN_BRIDGE(tree, bridge) \ + list_for_each_entry((tree), &(bridge)->trees, bridge_list) +#define FOREACH_PTP_IN_TREE(ptp, tree) \ + list_for_each_entry((ptp), &(tree)->ports, tree_list) +#define FOREACH_PTP_IN_PORT(ptp, port) \ + list_for_each_entry((ptp), &(port)->trees, port_list) + +/* 17.20.11 of 802.1D */ +#define rstpVersion(br) ((br)->ForceProtocolVersion >= protoRSTP) +/* Bridge assurance is operational only when NetworkPort type is configured + * and the operation status is pointToPoint and version is RSTP/MSTP + */ +#define assurancePort(prt) ((prt)->NetworkPort && (prt)->operPointToPointMAC \ + && (prt)->sendRSTP) +/* + * Recalculate configuration digest. (13.7) + */ +static void RecalcConfigDigest(bridge_t *br) +{ + __be16 vid2mstid[MAX_VID + 2]; + unsigned char mstp_key[] = HMAC_KEY; + int vid; + + vid2mstid[0] = vid2mstid[MAX_VID + 1] = 0; + for(vid = 1; vid <= MAX_VID; ++vid) + vid2mstid[vid] = br->fid2mstid[br->vid2fid[vid]]; + + hmac_md5((void *)vid2mstid, sizeof(vid2mstid), mstp_key, sizeof(mstp_key), + (caddr_t)br->MstConfigId.s.configuration_digest); +} + +/* + * 13.37.1 - Table 13-3 + */ +static __u32 compute_pcost(int speed) +{ + /* speed is in MB/s*/ + if(speed > 0) + return (speed < 20000000) ? 20000000 / speed : 1; + else + return MAX_PATH_COST; +} + +static void bridge_default_internal_vars(bridge_t *br) +{ + br->uptime = 0; +} + +static void tree_default_internal_vars(tree_t *tree) +{ + /* 12.8.1.1.3.(b,c,d) */ + tree->time_since_topology_change = 0; + tree->topology_change_count = 0; + tree->topology_change = false; /* since all tcWhile are initialized to 0 */ + strncpy(tree->topology_change_port, "None", IFNAMSIZ); + strncpy(tree->last_topology_change_port, "None", IFNAMSIZ); + + /* The following are initialized in BEGIN state: + * - rootPortId, rootPriority, rootTimes: in Port Role Selection SM + */ +} + +static void port_default_internal_vars(port_t *prt) +{ + prt->infoInternal = false; + prt->rcvdInternal = false; + prt->rcvdTcAck = false; + prt->rcvdTcn = false; + assign(prt->rapidAgeingWhile, 0u); + assign(prt->brAssuRcvdInfoWhile, 0u); + prt->BaInconsistent = false; + prt->num_rx_bpdu_filtered = 0; + prt->num_rx_bpdu = 0; + prt->num_rx_tcn = 0; + prt->num_tx_bpdu = 0; + prt->num_tx_tcn = 0; + prt->num_trans_fwd = 0; + prt->num_trans_blk = 0; + + /* The following are initialized in BEGIN state: + * - mdelayWhile. mcheck, sendRSTP: in Port Protocol Migration SM + * - helloWhen, newInfo, newInfoMsti, txCount: in Port Transmit SM + * - edgeDelayWhile, rcvdBpdu, rcvdRSTP, rcvdSTP : in Port Receive SM + * - operEdge: in Bridge Detection SM + * - tcAck: in Topology Change SM + */ +} + +static void ptp_default_internal_vars(per_tree_port_t *ptp) +{ + ptp->rcvdTc = false; + ptp->tcProp = false; + ptp->updtInfo = false; + ptp->master = false; /* 13.24.5 */ + ptp->disputed = false; + assign(ptp->rcvdInfo, (port_info_t)0); + ptp->mastered = false; + memset(&ptp->msgPriority, 0, sizeof(ptp->msgPriority)); + memset(&ptp->msgTimes, 0, sizeof(ptp->msgTimes)); + + /* The following are initialized in BEGIN state: + * - rcvdMsg: in Port Receive SM + * - fdWhile, rrWhile, rbWhile, role, learn, forward, + * sync, synced, reRoot: in Port Role Transitions SM + * - tcWhile, fdbFlush: Topology Change SM + * - rcvdInfoWhile, proposed, proposing, agree, agreed, + * infoIs, reselect, selected: Port Information SM + * - forwarding, learning: Port State Transition SM + * - selectedRole, designatedPriority, designatedTimes: Port Role Selection SM + */ +} + +static tree_t * create_tree(bridge_t *br, __u8 *macaddr, __be16 MSTID) +{ + /* Initialize all fields except anchor */ + tree_t *tree = calloc(1, sizeof(*tree)); + if(!tree) + { + ERROR_BRNAME(br, "Out of memory"); + return NULL; + } + tree->bridge = br; + tree->MSTID = MSTID; + INIT_LIST_HEAD(&tree->ports); + + memcpy(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN); + /* 0x8000 = default bridge priority (17.14 of 802.1D) */ + tree->BridgeIdentifier.s.priority = __constant_cpu_to_be16(0x8000) | MSTID; + assign(tree->BridgePriority.RootID, tree->BridgeIdentifier); + assign(tree->BridgePriority.RRootID, tree->BridgeIdentifier); + assign(tree->BridgePriority.DesignatedBridgeID, tree->BridgeIdentifier); + /* 13.23.4 */ + assign(tree->BridgeTimes.remainingHops, br->MaxHops); + assign(tree->BridgeTimes.Forward_Delay, br->Forward_Delay); + assign(tree->BridgeTimes.Max_Age, br->Max_Age); + assign(tree->BridgeTimes.Message_Age, (__u8)0); + assign(tree->BridgeTimes.Hello_Time, br->Hello_Time); + + tree_default_internal_vars(tree); + + return tree; +} + +static per_tree_port_t * create_ptp(tree_t *tree, port_t *prt) +{ + /* Initialize all fields except anchors */ + per_tree_port_t *ptp = calloc(1, sizeof(*ptp)); + if(!ptp) + { + ERROR_PRTNAME(prt->bridge, prt, "Out of memory"); + return NULL; + } + ptp->port = prt; + ptp->tree = tree; + ptp->MSTID = tree->MSTID; + + ptp->state = BR_STATE_DISABLED; + /* 0x80 = default port priority (17.14 of 802.1D) */ + ptp->portId = __constant_cpu_to_be16(0x8000) | prt->port_number; + assign(ptp->AdminInternalPortPathCost, 0u); + assign(ptp->InternalPortPathCost, compute_pcost(GET_PORT_SPEED(prt))); + /* 802.1Q leaves portPriority and portTimes uninitialized */ + assign(ptp->portPriority, tree->BridgePriority); + assign(ptp->portTimes, tree->BridgeTimes); + + ptp->calledFromFlushRoutine = false; + + ptp_default_internal_vars(ptp); + + return ptp; +} + +/* External events */ + +bool MSTP_IN_bridge_create(bridge_t *br, __u8 *macaddr) +{ + tree_t *cist; + + if (!driver_create_bridge(br, macaddr)) + return false; + + /* Initialize all fields except sysdeps and anchor */ + INIT_LIST_HEAD(&br->ports); + INIT_LIST_HEAD(&br->trees); + br->bridgeEnabled = false; + memset(br->vid2fid, 0, sizeof(br->vid2fid)); + memset(br->fid2mstid, 0, sizeof(br->fid2mstid)); + assign(br->MstConfigId.s.selector, (__u8)0); + sprintf((char *)br->MstConfigId.s.configuration_name, + "%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", + macaddr[0], macaddr[1], macaddr[2], + macaddr[3], macaddr[4], macaddr[5]); + assign(br->MstConfigId.s.revision_level, __constant_cpu_to_be16(0)); + RecalcConfigDigest(br); /* set br->MstConfigId.s.configuration_digest */ + br->ForceProtocolVersion = protoRSTP; + assign(br->MaxHops, (__u8)20); /* 13.37.3 */ + assign(br->Forward_Delay, (__u8)15); /* 17.14 of 802.1D */ + assign(br->Max_Age, (__u8)20); /* 17.14 of 802.1D */ + assign(br->Transmit_Hold_Count, 6u); /* 17.14 of 802.1D */ + assign(br->Migrate_Time, 3u); /* 17.14 of 802.1D */ + assign(br->Ageing_Time, 300u);/* 8.8.3 Table 8-3 */ + assign(br->Hello_Time, (__u8)2); /* 17.14 of 802.1D */ + + bridge_default_internal_vars(br); + + /* Create CIST */ + if(!(cist = create_tree(br, macaddr, 0))) + return false; + list_add_tail(&cist->bridge_list, &br->trees); + + return true; +} + +bool MSTP_IN_port_create_and_add_tail(port_t *prt, __u16 portno) +{ + tree_t *tree; + per_tree_port_t *ptp, *nxt; + bridge_t *br = prt->bridge; + + if (!driver_create_port(prt, portno)) + return false; + + /* Initialize all fields except sysdeps and bridge */ + INIT_LIST_HEAD(&prt->trees); + prt->port_number = __cpu_to_be16(portno); + + assign(prt->AdminExternalPortPathCost, 0u); + /* Default for operP2P is false because by default AdminP2P + * says to auto-detect p2p state, and it is derived from duplex + * and initially port is in down state and in this down state + * duplex is set to false (half) */ + prt->AdminP2P = p2pAuto; + prt->operPointToPointMAC = false; + prt->portEnabled = false; + prt->restrictedRole = false; /* 13.25.14 */ + prt->restrictedTcn = false; /* 13.25.15 */ + assign(prt->ExternalPortPathCost, MAX_PATH_COST); /* 13.37.1 */ + prt->AdminEdgePort = false; /* 13.25 */ + prt->AutoEdge = true; /* 13.25 */ + prt->BpduGuardPort = false; + prt->BpduGuardError = false; + prt->NetworkPort = false; + prt->dontTxmtBpdu = false; + prt->bpduFilterPort = false; + prt->deleted = false; + + port_default_internal_vars(prt); + + /* Create PerTreePort structures for all existing trees */ + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(!(ptp = create_ptp(tree, prt))) + { + /* Remove and free all previously created entries in port's list */ + list_for_each_entry_safe(ptp, nxt, &prt->trees, port_list) + { + list_del(&ptp->port_list); + list_del(&ptp->tree_list); + free(ptp); + } + return false; + } + list_add_tail(&ptp->port_list, &prt->trees); + list_add_tail(&ptp->tree_list, &tree->ports); + } + + /* Add new port to the tail of the list in the bridge */ + /* NOTE: if one wants add port NOT to the tail of the list of ports, + * one should revise above loop (FOREACH_TREE_IN_BRIDGE) + * because it heavily depends on the fact that port is added to the tail. + */ + list_add_tail(&prt->br_list, &br->ports); + + prt_state_machines_begin(prt); + return true; +} + +void MSTP_IN_delete_port(port_t *prt) +{ + per_tree_port_t *ptp, *nxt; + bridge_t *br = prt->bridge; + + driver_delete_port(prt); + + prt->deleted = true; + if(prt->portEnabled) + { + prt->portEnabled = false; + br_state_machines_run(br); + } + + list_for_each_entry_safe(ptp, nxt, &prt->trees, port_list) + { + list_del(&ptp->port_list); + list_del(&ptp->tree_list); + free(ptp); + } + + list_del(&prt->br_list); + br_state_machines_run(br); +} + +void MSTP_IN_delete_bridge(bridge_t *br) +{ + tree_t *tree, *nxt_tree; + port_t *prt, *nxt_prt; + + driver_delete_bridge(br); + + br->bridgeEnabled = false; + + /* We SHOULD first delete all ports and only THEN delete all tree_t + * structures as the tree_t structure contains the head for the per-port + * list of tree data (tree_t.ports). + * If this list_head will be deleted before all the per_tree_ports + * bad things will happen ;) + */ + + list_for_each_entry_safe(prt, nxt_prt, &br->ports, br_list) + { + MSTP_IN_delete_port(prt); + free(prt); + } + + list_for_each_entry_safe(tree, nxt_tree, &br->trees, bridge_list) + { + list_del(&tree->bridge_list); + free(tree); + } +} + +void MSTP_IN_set_bridge_address(bridge_t *br, __u8 *macaddr) +{ + tree_t *tree; + bool changed = false; + + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(0 == memcmp(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN)) + continue; + changed = true; + memcpy(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN); + tree->BridgePriority.RootID = tree->BridgePriority.RRootID = + tree->BridgePriority.DesignatedBridgeID = tree->BridgeIdentifier; + } + + if(changed) + br_state_machines_begin(br); +} + +void MSTP_IN_set_bridge_enable(bridge_t *br, bool up) +{ + port_t *prt; + per_tree_port_t *ptp; + tree_t *tree; + + if(br->bridgeEnabled == up) + return; + br->bridgeEnabled = up; + + /* Reset all internal states and variables, + * except those which are user-configurable */ + bridge_default_internal_vars(br); + FOREACH_TREE_IN_BRIDGE(tree, br) + { + tree_default_internal_vars(tree); + } + FOREACH_PORT_IN_BRIDGE(prt, br) + { + /* NOTE: Don't check prt->deleted here, as it is imposible condition */ + /* NOTE: In the port_default_internal_vars() rapidAgeingWhile will be + * reset, so we should stop rapid ageing procedure here. + */ + if(prt->rapidAgeingWhile) + { + MSTP_OUT_set_ageing_time(prt, br->Ageing_Time); + } + port_default_internal_vars(prt); + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(BR_STATE_DISABLED != ptp->state) + { + MSTP_OUT_set_state(ptp, BR_STATE_DISABLED); + } + ptp_default_internal_vars(ptp); + } + } + br_state_machines_begin(br); +} + +void MSTP_IN_set_port_enable(port_t *prt, bool up, int speed, int duplex) +{ + __u32 computed_pcost, new_ExternalPathCost, new_InternalPathCost; + per_tree_port_t *ptp; + bool new_p2p; + bool changed = false; + + if(up) + { + computed_pcost = compute_pcost(speed); + new_ExternalPathCost = (0 == prt->AdminExternalPortPathCost) ? + computed_pcost + : prt->AdminExternalPortPathCost; + if(prt->ExternalPortPathCost != new_ExternalPathCost) + { + assign(prt->ExternalPortPathCost, new_ExternalPathCost); + changed = true; + } + FOREACH_PTP_IN_PORT(ptp, prt) + { + new_InternalPathCost = (0 == ptp->AdminInternalPortPathCost) ? + computed_pcost + : ptp->AdminInternalPortPathCost; + if(ptp->InternalPortPathCost != new_InternalPathCost) + { + assign(ptp->InternalPortPathCost, new_InternalPathCost); + changed = true; + } + } + + switch(prt->AdminP2P) + { + case p2pForceTrue: + new_p2p = true; + break; + case p2pForceFalse: + new_p2p = false; + break; + case p2pAuto: + default: + new_p2p = !!duplex; + break; + } + if(prt->operPointToPointMAC != new_p2p) + { + prt->operPointToPointMAC = new_p2p; + changed = true; + } + + if(!prt->portEnabled) + { + prt->portEnabled = true; + prt->BpduGuardError = false; + prt->BaInconsistent = false; + prt->num_rx_bpdu_filtered = 0; + prt->num_rx_bpdu = 0; + prt->num_rx_tcn = 0; + prt->num_tx_bpdu = 0; + prt->num_tx_tcn = 0; + changed = true; + /* When port is enabled, initialize bridge assurance timer, + * so that enough time is given before port is put in + * inconsistent state. + */ + updtbrAssuRcvdInfoWhile(prt); + } + } + else + { + if(prt->portEnabled) + { + prt->portEnabled = false; + changed = true; + } + } + + if(changed) + br_state_machines_run(prt->bridge); +} + +void MSTP_IN_one_second(bridge_t *br) +{ + port_t *prt; + tree_t *tree; + + ++(br->uptime); + + if(!br->bridgeEnabled) + return; + + FOREACH_TREE_IN_BRIDGE(tree, br) + if(!(tree->topology_change)) + ++(tree->time_since_topology_change); + + FOREACH_PORT_IN_BRIDGE(prt, br) + { + PTSM_tick(prt); + /* support for rapid ageing */ + if(prt->rapidAgeingWhile) + { + if((--(prt->rapidAgeingWhile)) == 0) + { + if(!prt->deleted) + MSTP_OUT_set_ageing_time(prt, br->Ageing_Time); + } + } + } + + br_state_machines_run(br); +} + +void MSTP_IN_all_fids_flushed(per_tree_port_t *ptp) +{ + bridge_t *br = ptp->port->bridge; + ptp->fdbFlush = false; + if(!br->bridgeEnabled) + return; + if(!ptp->calledFromFlushRoutine) + { + TCSM_run(ptp, false /* actual run */); + br_state_machines_run(br); + } +} + +/* NOTE: bpdu pointer is unaligned, but it works because + * bpdu_t is packed. Don't try to cast bpdu to non-packed type ;) + */ +void MSTP_IN_rx_bpdu(port_t *prt, bpdu_t *bpdu, int size) +{ + int mstis_size; + bridge_t *br = prt->bridge; + + ++(prt->num_rx_bpdu); + + if(prt->BpduGuardPort) + { + prt->BpduGuardError = true; + ERROR_PRTNAME(br, prt, + "Received BPDU on BPDU Guarded Port - Port Down"); + MSTP_OUT_shutdown_port(prt); + return; + } + + if(prt->bpduFilterPort) + { + LOG_PRTNAME(br, prt, + "Received BPDU on BPDU Filtered Port - discarded"); + ++(prt->num_rx_bpdu_filtered); + return; + } + + if(!br->bridgeEnabled) + { + INFO_PRTNAME(br, prt, "Received BPDU while bridge is disabled"); + return; + } + + if(prt->rcvdBpdu) + { + ERROR_PRTNAME(br, prt, "Port hasn't processed previous BPDU"); + return; + } + + /* 14.4 Validation */ + if((TCN_BPDU_SIZE > size) || (0 != bpdu->protocolIdentifier)) + { +bpdu_validation_failed: + INFO_PRTNAME(br, prt, "BPDU validation failed"); + return; + } + switch(bpdu->bpduType) + { + case bpduTypeTCN: + /* 14.4.b) */ + /* Valid TCN BPDU */ + bpdu->protocolVersion = protoSTP; + LOG_PRTNAME(br, prt, "received TCN BPDU"); + break; + case bpduTypeConfig: + /* 14.4.a) */ + if(CONFIG_BPDU_SIZE > size) + goto bpdu_validation_failed; + /* Valid Config BPDU */ + bpdu->protocolVersion = protoSTP; + LOG_PRTNAME(br, prt, "received Config BPDU%s", + (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "" + ); + break; + case bpduTypeRST: + if(protoRSTP == bpdu->protocolVersion) + { /* 14.4.c) */ + if(RST_BPDU_SIZE > size) + goto bpdu_validation_failed; + /* Valid RST BPDU */ + /* bpdu->protocolVersion = protoRSTP; */ + LOG_PRTNAME(br, prt, "received RST BPDU%s", + (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "" + ); + break; + } + if(protoMSTP > bpdu->protocolVersion) + goto bpdu_validation_failed; + /* Yes, 802.1Q-2005 says here to check if it contains + * "35 or more octets", not 36! (see 14.4.d).1) ) + * That's why I check size against CONFIG_BPDU_SIZE + * and not RST_BPDU_SIZE. + */ + if(CONFIG_BPDU_SIZE > size) + goto bpdu_validation_failed; + mstis_size = __be16_to_cpu(bpdu->version3_len) + - MST_BPDU_VER3LEN_WO_MSTI_MSGS; + if((MST_BPDU_SIZE_WO_MSTI_MSGS > size) || (0 != bpdu->version1_len) + || (0 > mstis_size) + || ((MAX_STANDARD_MSTIS * sizeof(msti_configuration_message_t)) + < mstis_size) + || (0 != (mstis_size % sizeof(msti_configuration_message_t))) + ) + { /* 14.4.d) */ + /* Valid RST BPDU */ + bpdu->protocolVersion = protoRSTP; + LOG_PRTNAME(br, prt, "received RST BPDU"); + break; + } + /* 14.4.e) */ + /* Valid MST BPDU */ + bpdu->protocolVersion = protoMSTP; + prt->rcvdBpduNumOfMstis = mstis_size + / sizeof(msti_configuration_message_t); + LOG_PRTNAME(br, prt, "received MST BPDU%s with %d MSTIs", + (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "", + prt->rcvdBpduNumOfMstis + ); + break; + default: + goto bpdu_validation_failed; + } + + if((protoSTP == bpdu->protocolVersion) && (bpduTypeTCN == bpdu->bpduType)) + { + ++(prt->num_rx_tcn); + } + else + { + if(bpdu->flags & (1 << offsetTc)) + ++(prt->num_rx_tcn); + } + + assign(prt->rcvdBpduData, *bpdu); + prt->rcvdBpdu = true; + + /* Reset bridge assurance on receipt of valid BPDU */ + if(prt->BaInconsistent) + { + prt->BaInconsistent = false; + INFO_PRTNAME(br, prt, "Clear Bridge assurance inconsistency"); + } + updtbrAssuRcvdInfoWhile(prt); + + br_state_machines_run(br); +} + +/* 12.8.1.1 Read CIST Bridge Protocol Parameters */ +void MSTP_IN_get_cist_bridge_status(bridge_t *br, CIST_BridgeStatus *status) +{ + tree_t *cist = GET_CIST_TREE(br); + assign(status->bridge_id, cist->BridgeIdentifier); + assign(status->time_since_topology_change, + cist->time_since_topology_change); + assign(status->topology_change_count, cist->topology_change_count); + status->topology_change = cist->topology_change; + strncpy(status->topology_change_port, cist->topology_change_port, + IFNAMSIZ); + strncpy(status->last_topology_change_port, cist->last_topology_change_port, + IFNAMSIZ); + assign(status->designated_root, cist->rootPriority.RootID); + assign(status->root_path_cost, + __be32_to_cpu(cist->rootPriority.ExtRootPathCost)); + assign(status->regional_root, cist->rootPriority.RRootID); + assign(status->internal_path_cost, + __be32_to_cpu(cist->rootPriority.IntRootPathCost)); + assign(status->root_port_id, cist->rootPortId); + assign(status->root_max_age, cist->rootTimes.Max_Age); + assign(status->root_forward_delay, cist->rootTimes.Forward_Delay); + assign(status->bridge_max_age, br->Max_Age); + assign(status->bridge_forward_delay, br->Forward_Delay); + assign(status->max_hops, br->MaxHops); + assign(status->tx_hold_count, br->Transmit_Hold_Count); + status->protocol_version = br->ForceProtocolVersion; + status->enabled = br->bridgeEnabled; + assign(status->bridge_hello_time, br->Hello_Time); + assign(status->Ageing_Time, br->Ageing_Time); +} + +/* 12.8.1.2 Read MSTI Bridge Protocol Parameters */ +void MSTP_IN_get_msti_bridge_status(tree_t *tree, MSTI_BridgeStatus *status) +{ + assign(status->bridge_id, tree->BridgeIdentifier); + assign(status->time_since_topology_change, + tree->time_since_topology_change); + assign(status->topology_change_count, tree->topology_change_count); + status->topology_change = tree->topology_change; + strncpy(status->topology_change_port, tree->topology_change_port, + IFNAMSIZ); + strncpy(status->last_topology_change_port, tree->last_topology_change_port, + IFNAMSIZ); + assign(status->regional_root, tree->rootPriority.RRootID); + assign(status->internal_path_cost, + __be32_to_cpu(tree->rootPriority.IntRootPathCost)); + assign(status->root_port_id, tree->rootPortId); +} + +/* 12.8.1.3 Set CIST Bridge Protocol Parameters */ +int MSTP_IN_set_cist_bridge_config(bridge_t *br, CIST_BridgeConfig *cfg) +{ + bool changed, changedBridgeTimes, init; + int r = 0; + __u8 new_forward_delay, new_max_age; + tree_t *tree; + port_t *prt; + per_tree_port_t *ptp; + + /* Firstly, validation */ + if(cfg->set_bridge_max_age) + { + new_max_age = cfg->bridge_max_age; + if((6 > new_max_age) || (40 < new_max_age)) + { + ERROR_BRNAME(br, + "Bridge Max Age must be between 6 and 40 seconds"); + r = -1; + } + } + else + new_max_age = br->Max_Age; + + if(cfg->set_bridge_forward_delay) + { + new_forward_delay = cfg->bridge_forward_delay; + if((4 > new_forward_delay) || (30 < new_forward_delay)) + { + ERROR_BRNAME(br, + "Bridge Forward Delay must be between 4 and 30 seconds"); + r = -1; + } + } + else + new_forward_delay = br->Forward_Delay; + + if(cfg->set_bridge_max_age || cfg->set_bridge_forward_delay) + { + if((2 * (new_forward_delay - 1)) < new_max_age) + { + ERROR_BRNAME(br, "Configured Bridge Times don't meet " + "2 * (Bridge Foward Delay - 1 second) >= Bridge Max Age"); + r = -1; + } + } + + if(cfg->set_protocol_version) + { + switch(cfg->protocol_version) + { + case protoSTP: + case protoRSTP: + case protoMSTP: + break; + default: + ERROR_BRNAME(br, "Bad protocol version (%d)", + cfg->protocol_version); + r = -1; + } + } + + if(cfg->set_tx_hold_count) + { + if((1 > cfg->tx_hold_count) || (10 < cfg->tx_hold_count)) + { + ERROR_BRNAME(br, + "Transmit Hold Count must be between 1 and 10 seconds"); + r = -1; + } + } + + if(cfg->set_max_hops) + { + if((6 > cfg->max_hops) || (40 < cfg->max_hops)) + { + ERROR_BRNAME(br, "Bridge Max Hops must be between 6 and 40"); + r = -1; + } + } + + if(cfg->set_bridge_hello_time) + { + if((1 > cfg->bridge_hello_time) || (10 < cfg->bridge_hello_time)) + { + ERROR_BRNAME(br, "Bridge Hello Time must be between 1 and 10"); + r = -1; + } + } + + if(cfg->set_bridge_ageing_time) + { + if((10 > cfg->bridge_ageing_time)||(1000000 < cfg->bridge_ageing_time)) + { + ERROR_BRNAME(br, + "Bridge Ageing Time must be between 10 and 1000000 seconds"); + r = -1; + } + } + + if(r) + return r; + + /* Secondly, do set */ + changed = changedBridgeTimes = init = false; + + if(cfg->set_bridge_max_age || cfg->set_bridge_forward_delay) + { + if(cmp(new_max_age, !=, br->Max_Age) + || cmp(new_forward_delay, !=, br->Forward_Delay) + ) + { + assign(br->Max_Age, new_max_age); + assign(br->Forward_Delay, new_forward_delay); + changed = changedBridgeTimes = true; + } + } + + if((cfg->set_protocol_version) + && (cfg->protocol_version != br->ForceProtocolVersion) + ) + { + br->ForceProtocolVersion = cfg->protocol_version; + changed = init = true; + } + + if(cfg->set_tx_hold_count) + { + if(cfg->tx_hold_count != br->Transmit_Hold_Count) + { + assign(br->Transmit_Hold_Count, cfg->tx_hold_count); + FOREACH_PORT_IN_BRIDGE(prt, br) + assign(prt->txCount, 0u); + changed = true; + } + } + + if(cfg->set_max_hops) + { + if(cfg->max_hops != br->MaxHops) + { + assign(br->MaxHops, cfg->max_hops); + changed = changedBridgeTimes = true; + } + } + + if(cfg->set_bridge_hello_time) + { + if(cfg->bridge_hello_time != br->Hello_Time) + { + INFO_BRNAME(br, "bridge hello_time new=%hhu, old=%hhu", + cfg->bridge_hello_time, br->Hello_Time); + assign(br->Hello_Time, cfg->bridge_hello_time); + changed = changedBridgeTimes = true; + } + } + + if(cfg->set_bridge_ageing_time) + { + if(cfg->bridge_ageing_time != br->Ageing_Time) + { + INFO_BRNAME(br, "bridge ageing_time new=%u, old=%u", + cfg->bridge_ageing_time, br->Ageing_Time); + assign(br->Ageing_Time, cfg->bridge_ageing_time); + } + } + + /* Thirdly, finalize changes */ + if(changedBridgeTimes) + { + FOREACH_TREE_IN_BRIDGE(tree, br) + { + assign(tree->BridgeTimes.remainingHops, br->MaxHops); + assign(tree->BridgeTimes.Forward_Delay, br->Forward_Delay); + assign(tree->BridgeTimes.Max_Age, br->Max_Age); + assign(tree->BridgeTimes.Hello_Time, br->Hello_Time); + /* Comment found in rstpd by Srinivas Aji: + * Do this for any change in BridgeTimes. + * Otherwise we fail UNH rstp.op_D test 3.2 since when administratively + * setting BridgeForwardDelay, etc, the values don't propagate from + * rootTimes to designatedTimes immediately without this change. + */ + FOREACH_PTP_IN_TREE(ptp, tree) + { + ptp->selected = false; + ptp->reselect = true; + /* TODO: change this when Hello_Time will be configurable + * per-port. For now, copy Bridge's Hello_Time + * to the port's Hello_Time. + */ + assign(ptp->portTimes.Hello_Time, br->Hello_Time); + } + } + } + + if(changed && br->bridgeEnabled) + { + if(init) + br_state_machines_begin(br); + else + br_state_machines_run(br); + } + + return 0; +} + +/* 12.8.1.4 Set MSTI Bridge Protocol Parameters */ +int MSTP_IN_set_msti_bridge_config(tree_t *tree, __u8 bridge_priority) +{ + per_tree_port_t *ptp; + __u8 valuePri; + + if(15 < bridge_priority) + { + ERROR_BRNAME(tree->bridge, + "MSTI %hu: Bridge Priority must be between 0 and 15", + __be16_to_cpu(tree->MSTID)); + return -1; + } + + valuePri = bridge_priority << 4; + if(GET_PRIORITY_FROM_IDENTIFIER(tree->BridgeIdentifier) == valuePri) + return 0; + SET_PRIORITY_IN_IDENTIFIER(valuePri, tree->BridgeIdentifier); + tree->BridgePriority.RootID = tree->BridgePriority.RRootID = + tree->BridgePriority.DesignatedBridgeID = tree->BridgeIdentifier; + /* 12.8.1.4.4 do not require reselect, but I think it is needed, + * because 12.8.1.3.4.c) requires it */ + FOREACH_PTP_IN_TREE(ptp, tree) + { + ptp->selected = false; + ptp->reselect = true; + } + return 0; +} + +/* 12.8.2.1 Read CIST Port Parameters */ +void MSTP_IN_get_cist_port_status(port_t *prt, CIST_PortStatus *status) +{ + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + /* 12.8.2.2.3 b) */ + status->uptime = (signed int)((prt->bridge)->uptime) + - (signed int)(cist->start_time); + status->state = cist->state; + assign(status->port_id, cist->portId); + assign(status->admin_external_port_path_cost, + prt->AdminExternalPortPathCost); + assign(status->external_port_path_cost, prt->ExternalPortPathCost); + assign(status->designated_root, cist->portPriority.RootID); + assign(status->designated_external_cost, + __be32_to_cpu(cist->portPriority.ExtRootPathCost)); + assign(status->designated_bridge, cist->portPriority.DesignatedBridgeID); + assign(status->designated_port, cist->portPriority.DesignatedPortID); + assign(status->designated_regional_root, cist->portPriority.RRootID); + assign(status->designated_internal_cost, + __be32_to_cpu(cist->portPriority.IntRootPathCost)); + status->tc_ack = prt->tcAck; + assign(status->port_hello_time, cist->portTimes.Hello_Time); + status->admin_edge_port = prt->AdminEdgePort; + status->auto_edge_port = prt->AutoEdge; + status->oper_edge_port = prt->operEdge; + status->enabled = prt->portEnabled; + status->admin_p2p = prt->AdminP2P; + status->oper_p2p = prt->operPointToPointMAC; + status->restricted_role = prt->restrictedRole; + status->restricted_tcn = prt->restrictedTcn; + status->role = cist->role; + status->disputed = cist->disputed; + assign(status->admin_internal_port_path_cost, + cist->AdminInternalPortPathCost); + assign(status->internal_port_path_cost, cist->InternalPortPathCost); + status->bpdu_guard_port = prt->BpduGuardPort; + status->bpdu_guard_error = prt->BpduGuardError; + status->network_port = prt->NetworkPort; + status->ba_inconsistent = prt->BaInconsistent; + status->bpdu_filter_port = prt->bpduFilterPort; + status->num_rx_bpdu_filtered = prt->num_rx_bpdu_filtered; + status->num_rx_bpdu = prt->num_rx_bpdu; + status->num_rx_tcn = prt->num_rx_tcn; + status->num_tx_bpdu = prt->num_tx_bpdu; + status->num_tx_tcn = prt->num_tx_tcn; + status->num_trans_fwd = prt->num_trans_fwd; + status->num_trans_blk = prt->num_trans_blk; + status->rcvdBpdu = prt->rcvdBpdu; + status->rcvdRSTP = prt->rcvdRSTP; + status->rcvdSTP = prt->rcvdSTP; + status->rcvdTcAck = prt->rcvdTcAck; + status->rcvdTcn = prt->rcvdTcn; + status->sendRSTP = prt->sendRSTP; +} + +/* 12.8.2.2 Read MSTI Port Parameters */ +void MSTP_IN_get_msti_port_status(per_tree_port_t *ptp, + MSTI_PortStatus *status) +{ + status->uptime = (signed int)((ptp->port->bridge)->uptime) + - (signed int)(ptp->start_time); + status->state = ptp->state; + assign(status->port_id, ptp->portId); + assign(status->admin_internal_port_path_cost, + ptp->AdminInternalPortPathCost); + assign(status->internal_port_path_cost, ptp->InternalPortPathCost); + assign(status->designated_regional_root, ptp->portPriority.RRootID); + assign(status->designated_internal_cost, + __be32_to_cpu(ptp->portPriority.IntRootPathCost)); + assign(status->designated_bridge, ptp->portPriority.DesignatedBridgeID); + assign(status->designated_port, ptp->portPriority.DesignatedPortID); + status->role = ptp->role; + status->disputed = ptp->disputed; +} + +/* 12.8.2.3 Set CIST port parameters */ +int MSTP_IN_set_cist_port_config(port_t *prt, CIST_PortConfig *cfg) +{ + bool changed; + __u32 new_ExternalPathCost; + bool new_p2p; + per_tree_port_t *cist; + bridge_t *br = prt->bridge; + + /* Firstly, validation */ + if(cfg->set_admin_p2p) + { + switch(cfg->admin_p2p) + { + case p2pAuto: + case p2pForceTrue: + case p2pForceFalse: + break; + default: + cfg->admin_p2p = p2pAuto; + } + } + + /* Secondly, do set */ + changed = false; + + if(cfg->set_admin_external_port_path_cost) + { + prt->AdminExternalPortPathCost = cfg->admin_external_port_path_cost; + new_ExternalPathCost = (0 == prt->AdminExternalPortPathCost) ? + compute_pcost(GET_PORT_SPEED(prt)) + : prt->AdminExternalPortPathCost; + if(prt->ExternalPortPathCost != new_ExternalPathCost) + { + assign(prt->ExternalPortPathCost, new_ExternalPathCost); + changed = true; + /* 12.8.2.3.4 */ + cist = GET_CIST_PTP_FROM_PORT(prt); + cist->selected = false; + cist->reselect = true; + } + } + + if(cfg->set_admin_p2p) + { + prt->AdminP2P = cfg->admin_p2p; + switch(prt->AdminP2P) + { + case p2pForceTrue: + new_p2p = true; + break; + case p2pForceFalse: + new_p2p = false; + break; + case p2pAuto: + default: + new_p2p = !!GET_PORT_DUPLEX(prt); + break; + } + if(prt->operPointToPointMAC != new_p2p) + { + prt->operPointToPointMAC = new_p2p; + changed = true; + } + } + + if(cfg->set_admin_edge_port) + { + if(prt->AdminEdgePort != cfg->admin_edge_port) + { + prt->AdminEdgePort = cfg->admin_edge_port; + BDSM_begin(prt); + changed = true; + } + } + + if(cfg->set_auto_edge_port) + { + if(prt->AutoEdge != cfg->auto_edge_port) + { + prt->AutoEdge = cfg->auto_edge_port; + changed = true; + } + } + + if(cfg->set_restricted_role) + { + if(prt->restrictedRole != cfg->restricted_role) + { + prt->restrictedRole = cfg->restricted_role; + changed = true; + } + } + + if(cfg->set_restricted_tcn) + { + if(prt->restrictedTcn != cfg->restricted_tcn) + { + prt->restrictedTcn = cfg->restricted_tcn; + changed = true; + } + } + + if(cfg->set_bpdu_guard_port) + { + if(prt->BpduGuardPort != cfg->bpdu_guard_port) + { + prt->BpduGuardPort = cfg->bpdu_guard_port; + INFO_PRTNAME(br, prt,"BpduGuardPort new=%d", prt->BpduGuardPort); + } + } + + if(cfg->set_network_port) + { + if(prt->NetworkPort != cfg->network_port) + { + prt->NetworkPort = cfg->network_port; + INFO_PRTNAME(br, prt, "NetworkPort new=%d", prt->NetworkPort); + /* When Network port config is removed and bridge assurance + * inconsistency is set, clear the inconsistency. + */ + if(!prt->NetworkPort && prt->BaInconsistent) + { + prt->BaInconsistent = false; + INFO_PRTNAME(br, prt, "Clear Bridge assurance inconsistency"); + } + changed = true; + } + } + + if(cfg->set_dont_txmt) + { + if(prt->dontTxmtBpdu != cfg->dont_txmt) + { + prt->dontTxmtBpdu = cfg->dont_txmt; + INFO_PRTNAME(br, prt, "donttxmt new=%d", prt->dontTxmtBpdu); + } + } + + if(cfg->set_bpdu_filter_port) + { + if (prt->bpduFilterPort != cfg->bpdu_filter_port) + { + prt->bpduFilterPort = cfg->bpdu_filter_port; + prt->num_rx_bpdu_filtered = 0; + INFO_PRTNAME(br, prt,"bpduFilterPort new=%d", prt->bpduFilterPort); + } + } + + if(changed && prt->portEnabled) + br_state_machines_run(prt->bridge); + + return 0; +} + +/* 12.8.2.4 Set MSTI port parameters */ +int MSTP_IN_set_msti_port_config(per_tree_port_t *ptp, MSTI_PortConfig *cfg) +{ + __u8 valuePri; + __u32 new_InternalPathCost; + bool changed = false; + port_t *prt = ptp->port; + bridge_t *br = prt->bridge; + + if(cfg->set_port_priority) + { + if(15 < cfg->port_priority) + { + ERROR_MSTINAME(br, prt, ptp, + "Port Priority must be between 0 and 15"); + return -1; + } + valuePri = cfg->port_priority << 4; + if(GET_PRIORITY_FROM_IDENTIFIER(ptp->portId) != valuePri) + { + SET_PRIORITY_IN_IDENTIFIER(valuePri, ptp->portId); + changed = true; + } + } + + if(cfg->set_admin_internal_port_path_cost) + { + ptp->AdminInternalPortPathCost = cfg->admin_internal_port_path_cost; + new_InternalPathCost = (0 == ptp->AdminInternalPortPathCost) ? + compute_pcost(GET_PORT_SPEED(prt)) + : ptp->AdminInternalPortPathCost; + if(ptp->InternalPortPathCost != new_InternalPathCost) + { + assign(ptp->InternalPortPathCost, new_InternalPathCost); + changed = true; + } + } + + if(changed && prt->portEnabled) + { + /* 12.8.2.4.4 */ + ptp->selected = false; + ptp->reselect = true; + + br_state_machines_run(br); + } + + return 0; +} + +/* 12.8.2.5 Force BPDU Migration Check */ +int MSTP_IN_port_mcheck(port_t *prt) +{ + bridge_t *br = prt->bridge; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + if(rstpVersion(br) && prt->portEnabled && br->bridgeEnabled) + { + prt->mcheck = true; + cist->proposing = true; + br_state_machines_run(br); + } + + return 0; +} + +/* 12.10.3.8 Set VID to FID allocation */ +bool MSTP_IN_set_vid2fid(bridge_t *br, __u16 vid, __u16 fid) +{ + bool vid2mstid_changed; + + if((vid < 1) || (vid > MAX_VID) || (fid > MAX_FID)) + { + ERROR_BRNAME(br, "Error allocating VID(%hu) to FID(%hu)", vid, fid); + return false; + } + + vid2mstid_changed = + (br->fid2mstid[fid] != br->fid2mstid[br->vid2fid[vid]]); + br->vid2fid[vid] = fid; + if(vid2mstid_changed) + { + RecalcConfigDigest(br); + br_state_machines_begin(br); + } + + return true; +} + +/* Set all VID-to-FID mappings at once */ +bool MSTP_IN_set_all_vids2fids(bridge_t *br, __u16 *vids2fids) +{ + bool vid2mstid_changed; + int vid; + + vid2mstid_changed = false; + for(vid = 1; vid <= MAX_VID; ++vid) + { + if(vids2fids[vid] > MAX_FID) + { /* Incorrect value == keep prev value */ + vids2fids[vid] = br->vid2fid[vid]; + continue; + } + if(br->fid2mstid[vids2fids[vid]] != br->fid2mstid[br->vid2fid[vid]]) + vid2mstid_changed = true; + } + memcpy(br->vid2fid, vids2fids, sizeof(br->vid2fid)); + if(vid2mstid_changed) + { + RecalcConfigDigest(br); + br_state_machines_begin(br); + } + + return true; +} + +/* 12.12.2.2 Set FID to MSTID allocation */ +bool MSTP_IN_set_fid2mstid(bridge_t *br, __u16 fid, __u16 mstid) +{ + tree_t *tree; + __be16 MSTID; + bool found; + int vid; + + if(fid > MAX_FID) + { + ERROR_BRNAME(br, "Bad FID(%hu)", fid); + return false; + } + + MSTID = __cpu_to_be16(mstid); + found = false; + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(tree->MSTID == MSTID) + { + found = true; + break; + } + } + if(!found) + { + ERROR_BRNAME(br, "MSTID(%hu) not found", mstid); + return false; + } + + if(br->fid2mstid[fid] != MSTID) + { + br->fid2mstid[fid] = MSTID; + /* check if there are VLANs using this FID */ + for(vid = 1; vid <= MAX_VID; ++vid) + { + if(br->vid2fid[vid] == fid) + { + RecalcConfigDigest(br); + br_state_machines_begin(br); + break; + } + } + } + + return true; +} + +/* Set all FID-to-MSTID mappings at once */ +bool MSTP_IN_set_all_fids2mstids(bridge_t *br, __u16 *fids2mstids) +{ + tree_t *tree; + __be16 MSTID[MAX_FID + 1]; + bool found, vid2mstid_changed; + int fid, vid; + __be16 prev_vid2mstid[MAX_VID + 2]; + + for(fid = 0; fid <= MAX_FID; ++fid) + { + if(fids2mstids[fid] > MAX_MSTID) + { /* Incorrect value == keep prev value */ + fids2mstids[fid] = __be16_to_cpu(MSTID[fid] = br->fid2mstid[fid]); + } + else + MSTID[fid] = __cpu_to_be16(fids2mstids[fid]); + found = false; + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(tree->MSTID == MSTID[fid]) + { + found = true; + break; + } + } + if(!found) + { + ERROR_BRNAME(br, + "Error allocating FID(%hu) to MSTID(%hu): MSTID not found", + fid, fids2mstids[fid]); + return false; + } + } + + for(vid = 1; vid <= MAX_VID; ++vid) + prev_vid2mstid[vid] = br->fid2mstid[br->vid2fid[vid]]; + memcpy(br->fid2mstid, MSTID, sizeof(br->fid2mstid)); + vid2mstid_changed = false; + for(vid = 1; vid <= MAX_VID; ++vid) + { + if(prev_vid2mstid[vid] != br->fid2mstid[br->vid2fid[vid]]) + { + vid2mstid_changed = true; + break; + } + } + if(vid2mstid_changed) + { + RecalcConfigDigest(br); + br_state_machines_begin(br); + } + + return true; +} + +/* 12.12.1.1 Read MSTI List */ +bool MSTP_IN_get_mstilist(bridge_t *br, int *num_mstis, __u16 *mstids) +{ + tree_t *tree; + + *num_mstis = 0; + FOREACH_TREE_IN_BRIDGE(tree, br) + { + mstids[*num_mstis] = __be16_to_cpu(tree->MSTID); + /* Check for "<", not for "<=", as num_mstis include CIST */ + if(MAX_IMPLEMENTATION_MSTIS < ++(*num_mstis)) + break; + } + + return true; +} + +/* 12.12.1.2 Create MSTI */ +bool MSTP_IN_create_msti(bridge_t *br, __u16 mstid) +{ + tree_t *tree, *tree_after, *new_tree; + per_tree_port_t *ptp, *nxt, *ptp_after, *new_ptp; + int num_of_mstis; + __be16 MSTID; + + if((mstid < 1) || (mstid > MAX_MSTID)) + { + ERROR_BRNAME(br, "Bad MSTID(%hu)", mstid); + return false; + } + + MSTID = __cpu_to_be16(mstid); + /* Find place where to insert new MSTID. + * Also check if such MSTID is already in the list. + * Also count existing mstis. + */ + tree_after = NULL; + num_of_mstis = 0; + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(tree->MSTID == MSTID) + { + INFO_BRNAME(br, "MSTID(%hu) is already in the list", mstid); + return true; /* yes, it is success */ + } + if(cmp(tree->MSTID, <, MSTID)) + tree_after = tree; + ++num_of_mstis; + } + /* Sanity check */ + if(NULL == tree_after) + { + ERROR_BRNAME(br, "Can't add MSTID(%hu): no CIST in the list", mstid); + return false; + } + /* End of Sanity check */ + + /* Check for "<", not for "<=", as num_of_mstis include CIST */ + if(MAX_IMPLEMENTATION_MSTIS < num_of_mstis) + { + ERROR_BRNAME(br, "Can't add MSTID(%hu): maximum count(%u) reached", + mstid, MAX_IMPLEMENTATION_MSTIS); + return false; + } + + /* Create new tree and its list of PerTreePort structures */ + tree = GET_CIST_TREE(br); + if(!(new_tree=create_tree(br,tree->BridgeIdentifier.s.mac_address,MSTID))) + return false; + + FOREACH_PTP_IN_TREE(ptp_after, tree_after) + { + if(!(new_ptp = create_ptp(new_tree, ptp_after->port))) + { + /* Remove and free all previously created entries in tree's list */ + list_for_each_entry_safe(ptp, nxt, &new_tree->ports, tree_list) + { + list_del(&ptp->port_list); + list_del(&ptp->tree_list); + free(ptp); + } + return false; + } + list_add(&new_ptp->port_list, &ptp_after->port_list); + list_add_tail(&new_ptp->tree_list, &new_tree->ports); + } + + list_add(&new_tree->bridge_list, &tree_after->bridge_list); + /* There are no FIDs allocated to this MSTID, so VID-to-MSTID mapping + * did not change. So, no need in RecalcConfigDigest. + * Just initialize state machines for this tree. + */ + tree_state_machines_begin(new_tree); + return true; +} + +/* 12.12.1.3 Delete MSTI */ +bool MSTP_IN_delete_msti(bridge_t *br, __u16 mstid) +{ + tree_t *tree; + per_tree_port_t *ptp, *nxt; + int fid; + bool found; + __be16 MSTID = __cpu_to_be16(mstid); + + if((mstid < 1) || (mstid > MAX_MSTID)) + { + ERROR_BRNAME(br, "Bad MSTID(%hu)", mstid); + return false; + } + + /* Check if there are FIDs associated with this MSTID */ + for(fid = 0; fid <= MAX_FID; ++fid) + { + if(br->fid2mstid[fid] == MSTID) + { + ERROR_BRNAME(br, + "Can't delete MSTID(%hu): there are FIDs allocated to it", + mstid); + return false; + } + } + + found = false; + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(tree->MSTID == MSTID) + { + found = true; + break; + } + } + if(!found) + { + INFO_BRNAME(br, "MSTID(%hu) is not in the list", mstid); + return true; /* yes, it is success */ + } + + list_del(&tree->bridge_list); + list_for_each_entry_safe(ptp, nxt, &tree->ports, tree_list) + { + list_del(&ptp->port_list); + list_del(&ptp->tree_list); + free(ptp); + } + free(tree); + + /* There are no FIDs allocated to this MSTID, so VID-to-MSTID mapping + * did not change. So, no need in RecalcConfigDigest. + * Give state machine a spare run, just for the case... + */ + br_state_machines_run(br); + return true; +} + +/* 12.12.3.4 Set MST Configuration Identifier Elements */ +void MSTP_IN_set_mst_config_id(bridge_t *br, __u16 revision, __u8 *name) +{ + __be16 valueRevision = __cpu_to_be16(revision); + bool changed = (0 != strncmp((char *)name, (char *)br->MstConfigId.s.configuration_name, + sizeof(br->MstConfigId.s.configuration_name)) + ) + || (valueRevision != br->MstConfigId.s.revision_level); + + if(changed) + { + assign(br->MstConfigId.s.revision_level, valueRevision); + memset(br->MstConfigId.s.configuration_name, 0, + sizeof(br->MstConfigId.s.configuration_name)); + strncpy((char *)br->MstConfigId.s.configuration_name, (char *)name, + sizeof(br->MstConfigId.s.configuration_name) - 1); + br_state_machines_begin(br); + } +} + +/* + * If hint_SetToYes == true, some tcWhile in this tree has non-zero value. + * If hint_SetToYes == false, some tcWhile in this tree has just became zero, + * so we should check all other tcWhile's in this tree. + */ +static void set_TopologyChange(tree_t *tree, bool hint_SetToYes, port_t *port) +{ + per_tree_port_t *ptp; + bool prev_tc_not_set = !tree->topology_change; + + if(hint_SetToYes) + { + tree->topology_change = true; + tree->time_since_topology_change = 0; + if(prev_tc_not_set) + ++(tree->topology_change_count); + strncpy(tree->topology_change_port, tree->last_topology_change_port, + IFNAMSIZ); + strncpy(tree->last_topology_change_port, port->sysdeps.name, IFNAMSIZ); + return; + } + + /* Some tcWhile has just became zero. Check if we need reset + * topology_change flag */ + if(prev_tc_not_set) + return; + + tree->topology_change = false; + FOREACH_PTP_IN_TREE(ptp, tree) + { + if(0 != ptp->tcWhile) + { + tree->topology_change = true; + tree->time_since_topology_change = 0; + return; + } + } +} + +/* Helper functions, compare two priority vectors */ +static bool samePriorityAndTimers(port_priority_vector_t *vec1, + port_priority_vector_t *vec2, + times_t *time1, + times_t *time2, + bool cist) +{ + if(cist) + { + if(cmp(time1->Forward_Delay, !=, time2->Forward_Delay)) + return false; + if(cmp(time1->Max_Age, !=, time2->Max_Age)) + return false; + if(cmp(time1->Message_Age, !=, time2->Message_Age)) + return false; + if(cmp(time1->Hello_Time, !=, time2->Hello_Time)) + return false; + + if(cmp(vec1->RootID, !=, vec2->RootID)) + return false; + if(cmp(vec1->ExtRootPathCost, !=, vec2->ExtRootPathCost)) + return false; + } + + if(cmp(time1->remainingHops, !=, time2->remainingHops)) + return false; + + if(cmp(vec1->RRootID, !=, vec2->RRootID)) + return false; + if(cmp(vec1->IntRootPathCost, !=, vec2->IntRootPathCost)) + return false; + if(cmp(vec1->DesignatedBridgeID, !=, vec2->DesignatedBridgeID)) + return false; + if(cmp(vec1->DesignatedPortID, !=, vec2->DesignatedPortID)) + return false; + + return true; +} + +static bool betterorsamePriority(port_priority_vector_t *vec1, + port_priority_vector_t *vec2, + port_identifier_t pId1, + port_identifier_t pId2, + bool cist) +{ + int result; + + if(cist) + { + if(0 < (result = _ncmp(vec1->RootID, vec2->RootID))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + if(0 < (result = _ncmp(vec1->ExtRootPathCost, vec2->ExtRootPathCost))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + } + + if(0 < (result = _ncmp(vec1->RRootID, vec2->RRootID))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + + if(0 < (result = _ncmp(vec1->IntRootPathCost, vec2->IntRootPathCost))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + + if(0 < (result = _ncmp(vec1->DesignatedBridgeID, vec2->DesignatedBridgeID))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + + if(0 < (result = _ncmp(vec1->DesignatedPortID, vec2->DesignatedPortID))) + return false; /* worse */ + else if(0 > result) + return true; /* better */ + /* The same. Check further. */ + + /* Port ID is a tie-breaker */ + return cmp(pId1, <=, pId2); +} + +/* 13.26.1 betterorsameInfo */ +static bool betterorsameInfo(per_tree_port_t *ptp, port_info_origin_t newInfoIs) +{ + if((ioReceived == newInfoIs) && (ioReceived == ptp->infoIs)) + return betterorsamePriority(&ptp->msgPriority, + &ptp->portPriority, + 0, 0, (0 == ptp->MSTID)); + else if((ioMine == newInfoIs) && (ioMine == ptp->infoIs)) + return betterorsamePriority(&ptp->designatedPriority, + &ptp->portPriority, + 0, 0, (0 == ptp->MSTID)); + return false; +} + +/* 13.26.2 clearAllRcvdMsgs */ +static bool clearAllRcvdMsgs(port_t *prt, bool dry_run) +{ + per_tree_port_t *ptp; + + if(dry_run) + { + FOREACH_PTP_IN_PORT(ptp, prt) + if(ptp->rcvdMsg) + return true; + return false; + } + + FOREACH_PTP_IN_PORT(ptp, prt) + ptp->rcvdMsg = false; + + return false; +} + +/* 13.26.3 clearReselectTree */ +static void clearReselectTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + ptp->reselect = false; +} + +/* 13.26.4 fromSameRegion */ +static bool fromSameRegion(port_t *prt) +{ + /* Check for rcvdRSTP is superfluous here */ + if((protoMSTP > prt->rcvdBpduData.protocolVersion)/* || (!prt->rcvdRSTP)*/) + return false; + return cmp(prt->bridge->MstConfigId, + ==, prt->rcvdBpduData.mstConfigurationIdentifier); +} + +/* 13.26.5 newTcWhile */ +static void newTcWhile(per_tree_port_t *ptp) +{ + if(0 != ptp->tcWhile) + return; + + tree_t *tree = ptp->tree; + port_t *prt = ptp->port; + + if(prt->sendRSTP) + { + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + ptp->tcWhile = cist->portTimes.Hello_Time + 1; + set_TopologyChange(tree, true, prt); + + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + return; + } + + times_t *times = &tree->rootTimes; + + ptp->tcWhile = times->Max_Age + times->Forward_Delay; + set_TopologyChange(tree, true, prt); +} + +/* 13.26.6 rcvInfo */ +static port_info_t rcvInfo(per_tree_port_t *ptp) +{ + msti_configuration_message_t *msti_msg; + per_tree_port_t *ptp_1; + bool roleIsDesignated, cist; + bool msg_Better_port, msg_SamePriorityAndTimers_port; + port_priority_vector_t *mPri = &(ptp->msgPriority); + times_t *mTimes = &(ptp->msgTimes); + port_t *prt = ptp->port; + bpdu_t *b = &(prt->rcvdBpduData); + + if(bpduTypeTCN == b->bpduType) + { + prt->rcvdTcn = true; + FOREACH_PTP_IN_PORT(ptp_1, prt) + ptp_1->rcvdTc = true; + return OtherInfo; + } + + if(0 == ptp->MSTID) + { /* CIST */ + if(protoSTP != b->protocolVersion) + { + switch(BPDU_FLAGS_ROLE_GET(b->flags)) + { + case encodedRoleAlternateBackup: + case encodedRoleRoot: + roleIsDesignated = false; + break; + case encodedRoleDesignated: + roleIsDesignated = true; + break; + case encodedRoleMaster: + /* 802.1D-2004 S9.2.9 P61. The Unknown value of Port Role + * cannot be generated by a valid implementation; however, + * this value is accepted on receipt. roleMaster in MSTP is + * roleUnknown in RSTP. + * NOTE.If the Unknown value of the Port Role parameter is + * received, the state machines will effectively treat the RST + * BPDU as if it were a Configuration BPDU + */ + if(protoRSTP == b->protocolVersion) + { + roleIsDesignated = true; + break; + } + else + { + return OtherInfo; + } + break; + default: + return OtherInfo; + } + } + else + { /* 13.26.6.NOTE: A Configuration BPDU implicitly conveys a + * Designated Port Role */ + roleIsDesignated = true; + } + cist = true; + + assign(mPri->RRootID, b->cistRRootID); + assign(mPri->DesignatedPortID, b->cistPortID); + assign(mPri->RootID, b->cistRootID); + assign(mPri->ExtRootPathCost, b->cistExtRootPathCost); + /* messageTimes */ +#define NEAREST_WHOLE_SECOND(msgTime) \ + ((128 > msgTime[1]) ? msgTime[0] : msgTime[0] + 1) + mTimes->Forward_Delay = NEAREST_WHOLE_SECOND(b->ForwardDelay); + mTimes->Max_Age = NEAREST_WHOLE_SECOND(b->MaxAge); + mTimes->Message_Age = NEAREST_WHOLE_SECOND(b->MessageAge); + mTimes->Hello_Time = NEAREST_WHOLE_SECOND(b->HelloTime); + if(protoMSTP > b->protocolVersion) + { /* STP Configuration BPDU or RST BPDU */ + assign(mPri->IntRootPathCost, __constant_cpu_to_be32(0)); + assign(mPri->DesignatedBridgeID, b->cistRRootID); + /* messageTimes.remainingHops */ + assign(mTimes->remainingHops, prt->bridge->MaxHops); + } + else + { /* MST BPDU */ + assign(mPri->IntRootPathCost, b->cistIntRootPathCost); + assign(mPri->DesignatedBridgeID, b->cistBridgeID); + /* messageTimes.remainingHops */ + assign(mTimes->remainingHops, b->cistRemainingHops); + } + } + else + { /* MSTI */ + if(protoMSTP > b->protocolVersion) + return OtherInfo; + msti_msg = ptp->rcvdMstiConfig; + switch(BPDU_FLAGS_ROLE_GET(msti_msg->flags)) + { + case encodedRoleAlternateBackup: + case encodedRoleRoot: + roleIsDesignated = false; + break; + case encodedRoleDesignated: + roleIsDesignated = true; + break; + default: + return OtherInfo; + } + cist = false; + + assign(mPri->RRootID, msti_msg->mstiRRootID); + assign(mPri->IntRootPathCost, msti_msg->mstiIntRootPathCost); + /* Build MSTI DesignatedBridgeID */ + assign(mPri->DesignatedBridgeID, b->cistBridgeID); + assign(mPri->DesignatedBridgeID.s.priority, ptp->MSTID); + SET_PRIORITY_IN_IDENTIFIER(msti_msg->bridgeIdentifierPriority, + mPri->DesignatedBridgeID); + /* Build MSTI DesignatedPortID */ + assign(mPri->DesignatedPortID, b->cistPortID); + SET_PRIORITY_IN_IDENTIFIER(msti_msg->portIdentifierPriority, + mPri->DesignatedPortID); + /* messageTimes */ + assign(mTimes->remainingHops, msti_msg->remainingHops); + } + + msg_Better_port = !betterorsamePriority(&(ptp->portPriority), mPri, + 0, 0, cist); + if(roleIsDesignated) + { + /* a).1) */ + if(msg_Better_port + || ((0 == memcmp(mPri->DesignatedBridgeID.s.mac_address, + ptp->portPriority.DesignatedBridgeID.s.mac_address, + ETH_ALEN) + ) + && (0 == ((mPri->DesignatedPortID + ^ ptp->portPriority.DesignatedPortID + ) & __constant_cpu_to_be16(0x0FFF) + ) + ) + ) + ) + return SuperiorDesignatedInfo; + + /* a).2) */ + /* We already know that msgPriority _IS_NOT_BETTER_than portPriority. + * So, if msgPriority _IS_SAME_OR_BETTER_than portPriority then + * msgPriority _IS_SAME_as portPriority. + */ + msg_SamePriorityAndTimers_port = + samePriorityAndTimers(mPri, &(ptp->portPriority), + mTimes, &(ptp->portTimes), + cist); + if((!msg_SamePriorityAndTimers_port) + && betterorsamePriority(mPri, &(ptp->portPriority), 0, 0, cist) + ) + return SuperiorDesignatedInfo; + + /* b) */ + if(msg_SamePriorityAndTimers_port && (ioReceived == ptp->infoIs)) + return RepeatedDesignatedInfo; + + /* c) */ + return InferiorDesignatedInfo; + } + + /* d) */ + if(!msg_Better_port) + return InferiorRootAlternateInfo; + + return OtherInfo; +} + +/* 13.26.7 recordAgreement */ +static void recordAgreement(per_tree_port_t *ptp) +{ + bool cist_agreed, cist_proposing; + per_tree_port_t *cist; + port_t *prt = ptp->port; + bpdu_t *b = &(prt->rcvdBpduData); + + if(0 == ptp->MSTID) + { /* CIST */ + if(rstpVersion(prt->bridge) && prt->operPointToPointMAC + && (b->flags & (1 << offsetAgreement)) + ) + { + ptp->agreed = true; + ptp->proposing = false; + } + else + ptp->agreed = false; + cist_agreed = ptp->agreed; + cist_proposing = ptp->proposing; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + ptp->agreed = cist_agreed; + ptp->proposing = cist_proposing; + } + return; + } + /* MSTI */ + cist = GET_CIST_PTP_FROM_PORT(prt); + if(prt->operPointToPointMAC + && cmp(b->cistRootID, ==, cist->portPriority.RootID) + && cmp(b->cistExtRootPathCost, ==, cist->portPriority.ExtRootPathCost) + && cmp(b->cistRRootID, ==, cist->portPriority.RRootID) + && (ptp->rcvdMstiConfig->flags & (1 << offsetAgreement)) + ) + { + ptp->agreed = true; + ptp->proposing = false; + } + else + ptp->agreed = false; +} + +/* 13.26.8 recordDispute */ +static void recordDispute(per_tree_port_t *ptp) +{ + port_t *prt; + + if(0 == ptp->MSTID) + { /* CIST */ + prt = ptp->port; + /* 802.1Q-2005(-2011) is somewhat unclear for the case + * (!prt->rcvdInternal): if we should record dispute for all MSTIs + * unconditionally or only when CIST Learning flag is set in BPDU. + * I guess that in this case MSTIs should be in sync with CIST + * so record dispute for the MSTIs only when the same is done for CIST. + * Additional supporting argument to this guess is that in + * setTcFlags() we do the same. + * But that is only a guess and I could be wrong here ;) + */ + if(prt->rcvdBpduData.flags & (1 << offsetLearnig)) + { + ptp->disputed = true; + ptp->agreed = false; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + ptp->disputed = true; + ptp->agreed = false; + } + } + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetLearnig)) + { + ptp->disputed = true; + ptp->agreed = false; + } +} + +/* 13.26.9 recordMastered */ +static void recordMastered(per_tree_port_t *ptp) +{ + port_t *prt = ptp->port; + + if(0 == ptp->MSTID) + { /* CIST */ + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + ptp->mastered = false; + return; + } + /* MSTI */ + ptp->mastered = prt->operPointToPointMAC + && (ptp->rcvdMstiConfig->flags & (1 << offsetMaster)); +} + +/* 13.26.f) recordPriority */ +static void recordPriority(per_tree_port_t *ptp) +{ + assign(ptp->portPriority, ptp->msgPriority); +} + +/* 13.26.10 recordProposal */ +static void recordProposal(per_tree_port_t *ptp) +{ + bool cist_proposed; + port_t *prt; + + /* 802.1Q-2005 says to check if received message conveys + * a Designated Port Role. But there is no need in this check, + * as it is always true. This function is called only in two states: + * PISM_SUPERIOR_DESIGNATED and PISM_REPEATED_DESIGNATED, which + * can be entered only if rcvInfo returns + * SuperiorDesignatedInfo or RepeatedDesignatedInfo. + * Which in turn can only happen if message conveys designated role + * (see rcvInfo). + */ + if(0 == ptp->MSTID) + { /* CIST */ + prt = ptp->port; + if(prt->rcvdBpduData.flags & (1 << offsetProposal)) + ptp->proposed = true; + cist_proposed = ptp->proposed; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + ptp->proposed = cist_proposed; + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetProposal)) + ptp->proposed = true; +} + +/* 13.26.11 recordTimes */ +static void recordTimes(per_tree_port_t *ptp) +{ + /* 802.1Q-2005 and 802.1D-2004 both say that we have to copy + * Hello_Time from msgTimes to portTimes. + * 802.1Q-2011, on the other hand, says that Hello_Time should be set + * to the default here. + * As we have configurable Hello_Time, I choose the third option: + * preserve the configured Hello_Time, It is in accordance with the + * spirit of 802.1Q-2011, if we allow Hello_Time to be configurable. + */ + __u8 prev_Hello_Time = 0; + assign(prev_Hello_Time, ptp->portTimes.Hello_Time); + assign(ptp->portTimes, ptp->msgTimes); + assign(ptp->portTimes.Hello_Time, prev_Hello_Time); +} + +/* 13.24.s) + 17.19.7 of 802.1D : fdbFlush */ +static void set_fdbFlush(per_tree_port_t *ptp) +{ + port_t *prt = ptp->port; + + if(prt->operEdge || prt->deleted) + { + ptp->fdbFlush = false; + return; + } + + bridge_t *br = prt->bridge; + + if(rstpVersion(br)) + { + ptp->fdbFlush = true; + ptp->calledFromFlushRoutine = true; + MSTP_OUT_flush_all_fids(ptp); + ptp->calledFromFlushRoutine = false; + } + else + { + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + unsigned int FwdDelay = cist->designatedTimes.Forward_Delay; + /* Initiate rapid ageing */ + MSTP_OUT_set_ageing_time(prt, FwdDelay); + assign(prt->rapidAgeingWhile, FwdDelay); + ptp->fdbFlush = false; + } +} + +/* 13.26.12 setRcvdMsgs */ +static void setRcvdMsgs(port_t *prt) +{ + msti_configuration_message_t *msti_msg; + int i; + __be16 msg_MSTID; + bool found; + per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); + ptp->rcvdMsg = true; + + /* 802.1Q-2005 says: + * "Make the received CST or CIST message available to the CIST Port + * Information state machines" + * No need to do something special here, we already have rcvdBpduData. + */ + + if(prt->rcvdInternal) + { + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + found = false; + /* Find if message for this MSTI is conveyed in the BPDU */ + for(i = 0, msti_msg = prt->rcvdBpduData.mstConfiguration; + i < prt->rcvdBpduNumOfMstis; + ++i, ++msti_msg) + { + msg_MSTID = msti_msg->mstiRRootID.s.priority + & __constant_cpu_to_be16(0x0FFF); + if(msg_MSTID == ptp->MSTID) + { + found = true; + break; + } + } + if(found) + { + ptp->rcvdMsg = true; + /* 802.1Q-2005 says: + * "Make available each MSTI message and the common parts of + * the CIST message priority (the CIST Root Identifier, + * External Root Path Cost and Regional Root Identifier) + * to the Port Information state machine for that MSTI" + * We set pointer to the MSTI configuration message for + * fast access, while do not anything special for common + * parts of the message, as the whole message is available + * in rcvdBpduData. + */ + ptp->rcvdMstiConfig = msti_msg; + } + } + } +} + +/* 13.26.13 setReRootTree */ +static void setReRootTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + ptp->reRoot = true; +} + +/* 13.26.14 setSelectedTree */ +static void setSelectedTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + /* + * 802.1Q-2005 says that I should check "reselect" var + * and take no action if it is "true" for any of the ports. + * But there is no need in this check as setSelectedTree is called + * only from PRSSM_to_ROLE_SELECTION, which is atomic, and it is called + * in this sequence (13.33): + * clearReselectTree(tree); + * updtRolesTree(tree); + * setSelectedTree(tree); + * And we know that clearReselectTree resets "reselect" for all ports + * and updtRolesTree() does not change value of "reselect". + */ + FOREACH_PTP_IN_TREE(ptp, tree) + ptp->selected = true; +} + +/* 13.26.15 setSyncTree */ +static void setSyncTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + ptp->sync = true; +} + +/* 13.26.16 setTcFlags */ +static void setTcFlags(per_tree_port_t *ptp) +{ + __u8 cistFlags; + port_t *prt; + + if(0 == ptp->MSTID) + { /* CIST */ + prt = ptp->port; + cistFlags = prt->rcvdBpduData.flags; + if(cistFlags & (1 << offsetTcAck)) + prt->rcvdTcAck = true; + if(cistFlags & (1 << offsetTc)) + { + ptp->rcvdTc = true; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + ptp->proposed = true; + } + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetTc)) + ptp->rcvdTc = true; +} + +/* 13.26.17 setTcPropTree */ +static void setTcPropTree(per_tree_port_t *ptp) +{ + per_tree_port_t *ptp_1; + + if(ptp->port->restrictedTcn) + return; + + FOREACH_PTP_IN_TREE(ptp_1, ptp->tree) + { + if(ptp != ptp_1) + ptp_1->tcProp = true; + } +} + +/* 13.26.18 syncMaster */ +static void syncMaster(bridge_t *br) +{ + per_tree_port_t *ptp; + tree_t *tree = GET_CIST_TREE(br); + + /* For each MSTI */ + list_for_each_entry_continue(tree, &br->trees, bridge_list) + { + FOREACH_PTP_IN_TREE(ptp, tree) + { + /* for each Port that has infoInternal set */ + if(ptp->port->infoInternal) + { + ptp->agree = false; + ptp->agreed = false; + ptp->synced = false; + ptp->sync = true; + } + } + } +} + +/* 13.26.19 txConfig */ +static void txConfig(port_t *prt) +{ + bpdu_t b; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) + return; + + b.protocolIdentifier = 0; + b.protocolVersion = protoSTP; + b.bpduType = bpduTypeConfig; + /* Standard says "tcWhile ... for the Port". Which one tcWhile? + * I guess that this means tcWhile for the CIST. + * But that is only a guess and I could be wrong here ;) + */ + b.flags = (0 != cist->tcWhile) ? (1 << offsetTc) : 0; + if(prt->tcAck) + b.flags |= (1 << offsetTcAck); + assign(b.cistRootID, cist->designatedPriority.RootID); + assign(b.cistExtRootPathCost, cist->designatedPriority.ExtRootPathCost); + assign(b.cistRRootID, cist->designatedPriority.DesignatedBridgeID); + assign(b.cistPortID, cist->designatedPriority.DesignatedPortID); + b.MessageAge[0] = cist->designatedTimes.Message_Age; + b.MessageAge[1] = 0; + b.MaxAge[0] = cist->designatedTimes.Max_Age; + b.MaxAge[1] = 0; + b.HelloTime[0] = cist->portTimes.Hello_Time; /* ! use portTimes ! */ + b.HelloTime[1] = 0; + b.ForwardDelay[0] = cist->designatedTimes.Forward_Delay; + b.ForwardDelay[1] = 0; + + MSTP_OUT_tx_bpdu(prt, &b, CONFIG_BPDU_SIZE); +} + +static inline __u8 message_role_from_port_role(per_tree_port_t *ptp) +{ + switch(ptp->role) + { + case roleRoot: + return encodedRoleRoot; + case roleDesignated: + return encodedRoleDesignated; + case roleAlternate: + case roleBackup: + return encodedRoleAlternateBackup; + case roleMaster: + return encodedRoleMaster; + default: + ERROR_PRTNAME(ptp->port->bridge, ptp->port, + "Attempt to send from port with Disabled role"); + return encodedRoleAlternateBackup; + } +} + +/* 802.1Q-2005: 13.26.20 txMstp + * 802.1Q-2011: 13.27.27 txRstp + */ +static void txMstp(port_t *prt) +{ + bpdu_t b; + bridge_t *br = prt->bridge; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + int msti_msgs_total_size; + per_tree_port_t *ptp; + msti_configuration_message_t *msti_msg; + + if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) + return; + + b.protocolIdentifier = 0; + b.bpduType = bpduTypeRST; + /* Standard says "{tcWhile, agree, proposing} ... for the Port". + * Which one {tcWhile, agree, proposing}? + * I guess that this means {tcWhile, agree, proposing} for the CIST. + * But that is only a guess and I could be wrong here ;) + */ + b.flags = BPDU_FLAGS_ROLE_SET(message_role_from_port_role(cist)); + if(0 != cist->tcWhile) + b.flags |= (1 << offsetTc); + if(cist->proposing) + b.flags |= (1 << offsetProposal); + if(cist->learning) + b.flags |= (1 << offsetLearnig); + if(cist->forwarding) + b.flags |= (1 << offsetForwarding); + if(cist->agree) + b.flags |= (1 << offsetAgreement); + assign(b.cistRootID, cist->designatedPriority.RootID); + assign(b.cistExtRootPathCost, cist->designatedPriority.ExtRootPathCost); + assign(b.cistRRootID, cist->designatedPriority.RRootID); + assign(b.cistPortID, cist->designatedPriority.DesignatedPortID); + b.MessageAge[0] = cist->designatedTimes.Message_Age; + b.MessageAge[1] = 0; + b.MaxAge[0] = cist->designatedTimes.Max_Age; + b.MaxAge[1] = 0; + b.HelloTime[0] = cist->portTimes.Hello_Time; /* ! use portTimes ! */ + b.HelloTime[1] = 0; + b.ForwardDelay[0] = cist->designatedTimes.Forward_Delay; + b.ForwardDelay[1] = 0; + + b.version1_len = 0; + + if(br->ForceProtocolVersion < protoMSTP) + { + b.protocolVersion = protoRSTP; + MSTP_OUT_tx_bpdu(prt, &b, RST_BPDU_SIZE); + return; + } + + b.protocolVersion = protoMSTP; + + /* MST specific fields */ + assign(b.mstConfigurationIdentifier, br->MstConfigId); + assign(b.cistIntRootPathCost, cist->designatedPriority.IntRootPathCost); + assign(b.cistBridgeID, cist->designatedPriority.DesignatedBridgeID); + assign(b.cistRemainingHops, cist->designatedTimes.remainingHops); + + msti_msgs_total_size = 0; + ptp = cist; + msti_msg = b.mstConfiguration; + /* 13.26.20.f) requires that msti configs should be inserted in + * MSTID order. This is met by inserting trees in port's list of trees + * in sorted (by MSTID) order (see MSTP_IN_create_msti) */ + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + msti_msg->flags = + BPDU_FLAGS_ROLE_SET(message_role_from_port_role(ptp)); + if(0 != ptp->tcWhile) + msti_msg->flags |= (1 << offsetTc); + if(ptp->proposing) + msti_msg->flags |= (1 << offsetProposal); + if(ptp->learning) + msti_msg->flags |= (1 << offsetLearnig); + if(ptp->forwarding) + msti_msg->flags |= (1 << offsetForwarding); + if(ptp->agree) + msti_msg->flags |= (1 << offsetAgreement); + if(ptp->master) + msti_msg->flags |= (1 << offsetMaster); + assign(msti_msg->mstiRRootID, ptp->designatedPriority.RRootID); + assign(msti_msg->mstiIntRootPathCost, + ptp->designatedPriority.IntRootPathCost); + msti_msg->bridgeIdentifierPriority = + GET_PRIORITY_FROM_IDENTIFIER(ptp->designatedPriority.DesignatedBridgeID); + msti_msg->portIdentifierPriority = + GET_PRIORITY_FROM_IDENTIFIER(ptp->designatedPriority.DesignatedPortID); + assign(msti_msg->remainingHops, ptp->designatedTimes.remainingHops); + + msti_msgs_total_size += sizeof(msti_configuration_message_t); + ++msti_msg; + } + + assign(b.version3_len, __cpu_to_be16(MST_BPDU_VER3LEN_WO_MSTI_MSGS + + msti_msgs_total_size)); + MSTP_OUT_tx_bpdu(prt, &b, MST_BPDU_SIZE_WO_MSTI_MSGS + + msti_msgs_total_size); +} + +/* 13.26.a) txTcn */ +static void txTcn(port_t *prt) +{ + bpdu_t b; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) + return; + + b.protocolIdentifier = 0; + b.protocolVersion = protoSTP; + b.bpduType = bpduTypeTCN; + + MSTP_OUT_tx_bpdu(prt, &b, TCN_BPDU_SIZE); +} + +/* 13.26.21 updtBPDUVersion */ +static void updtBPDUVersion(port_t *prt) +{ + if(protoRSTP <= prt->rcvdBpduData.protocolVersion) + prt->rcvdRSTP = true; + else + prt->rcvdSTP = true; +} + +/* 13.26.22 updtRcvdInfoWhile */ +static void updtRcvdInfoWhile(per_tree_port_t *ptp) +{ + port_t *prt = ptp->port; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + unsigned int Message_Age = cist->portTimes.Message_Age; + unsigned int Max_Age = cist->portTimes.Max_Age; + unsigned int Hello_Time = cist->portTimes.Hello_Time; + + /* NOTE: 802.1Q-2005(-2011) says that we should use + * "remainingHops ... from the CIST's portTimes parameter" + * As for me this is clear oversight in the standard, + * the remainingHops should be taken form the port's own portTimes, + * not from CIST's. After all, if we don't use port's own + * remainingHops here, they aren't used anywhere at all. + * Besides, there is a scenario which breaks if we use CIST's + * remainingHops here: + * 1) Connect two switches (SW1,SW2) with two ports, thus forming a loop + * 2) Configure them to be in the same region, with two trees: + * 0 (CIST) and 1. + * 3) at SW1# mstpctl settreeprio br0 1 4 + * SW1 becomes regional root in tree 1 + * 4) at SW2# mstpctl settreeprio br0 1 14 + * 5) at SW1# mstpctl settreeprio br0 1 9 + * + * And now we have the classic "count-to-infinity" problem when the old + * info ("Regional Root is SW1 with priority 4") circulates in the loop, + * because it is better than current info ("Regional Root is SW1 with + * priority 9"). The only way to get rid of that old info is + * to age it out by the means of remainingHops counter. + * In this situation we certainly must use counter from tree 1, + * not CIST's. + */ + if((!prt->rcvdInternal && ((Message_Age + 1) <= Max_Age)) + || (prt->rcvdInternal && (ptp->portTimes.remainingHops > 1)) + ) + ptp->rcvdInfoWhile = 3 * Hello_Time; + else + ptp->rcvdInfoWhile = 0; +} + +static void updtbrAssuRcvdInfoWhile(port_t *prt) +{ + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + prt->brAssuRcvdInfoWhile = 3 * cist->portTimes.Hello_Time; +} + +/* 13.26.24 updtRolesDisabledTree */ +static void updtRolesDisabledTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + ptp->selectedRole = roleDisabled; +} + +/* Aux function, not in standard. + * Sets reselect for all MSTIs in the case CIST state for the port changes + */ +static void reselectMSTIs(port_t *prt) +{ + per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); + + /* For each non-CIST ptp */ + list_for_each_entry_continue(ptp, &prt->trees, port_list) + ptp->reselect = true; +} + +/* 13.26.23 updtRolesTree */ +static void updtRolesTree(tree_t *tree) +{ + per_tree_port_t *ptp, *root_ptp = NULL; + port_priority_vector_t root_path_priority; + bridge_identifier_t prevRRootID = tree->rootPriority.RRootID; + __be32 prevExtRootPathCost = tree->rootPriority.ExtRootPathCost; + bool cist = (0 == tree->MSTID); + + /* a), b) Select new root priority vector = {rootPriority, rootPortId} */ + /* Initial value = bridge priority vector = {BridgePriority, 0} */ + assign(tree->rootPriority, tree->BridgePriority); + assign(tree->rootPortId, __constant_cpu_to_be16(0)); + /* Now check root path priority vectors of all ports in tree and see if + * there is a better vector */ + FOREACH_PTP_IN_TREE(ptp, tree) + { + port_t *prt = ptp->port; + /* 802.1Q says to calculate root priority vector only if port + * is not Disabled, but check (infoIs == ioReceived) covers + * the case (infoIs != ioDisabled). + */ + if((ioReceived == ptp->infoIs) && !prt->restrictedRole + && cmp(ptp->portPriority.DesignatedBridgeID, !=, + tree->BridgeIdentifier) + ) + { + root_path_priority = ptp->portPriority; + if(prt->rcvdInternal) + { + assign(root_path_priority.IntRootPathCost, + __cpu_to_be32(__be32_to_cpu(root_path_priority.IntRootPathCost) + + ptp->InternalPortPathCost) + ); + } + else if(cist) /* Yes, this check might be superfluous, + * but I want to be on the safe side */ + { + assign(root_path_priority.ExtRootPathCost, + __cpu_to_be32(__be32_to_cpu(root_path_priority.ExtRootPathCost) + + prt->ExternalPortPathCost) + ); + assign(root_path_priority.RRootID, tree->BridgeIdentifier); + assign(root_path_priority.IntRootPathCost, + __constant_cpu_to_be32(0)); + } + if(betterorsamePriority(&root_path_priority, &tree->rootPriority, + ptp->portId, tree->rootPortId, cist)) + { + assign(tree->rootPriority, root_path_priority); + assign(tree->rootPortId, ptp->portId); + root_ptp = ptp; + } + } + } + + /* 802.1q-2005 says, that at some point we need compare portTimes with + * "... one for the Root Port ...". Bad IEEE! Why not mention explicit + * var names??? (see 13.26.23.g) for instance) + * These comparisons happen three times, twice in clause g) + * and once in clause i). Look for samePriorityAndTimers() calls. + * So, now I should guess what will work for the "times for the Root Port". + * Thanks to Rajani's experiments I know for sure that I should use + * designatedTimes here. Thank you, Rajani! + * NOTE: Both Alex Rozin (author of rstplib) and Srinivas Aji (author + * of rstpd) also compare portTimes with designatedTimes. + */ + + /* c) Set new rootTimes */ + if(root_ptp) + { + assign(tree->rootTimes, root_ptp->portTimes); + port_t *prt = root_ptp->port; + if(prt->rcvdInternal) + { + if(tree->rootTimes.remainingHops) + --(tree->rootTimes.remainingHops); + } + else + ++(tree->rootTimes.Message_Age); + } + else + { + assign(tree->rootTimes, tree->BridgeTimes); + } + + FOREACH_PTP_IN_TREE(ptp, tree) + { + port_t *prt = ptp->port; + + /* d) Set new designatedPriority */ + assign(ptp->designatedPriority, tree->rootPriority); + assign(ptp->designatedPriority.DesignatedBridgeID, + tree->BridgeIdentifier); + assign(ptp->designatedPriority.DesignatedPortID, ptp->portId); + /* I am not sure which condition to check here, as 802.1Q-2005 says: + * "... If {Port} is attached to a LAN that has one or more STP Bridges + * attached (as determined by the Port Protocol Migration state + * machine) ..." -- why not to mention explicit var name? Bad IEEE. + * But I guess that sendSTP (i.e. !sendRSTP) var will do ;) + */ + if(cist && !prt->sendRSTP) + assign(ptp->designatedPriority.RRootID, tree->BridgeIdentifier); + + /* e) Set new designatedTimes */ + assign(ptp->designatedTimes, tree->rootTimes); + /* Keep the configured Hello_Time for the port. + * NOTE: this is in accordance with the spirit of 802.1D-2004. + * Also, this does not contradict 802.1Q-2005(-2011), as in these + * standards both designatedTimes and rootTimes structures + * don't have Hello_Time member. + */ + assign(ptp->designatedTimes.Hello_Time, ptp->portTimes.Hello_Time); + } + + /* syncMaster */ + if(cist && cmp(tree->rootPriority.RRootID, !=, prevRRootID) + && ((0 != tree->rootPriority.ExtRootPathCost) + || (0 != prevExtRootPathCost) + ) + ) + syncMaster(tree->bridge); + + FOREACH_PTP_IN_TREE(ptp, tree) + { + port_t *prt = ptp->port; + per_tree_port_t *cist_tree = GET_CIST_PTP_FROM_PORT(prt); + + /* f) Set Disabled role */ + if(ioDisabled == ptp->infoIs) + { + ptp->selectedRole = roleDisabled; + continue; + } + + if(!cist && (ioReceived == cist_tree->infoIs) && !prt->infoInternal) + { + /* g) Set role for the boundary port in MSTI */ + if(roleRoot == cist_tree->selectedRole) + { + ptp->selectedRole = roleMaster; + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + &ptp->designatedTimes, + /*cist*/ false)) + ptp->updtInfo = true; + continue; + } + /* Bad IEEE again! It says in 13.26.23 g) 2) that + * MSTI state should follow CIST state only for the case of + * Alternate port. This is obviously wrong! + * In the descriptive clause 13.13 f) it says: + * "At a Boundary Port frames allocated to the CIST and + * all MSTIs are forwarded or not forwarded alike. + * This is because Port Role assignments are such that + * if the CIST Port Role is Root Port, the MSTI Port Role + * will be Master Port, and if the CIST Port Role is + * Designated Port, Alternate Port, Backup Port, + * or Disabled Port, each MSTI’s Port Role will be the same." + * So, ignore wrong 13.26.23 g) 2) and do as stated in 13.13 f) ! + */ + /* if(roleAlternate == cist_tree->selectedRole) */ + { + ptp->selectedRole = cist_tree->selectedRole; + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + &ptp->designatedTimes, + /*cist*/ false)) + ptp->updtInfo = true; + continue; + } + } + else + /* if(cist || (ioReceived != cist_tree->infoIs) || prt->infoInternal) */ + { + /* h) Set role for the aged info */ + if(ioAged == ptp->infoIs) + { + ptp->selectedRole = roleDesignated; + ptp->updtInfo = true; + continue; + } + /* i) Set role for the mine info */ + if(ioMine == ptp->infoIs) + { + ptp->selectedRole = roleDesignated; + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + &ptp->designatedTimes, + cist)) + ptp->updtInfo = true; + continue; + } + if(ioReceived == ptp->infoIs) + { + /* j) Set Root role */ + if(root_ptp == ptp) + { + ptp->selectedRole = roleRoot; + ptp->updtInfo = false; + } + else + { + if(betterorsamePriority(&ptp->portPriority, + &ptp->designatedPriority, + 0, 0, cist)) + { + if(cmp(ptp->portPriority.DesignatedBridgeID, !=, + tree->BridgeIdentifier)) + { + /* k) Set Alternate role */ + ptp->selectedRole = roleAlternate; + } + else + { + /* l) Set Backup role */ + ptp->selectedRole = roleBackup; + } + /* reset updtInfo for both k) and l) */ + ptp->updtInfo = false; + } + else /* designatedPriority is better than portPriority */ + { + /* m) Set Designated role */ + ptp->selectedRole = roleDesignated; + ptp->updtInfo = true; + } + } + /* This is not in standard. But we really should set here + * reselect for all MSTIs so that updtRolesTree is called + * for each MSTI and due to above clause g) MSTI role is + * changed to Master or reflects CIST port role. + * Because in 802.1Q-2005 this will not happen when BPDU arrives + * at boundary port - the rcvdMsg is not set for the MSTIs and + * updtRolesTree is not called. + * Bad IEEE !!! + */ + if(cist && (ptp->selectedRole != ptp->role)) + reselectMSTIs(prt); + continue; + } + } + } +} + +/* 13.27 The Port Timers state machine */ + +static void PTSM_tick(port_t *prt) +{ + per_tree_port_t *ptp; + + if(prt->helloWhen) + --(prt->helloWhen); + if(prt->mdelayWhile) + --(prt->mdelayWhile); + if(prt->edgeDelayWhile) + --(prt->edgeDelayWhile); + if(prt->txCount) + --(prt->txCount); + if(prt->brAssuRcvdInfoWhile) + --(prt->brAssuRcvdInfoWhile); + + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(ptp->fdWhile) + --(ptp->fdWhile); + if(ptp->rrWhile) + --(ptp->rrWhile); + if(ptp->rbWhile) + --(ptp->rbWhile); + if(ptp->tcWhile) + { + if(0 == --(ptp->tcWhile)) + set_TopologyChange(ptp->tree, false, prt); + } + if(ptp->rcvdInfoWhile) + --(ptp->rcvdInfoWhile); + } +} + +/* 13.28 Port Receive state machine */ +#define PRSM_begin(prt) PRSM_to_DISCARD((prt), false) +static bool PRSM_to_DISCARD(port_t *prt, bool dry_run) +{ + if(dry_run) + { + return (prt->PRSM_state != PRSM_DISCARD) + || prt->rcvdBpdu || prt->rcvdRSTP || prt->rcvdSTP + || (prt->edgeDelayWhile != prt->bridge->Migrate_Time) + || clearAllRcvdMsgs(prt, dry_run); + } + + prt->PRSM_state = PRSM_DISCARD; + + prt->rcvdBpdu = false; + prt->rcvdRSTP = false; + prt->rcvdSTP = false; + clearAllRcvdMsgs(prt, false /* actual run */); + assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); + + /* No need to run, no one condition will be met + * if(!begin) + * PRSM_run(prt, false); */ + return false; +} + +static void PRSM_to_RECEIVE(port_t *prt) +{ + prt->PRSM_state = PRSM_RECEIVE; + + updtBPDUVersion(prt); + prt->rcvdInternal = fromSameRegion(prt); + setRcvdMsgs(prt); + prt->operEdge = false; + prt->rcvdBpdu = false; + assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); + + /* No need to run, no one condition will be met + PRSM_run(prt, false); */ +} + +static bool PRSM_run(port_t *prt, bool dry_run) +{ + per_tree_port_t *ptp; + bool rcvdAnyMsg; + + if((prt->rcvdBpdu || (prt->edgeDelayWhile != prt->bridge->Migrate_Time)) + && !prt->portEnabled) + { + return PRSM_to_DISCARD(prt, dry_run); + } + + switch(prt->PRSM_state) + { + case PRSM_DISCARD: + if(prt->rcvdBpdu && prt->portEnabled) + { + if(dry_run) /* state change */ + return true; + PRSM_to_RECEIVE(prt); + } + return false; + case PRSM_RECEIVE: + rcvdAnyMsg = false; + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(ptp->rcvdMsg) + { + rcvdAnyMsg = true; + break; + } + } + if(prt->rcvdBpdu && prt->portEnabled && !rcvdAnyMsg) + { + if(dry_run) /* at least rcvdBpdu will change */ + return true; + PRSM_to_RECEIVE(prt); + } + default: + return false; + } +} + +/* 13.29 Port Protocol Migration state machine */ + +static bool PPMSM_run(port_t *prt, bool dry_run); +#define PPMSM_begin(prt) PPMSM_to_CHECKING_RSTP(prt) + +static void PPMSM_to_CHECKING_RSTP(port_t *prt/*, bool begin*/) +{ + prt->PPMSM_state = PPMSM_CHECKING_RSTP; + + bridge_t *br = prt->bridge; + prt->mcheck = false; + prt->sendRSTP = rstpVersion(br); + assign(prt->mdelayWhile, br->Migrate_Time); + + /* No need to run, no one condition will be met + * if(!begin) + * PPMSM_run(prt, false); */ +} + +static void PPMSM_to_SELECTING_STP(port_t *prt) +{ + prt->PPMSM_state = PPMSM_SELECTING_STP; + + prt->sendRSTP = false; + assign(prt->mdelayWhile, prt->bridge->Migrate_Time); + + PPMSM_run(prt, false /* actual run */); +} + +static void PPMSM_to_SENSING(port_t *prt) +{ + prt->PPMSM_state = PPMSM_SENSING; + + prt->rcvdRSTP = false; + prt->rcvdSTP = false; + + PPMSM_run(prt, false /* actual run */); +} + +static bool PPMSM_run(port_t *prt, bool dry_run) +{ + bridge_t *br = prt->bridge; + + switch(prt->PPMSM_state) + { + case PPMSM_CHECKING_RSTP: + if((prt->mdelayWhile != br->Migrate_Time) + && !prt->portEnabled) + { + if(dry_run) /* at least mdelayWhile will change */ + return true; + PPMSM_to_CHECKING_RSTP(prt); + return false; + } + if(0 == prt->mdelayWhile) + { + if(dry_run) /* state change */ + return true; + PPMSM_to_SENSING(prt); + } + return false; + case PPMSM_SELECTING_STP: + if(0 == prt->mdelayWhile || !prt->portEnabled || prt->mcheck) + { + if(dry_run) /* state change */ + return true; + PPMSM_to_SENSING(prt); + } + return false; + case PPMSM_SENSING: + if(!prt->portEnabled || prt->mcheck + || (rstpVersion(br) && !prt->sendRSTP && prt->rcvdRSTP)) + { + if(dry_run) /* state change */ + return true; + PPMSM_to_CHECKING_RSTP(prt); + return false; + } + if(prt->sendRSTP && prt->rcvdSTP) + { + if(dry_run) /* state change */ + return true; + PPMSM_to_SELECTING_STP(prt); + } + return false; + } + + return false; +} + +/* 13.30 Bridge Detection state machine */ +static void BDSM_to_EDGE(port_t *prt/*, bool begin*/) +{ + prt->BDSM_state = BDSM_EDGE; + + prt->operEdge = true; + + /* No need to run, no one condition will be met + * if(!begin) + * BDSM_run(prt, false); */ +} + +static void BDSM_to_NOT_EDGE(port_t *prt/*, bool begin*/) +{ + prt->BDSM_state = BDSM_NOT_EDGE; + + prt->operEdge = false; + + /* No need to run, no one condition will be met + * if(!begin) + * BDSM_run(prt, false); */ +} + +static void BDSM_begin(port_t *prt/*, bool begin*/) +{ + if(prt->AdminEdgePort) + BDSM_to_EDGE(prt/*, begin*/); + else + BDSM_to_NOT_EDGE(prt/*, begin*/); +} + +static bool BDSM_run(port_t *prt, bool dry_run) +{ + per_tree_port_t *cist; + + switch(prt->BDSM_state) + { + case BDSM_EDGE: + if(((!prt->portEnabled || !prt->AutoEdge) && !prt->AdminEdgePort) + || !prt->operEdge + ) + { + if(dry_run) /* state change */ + return true; + BDSM_to_NOT_EDGE(prt); + } + return false; + case BDSM_NOT_EDGE: + cist = GET_CIST_PTP_FROM_PORT(prt); + /* NOTE: 802.1Q-2005(-2011) is not clear, which of the per-tree + * "proposing" flags to use here, or one should combine + * them all for all trees? + * So, I decide that it will be the "proposing" flag + * from CIST tree - it seems like a good bet. + */ + if((!prt->portEnabled && prt->AdminEdgePort) + || ((0 == prt->edgeDelayWhile) && prt->AutoEdge && prt->sendRSTP + && cist->proposing) + ) + { + if(dry_run) /* state change */ + return true; + BDSM_to_EDGE(prt); + } + default: + return false; + } +} + +/* 13.31 Port Transmit state machine */ + +static bool PTSM_run(port_t *prt, bool dry_run); +#define PTSM_begin(prt) PTSM_to_TRANSMIT_INIT((prt), true, false) + +static bool PTSM_to_TRANSMIT_INIT(port_t *prt, bool begin, bool dry_run) +{ + if(dry_run) + { + return (prt->PTSM_state != PTSM_TRANSMIT_INIT) + || (!prt->newInfo) || (!prt->newInfoMsti) + || (0 != prt->txCount); + } + + prt->PTSM_state = PTSM_TRANSMIT_INIT; + + prt->newInfo = true; + prt->newInfoMsti = true; + assign(prt->txCount, 0u); + + if(!begin && prt->portEnabled) /* prevent infinite loop */ + PTSM_run(prt, false /* actual run */); + return false; +} + +static void PTSM_to_TRANSMIT_CONFIG(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_CONFIG; + + prt->newInfo = false; + txConfig(prt); + ++(prt->txCount); + prt->tcAck = false; + + PTSM_run(prt, false /* actual run */); +} + +static void PTSM_to_TRANSMIT_TCN(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_TCN; + + prt->newInfo = false; + txTcn(prt); + ++(prt->txCount); + + PTSM_run(prt, false /* actual run */); +} + +static void PTSM_to_TRANSMIT_RSTP(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_RSTP; + + prt->newInfo = false; + prt->newInfoMsti = false; + txMstp(prt); + ++(prt->txCount); + prt->tcAck = false; + + PTSM_run(prt, false /* actual run */); +} + +static void PTSM_to_TRANSMIT_PERIODIC(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_PERIODIC; + + per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); + bool cistDesignatedOrTCpropagatingRootPort = + (roleDesignated == ptp->role) + || ((roleRoot == ptp->role) && (0 != ptp->tcWhile)); + bool mstiDesignatedOrTCpropagatingRootPort; + + mstiDesignatedOrTCpropagatingRootPort = false; + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + if((roleDesignated == ptp->role) + || ((roleRoot == ptp->role) && (0 != ptp->tcWhile)) + ) + { + mstiDesignatedOrTCpropagatingRootPort = true; + break; + } + } + + prt->newInfo = prt->newInfo || cistDesignatedOrTCpropagatingRootPort; + prt->newInfoMsti = prt->newInfoMsti + || mstiDesignatedOrTCpropagatingRootPort; + + PTSM_run(prt, false /* actual run */); +} + +static void PTSM_to_IDLE(port_t *prt) +{ + prt->PTSM_state = PTSM_IDLE; + + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + prt->helloWhen = cist->portTimes.Hello_Time; + + PTSM_run(prt, false /* actual run */); +} + +static bool PTSM_run(port_t *prt, bool dry_run) +{ + /* bool allTransmitReady; */ + per_tree_port_t *ptp; + port_role_t cistRole; + bool mstiMasterPort; + + if(!prt->portEnabled) + { + return PTSM_to_TRANSMIT_INIT(prt, false, dry_run); + } + + switch(prt->PTSM_state) + { + case PTSM_TRANSMIT_INIT: + /* return; */ + case PTSM_TRANSMIT_CONFIG: + /* return; */ + case PTSM_TRANSMIT_TCN: + /* return; */ + case PTSM_TRANSMIT_RSTP: + /* return; */ + case PTSM_TRANSMIT_PERIODIC: + if(dry_run) /* state change */ + return true; + PTSM_to_IDLE(prt); /* UnConditional Transition */ + return false; + case PTSM_IDLE: + /* allTransmitReady = true; */ + ptp = GET_CIST_PTP_FROM_PORT(prt); + if(!ptp->selected || ptp->updtInfo) + { + /* allTransmitReady = false; */ + return false; + } + cistRole = ptp->role; + mstiMasterPort = false; + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + if(!ptp->selected || ptp->updtInfo) + { + /* allTransmitReady = false; */ + return false; + } + if(roleMaster == ptp->role) + mstiMasterPort = true; + } + if(0 == prt->helloWhen) + { + if(dry_run) /* state change */ + return true; + PTSM_to_TRANSMIT_PERIODIC(prt); + return false; + } + if(!(prt->txCount < prt->bridge->Transmit_Hold_Count)) + return false; + + if(prt->bpduFilterPort) + return false; + + if(prt->sendRSTP) + { /* implement MSTP */ + if(prt->newInfo || (prt->newInfoMsti && !mstiMasterPort) + || assurancePort(prt) + ) + { + if(dry_run) /* state change */ + return true; + PTSM_to_TRANSMIT_RSTP(prt); + return false; + } + } + else + { /* fallback to STP */ + if(prt->newInfo && (roleDesignated == cistRole)) + { + if(dry_run) /* state change */ + return true; + PTSM_to_TRANSMIT_CONFIG(prt); + return false; + } + if(prt->newInfo && (roleRoot == cistRole)) + { + if(dry_run) /* state change */ + return true; + PTSM_to_TRANSMIT_TCN(prt); + return false; + } + } + return false; + } + + return false; +} + +/* 13.32 Port Information state machine */ + +#ifdef PISM_ENABLE_LOG +#define PISM_LOG(_fmt, _args...) SMLOG_MSTINAME(ptp, _fmt, ##_args) +#else +#define PISM_LOG(_fmt, _args...) {} +#endif /* PISM_ENABLE_LOG */ + +static bool PISM_run(per_tree_port_t *ptp, bool dry_run); +#define PISM_begin(ptp) PISM_to_DISABLED((ptp), true) + +static void PISM_to_DISABLED(per_tree_port_t *ptp, bool begin) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_DISABLED; + + ptp->rcvdMsg = false; + ptp->proposing = false; + ptp->proposed = false; + ptp->agree = false; + ptp->agreed = false; + assign(ptp->rcvdInfoWhile, 0u); + ptp->infoIs = ioDisabled; + ptp->reselect = true; + ptp->selected = false; + + if(!begin) + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_AGED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_AGED; + + ptp->infoIs = ioAged; + ptp->reselect = true; + ptp->selected = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_UPDATE(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_UPDATE; + + ptp->proposing = false; + ptp->proposed = false; + ptp->agreed = ptp->agreed && betterorsameInfo(ptp, ioMine); + ptp->synced = ptp->synced && ptp->agreed; + assign(ptp->portPriority, ptp->designatedPriority); + assign(ptp->portTimes, ptp->designatedTimes); + ptp->updtInfo = false; + ptp->infoIs = ioMine; + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_SUPERIOR_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_SUPERIOR_DESIGNATED; + + port_t *prt = ptp->port; + + prt->infoInternal = prt->rcvdInternal; + ptp->agreed = false; + ptp->proposing = false; + recordProposal(ptp); + setTcFlags(ptp); + ptp->agree = ptp->agree && betterorsameInfo(ptp, ioReceived); + recordAgreement(ptp); + ptp->synced = ptp->synced && ptp->agreed; + recordPriority(ptp); + recordTimes(ptp); + updtRcvdInfoWhile(ptp); + ptp->infoIs = ioReceived; + ptp->reselect = true; + ptp->selected = false; + ptp->rcvdMsg = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_REPEATED_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_REPEATED_DESIGNATED; + + port_t *prt = ptp->port; + + prt->infoInternal = prt->rcvdInternal; + recordProposal(ptp); + setTcFlags(ptp); + recordAgreement(ptp); + updtRcvdInfoWhile(ptp); + ptp->rcvdMsg = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_INFERIOR_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_INFERIOR_DESIGNATED; + + recordDispute(ptp); + ptp->rcvdMsg = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_NOT_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_NOT_DESIGNATED; + + recordAgreement(ptp); + setTcFlags(ptp); + ptp->rcvdMsg = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_OTHER(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_OTHER; + + ptp->rcvdMsg = false; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_CURRENT(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_CURRENT; + + PISM_run(ptp, false /* actual run */); +} + +static void PISM_to_RECEIVE(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_RECEIVE; + + ptp->rcvdInfo = rcvInfo(ptp); + recordMastered(ptp); + + PISM_run(ptp, false /* actual run */); +} + +static bool PISM_run(per_tree_port_t *ptp, bool dry_run) +{ + bool rcvdXstMsg, updtXstInfo; + port_t *prt = ptp->port; + + if((!prt->portEnabled) && (ioDisabled != ptp->infoIs)) + { + if(dry_run) /* at least infoIs will change */ + return true; + PISM_to_DISABLED(ptp, false); + return false; + } + + switch(ptp->PISM_state) + { + case PISM_DISABLED: + if(prt->portEnabled) + { + if(dry_run) /* state change */ + return true; + PISM_to_AGED(ptp); + return false; + } + if(ptp->rcvdMsg) + { + if(dry_run) /* at least rcvdMsg will change */ + return true; + PISM_to_DISABLED(ptp, false); + } + return false; + case PISM_AGED: + if(ptp->selected && ptp->updtInfo) + { + if(dry_run) /* state change */ + return true; + PISM_to_UPDATE(ptp); + } + return false; + case PISM_UPDATE: + /* return; */ + case PISM_SUPERIOR_DESIGNATED: + /* return; */ + case PISM_REPEATED_DESIGNATED: + /* return; */ + case PISM_INFERIOR_DESIGNATED: + /* return; */ + case PISM_NOT_DESIGNATED: + /* return; */ + case PISM_OTHER: + if(dry_run) /* state change */ + return true; + PISM_to_CURRENT(ptp); + return false; + case PISM_CURRENT: + /* + * Although 802.1Q-2005 does not define rcvdXstMsg and updtXstInfo + * from 802.1s we can conclude that they are: + * - rcvdXstMsg = rcvdCistMsg, if tree is CIST + * rcvdMstiMsg, if tree is MSTI. + * - updtXstInfo = updtCistInfo, if tree is CIST + * updtMstiInfo, if tree is MSTI. + */ + if(0 == ptp->MSTID) + { /* CIST */ + rcvdXstMsg = ptp->rcvdMsg; /* 13.25.12 */ + updtXstInfo = ptp->updtInfo; /* 13.25.16 */ + } + else + { /* MSTI */ + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + rcvdXstMsg = !cist->rcvdMsg && ptp->rcvdMsg; /* 13.25.13 */ + updtXstInfo = ptp->updtInfo || cist->updtInfo; /* 13.25.17 */ + } + if(rcvdXstMsg && !updtXstInfo) + { + if(dry_run) /* state change */ + return true; + PISM_to_RECEIVE(ptp); + return false; + } + if((ioReceived == ptp->infoIs) && (0 == ptp->rcvdInfoWhile) + && !ptp->updtInfo && !rcvdXstMsg) + { + if(dry_run) /* state change */ + return true; + PISM_to_AGED(ptp); + return false; + } + if(ptp->selected && ptp->updtInfo) + { + if(dry_run) /* state change */ + return true; + PISM_to_UPDATE(ptp); + } + return false; + case PISM_RECEIVE: + switch(ptp->rcvdInfo) + { + case SuperiorDesignatedInfo: + if(dry_run) /* state change */ + return true; + PISM_to_SUPERIOR_DESIGNATED(ptp); + return false; + case RepeatedDesignatedInfo: + if(dry_run) /* state change */ + return true; + PISM_to_REPEATED_DESIGNATED(ptp); + return false; + case InferiorDesignatedInfo: + if(dry_run) /* state change */ + return true; + PISM_to_INFERIOR_DESIGNATED(ptp); + return false; + case InferiorRootAlternateInfo: + if(dry_run) /* state change */ + return true; + PISM_to_NOT_DESIGNATED(ptp); + return false; + case OtherInfo: + if(dry_run) /* state change */ + return true; + PISM_to_OTHER(ptp); + return false; + } + return false; + } + + return false; +} + +/* 13.33 Port Role Selection state machine */ + +static bool PRSSM_run(tree_t *tree, bool dry_run); +#define PRSSM_begin(tree) PRSSM_to_INIT_TREE(tree) + +static void PRSSM_to_INIT_TREE(tree_t *tree/*, bool begin*/) +{ + tree->PRSSM_state = PRSSM_INIT_TREE; + + updtRolesDisabledTree(tree); + + /* No need to check, as we assume begin = true here + * because transition to this state can be initiated only by BEGIN var. + * In other words, this function is called via xxx_begin macro only. + * if(!begin) + * PRSSM_run(prt, false); */ +} + +static void PRSSM_to_ROLE_SELECTION(tree_t *tree) +{ + tree->PRSSM_state = PRSSM_ROLE_SELECTION; + + clearReselectTree(tree); + updtRolesTree(tree); + setSelectedTree(tree); + + /* No need to run, no one condition will be met + PRSSM_run(tree, false); */ +} + +static bool PRSSM_run(tree_t *tree, bool dry_run) +{ + per_tree_port_t *ptp; + + switch(tree->PRSSM_state) + { + case PRSSM_INIT_TREE: + if(dry_run) /* state change */ + return true; + PRSSM_to_ROLE_SELECTION(tree); + return false; + case PRSSM_ROLE_SELECTION: + FOREACH_PTP_IN_TREE(ptp, tree) + if(ptp->reselect) + { + if(dry_run) /* at least reselect will change */ + return true; + PRSSM_to_ROLE_SELECTION(tree); + return false; + } + return false; + } + + return false; +} + +/* 13.34 Port Role Transitions state machine */ + +#ifdef PRTSM_ENABLE_LOG +#define PRTSM_LOG(_fmt, _args...) SMLOG_MSTINAME(ptp, _fmt, ##_args) +#else +#define PRTSM_LOG(_fmt, _args...) {} +#endif /* PRTSM_ENABLE_LOG */ + +static bool PRTSM_runr(per_tree_port_t *ptp, bool recursive_call, bool dry_run); +#define PRTSM_run(ptp, dry_run) PRTSM_runr((ptp), false, (dry_run)) +#define PRTSM_begin(ptp) PRTSM_to_INIT_PORT(ptp) + + /* Disabled Port role transitions */ + +static void PRTSM_to_INIT_PORT(per_tree_port_t *ptp/*, bool begin*/) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_INIT_PORT; + + unsigned int MaxAge, FwdDelay; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(ptp->port); + + ptp->role = roleDisabled; + ptp->learn = false; + ptp->forward = false; + ptp->synced = false; + ptp->sync = true; + ptp->reRoot = true; + /* 13.25.6 */ + FwdDelay = cist->designatedTimes.Forward_Delay; + assign(ptp->rrWhile, FwdDelay); + /* 13.25.8 */ + MaxAge = cist->designatedTimes.Max_Age; + assign(ptp->fdWhile, MaxAge); + assign(ptp->rbWhile, 0u); + + /* No need to check, as we assume begin = true here + * because transition to this state can be initiated only by BEGIN var. + * In other words, this function is called via xxx_begin macro only. + * if(!begin) + * PRTSM_runr(ptp, false, false); */ +} + +static void PRTSM_to_DISABLE_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DISABLE_PORT; + + /* Although 802.1Q-2005 says here to do role = selectedRole + * I have difficulties with it in the next scenario: + * 1) port was designated (role == selectedRole == roleDesignated) + * 2) some config event occurs, e.g. MAC address changes and + * br_state_machines_begin is called + * 3) role == selectedRole == roleDisabled, PRTSM_state = PRTSM_INIT_PORT + * Because port was not actually down, on the next run + * Port Role Selection state machine sets selectedRole = roleDesignated + * and updtInfo = true: + * 4) we have unconditional transition to DISABLE_PORT, and because + * updtInfo = true we can not follow transition to DESIGNATED_PORT + * 5) if we follow standard, role = selectedRole = roleDesignated and + * on the next run we have transition to the DISABLED_PORT + * And there we stuck. role == selectedRole, so we can not transit to + * DESIGNATED_PORT (it requires role != selectedRole ). + * + * Solution: do not follow the standard, and do role = roleDisabled + * instead of role = selectedRole. + */ + ptp->role = roleDisabled; + ptp->learn = false; + ptp->forward = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DISABLED_PORT(per_tree_port_t *ptp, unsigned int MaxAge) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DISABLED_PORT; + + assign(ptp->fdWhile, MaxAge); + ptp->synced = true; + assign(ptp->rrWhile, 0u); + ptp->sync = false; + ptp->reRoot = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + + /* MasterPort role transitions */ + +static void PRTSM_to_MASTER_PROPOSED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_PROPOSED; + + setSyncTree(ptp->tree); + ptp->proposed = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_AGREED; + + ptp->proposed = false; + ptp->sync = false; + ptp->agree = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_SYNCED; + + assign(ptp->rrWhile, 0u); + ptp->synced = true; + ptp->sync = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_RETIRED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_RETIRED; + + ptp->reRoot = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_FORWARD; + + ptp->forward = true; + assign(ptp->fdWhile, 0u); + ptp->agreed = ptp->port->sendRSTP; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_LEARN; + + ptp->learn = true; + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_DISCARD; + + ptp->learn = false; + ptp->forward = false; + ptp->disputed = false; + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_MASTER_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_PORT; + + ptp->role = roleMaster; + + PRTSM_runr(ptp, true, false /* actual run */); +} + + /* RootPort role transitions */ + +static void PRTSM_to_ROOT_PROPOSED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_PROPOSED; + + setSyncTree(ptp->tree); + ptp->proposed = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ROOT_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_AGREED; + + ptp->proposed = false; + ptp->sync = false; + ptp->agree = true; + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ROOT_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_SYNCED; + + ptp->synced = true; + ptp->sync = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_REROOT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_REROOT; + + setReRootTree(ptp->tree); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ROOT_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_FORWARD; + + assign(ptp->fdWhile, 0u); + ptp->forward = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ROOT_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_LEARN; + + assign(ptp->fdWhile, forwardDelay); + ptp->learn = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_REROOTED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_REROOTED; + + ptp->reRoot = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ROOT_PORT(per_tree_port_t *ptp, unsigned int FwdDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_PORT; + + ptp->role = roleRoot; + assign(ptp->rrWhile, FwdDelay); + + PRTSM_runr(ptp, true, false /* actual run */); +} + + /* DesignatedPort role transitions */ + +static void PRTSM_to_DESIGNATED_PROPOSE(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_PROPOSE; + + port_t *prt = ptp->port; + + ptp->proposing = true; + /* newInfoXst = TRUE; */ + if(0 == ptp->MSTID) + { /* CIST */ + /* 13.25.8. This tree is CIST. */ + unsigned int MaxAge = ptp->designatedTimes.Max_Age; + /* 13.25.c) -> 17.20.4 of 802.1D : EdgeDelay */ + unsigned int EdgeDelay = prt->operPointToPointMAC ? + prt->bridge->Migrate_Time + : MaxAge; + assign(prt->edgeDelayWhile, EdgeDelay); + prt->newInfo = true; + } + else + prt->newInfoMsti = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_AGREED; + + ptp->proposed = false; + ptp->sync = false; + ptp->agree = true; + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_SYNCED; + + assign(ptp->rrWhile, 0u); + ptp->synced = true; + ptp->sync = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_RETIRED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_RETIRED; + + ptp->reRoot = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_FORWARD; + + ptp->forward = true; + assign(ptp->fdWhile, 0u); + ptp->agreed = ptp->port->sendRSTP; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_LEARN; + + ptp->learn = true; + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_DISCARD; + + ptp->learn = false; + ptp->forward = false; + ptp->disputed = false; + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_DESIGNATED_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_PORT; + + ptp->role = roleDesignated; + + PRTSM_runr(ptp, true, false /* actual run */); +} + + /* AlternatePort and BackupPort role transitions */ + +static void PRTSM_to_BLOCK_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_BLOCK_PORT; + + ptp->role = ptp->selectedRole; + ptp->learn = false; + ptp->forward = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_BACKUP_PORT(per_tree_port_t *ptp, unsigned int HelloTime) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_BACKUP_PORT; + + assign(ptp->rbWhile, 2 * HelloTime); + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ALTERNATE_PROPOSED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ALTERNATE_PROPOSED; + + setSyncTree(ptp->tree); + ptp->proposed = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ALTERNATE_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ALTERNATE_AGREED; + + ptp->proposed = false; + ptp->agree = true; + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static void PRTSM_to_ALTERNATE_PORT(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ALTERNATE_PORT; + + assign(ptp->fdWhile, forwardDelay); + ptp->synced = true; + assign(ptp->rrWhile, 0u); + ptp->sync = false; + ptp->reRoot = false; + + PRTSM_runr(ptp, true, false /* actual run */); +} + +static bool PRTSM_runr(per_tree_port_t *ptp, bool recursive_call, bool dry_run) +{ + /* Following vars do not need recalculating on recursive calls */ + static unsigned int MaxAge, FwdDelay, forwardDelay, HelloTime; + static port_t *prt; + static tree_t *tree; + static per_tree_port_t *cist; + /* Following vars are recalculated on each state transition */ + bool allSynced, reRooted; + /* Following vars are auxiliary and don't depend on recursive_call */ + per_tree_port_t *ptp_1; + + if(!recursive_call) + { /* calculate these intermediate vars only first time in chain of + * recursive calls */ + prt = ptp->port; + tree = ptp->tree; + + cist = GET_CIST_PTP_FROM_PORT(prt); + + /* 13.25.6 */ + FwdDelay = cist->designatedTimes.Forward_Delay; + + /* 13.25.7 */ + HelloTime = cist->portTimes.Hello_Time; + + /* 13.25.d) -> 17.20.5 of 802.1D */ + forwardDelay = prt->sendRSTP ? HelloTime : FwdDelay; + + /* 13.25.8 */ + MaxAge = cist->designatedTimes.Max_Age; + } + + PRTSM_LOG("role = %d, selectedRole = %d, selected = %d, updtInfo = %d", + ptp->role, ptp->selectedRole, ptp->selected, ptp->updtInfo); + if((ptp->role != ptp->selectedRole) && ptp->selected && !ptp->updtInfo) + { + switch(ptp->selectedRole) + { + case roleDisabled: + if(dry_run) /* at least role will change */ + return true; + PRTSM_to_DISABLE_PORT(ptp); + return false; + case roleMaster: + if(dry_run) /* at least role will change */ + return true; + PRTSM_to_MASTER_PORT(ptp); + return false; + case roleRoot: + if(dry_run) /* at least role will change */ + return true; + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return false; + case roleDesignated: + if(dry_run) /* at least role will change */ + return true; + PRTSM_to_DESIGNATED_PORT(ptp); + return false; + case roleAlternate: + case roleBackup: + if(dry_run) /* at least role will change */ + return true; + PRTSM_to_BLOCK_PORT(ptp); + return false; + } + } + + /* 13.25.1 */ + allSynced = true; + FOREACH_PTP_IN_TREE(ptp_1, tree) + { + /* a) */ + if(!ptp_1->selected + || (ptp_1->role != ptp_1->selectedRole) + || ptp_1->updtInfo + ) + { + allSynced = false; + break; + } + + /* b) */ + switch(ptp->role) + { + case roleRoot: + case roleAlternate: + if((roleRoot != ptp_1->role) && !ptp_1->synced) + allSynced = false; + break; + case roleDesignated: + case roleMaster: + if((ptp != ptp_1) && !ptp_1->synced) + allSynced = false; + break; + default: + allSynced = false; + } + if(!allSynced) + break; + } + + switch(ptp->PRTSM_state) + { + /* Disabled Port role transitions */ + case PRTSM_INIT_PORT: + if(dry_run) /* state change */ + return true; + PRTSM_to_DISABLE_PORT(ptp); + return false; + case PRTSM_DISABLE_PORT: + if(ptp->selected && !ptp->updtInfo + && !ptp->learning && !ptp->forwarding + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DISABLED_PORT(ptp, MaxAge); + } + return false; + case PRTSM_DISABLED_PORT: + if(ptp->selected && !ptp->updtInfo + && (ptp->sync || ptp->reRoot || !ptp->synced + || (ptp->fdWhile != MaxAge)) + ) + { + if(dry_run) /* one of (sync,reRoot,synced,fdWhile) will change */ + return true; + PRTSM_to_DISABLED_PORT(ptp, MaxAge); + } + return false; + /* MasterPort role transitions */ + case PRTSM_MASTER_PROPOSED: + /* return; */ + case PRTSM_MASTER_AGREED: + /* return; */ + case PRTSM_MASTER_SYNCED: + /* return; */ + case PRTSM_MASTER_RETIRED: + /* return; */ + case PRTSM_MASTER_FORWARD: + /* return; */ + case PRTSM_MASTER_LEARN: + /* return; */ + case PRTSM_MASTER_DISCARD: + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_PORT(ptp); + return false; + case PRTSM_MASTER_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return false; + if(ptp->reRoot && (0 == ptp->rrWhile)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_RETIRED(ptp); + return false; + } + if((!ptp->learning && !ptp->forwarding && !ptp->synced) + || (ptp->agreed && !ptp->synced) + || (prt->operEdge && !ptp->synced) + || (ptp->sync && ptp->synced) + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_SYNCED(ptp); + return false; + } + if((allSynced && !ptp->agree) + || (ptp->proposed && ptp->agree) + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_AGREED(ptp); + return false; + } + if(ptp->proposed && !ptp->agree) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_PROPOSED(ptp); + return false; + } + if(((0 == ptp->fdWhile) || allSynced) + && ptp->learn && !ptp->forward + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_FORWARD(ptp); + return false; + } + if(((0 == ptp->fdWhile) || allSynced) + && !ptp->learn + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_LEARN(ptp, forwardDelay); + return false; + } + if(((ptp->sync && !ptp->synced) + || (ptp->reRoot && (0 != ptp->rrWhile)) + || ptp->disputed + ) + && !prt->operEdge && (ptp->learn || ptp->forward) + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_MASTER_DISCARD(ptp, forwardDelay); + return false; + } + return false; + /* RootPort role transitions */ + case PRTSM_ROOT_PROPOSED: + /* return; */ + case PRTSM_ROOT_AGREED: + /* return; */ + case PRTSM_ROOT_SYNCED: + /* return; */ + case PRTSM_REROOT: + /* return; */ + case PRTSM_ROOT_FORWARD: + /* return; */ + case PRTSM_ROOT_LEARN: + /* return; */ + case PRTSM_REROOTED: + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return false; + case PRTSM_ROOT_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return false; + if(!ptp->forward && !ptp->reRoot) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_REROOT(ptp); + return false; + } + if((ptp->agreed && !ptp->synced) || (ptp->sync && ptp->synced)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_SYNCED(ptp); + return false; + } + if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_AGREED(ptp); + return false; + } + if(ptp->proposed && !ptp->agree) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_PROPOSED(ptp); + return false; + } + /* 17.20.10 of 802.1D : reRooted */ + reRooted = true; + FOREACH_PTP_IN_TREE(ptp_1, tree) + { + if((ptp != ptp_1) && (0 != ptp_1->rrWhile)) + { + reRooted = false; + break; + } + } + if((0 == ptp->fdWhile) + || (reRooted && (0 == ptp->rbWhile) && rstpVersion(prt->bridge)) + ) + { + if(!ptp->learn) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_LEARN(ptp, forwardDelay); + return false; + } + else if(!ptp->forward) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_FORWARD(ptp); + return false; + } + } + if(ptp->reRoot && ptp->forward) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_REROOTED(ptp); + return false; + } + if(ptp->rrWhile != FwdDelay) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return false; + } + return false; + /* DesignatedPort role transitions */ + case PRTSM_DESIGNATED_PROPOSE: + /* return; */ + case PRTSM_DESIGNATED_AGREED: + /* return; */ + case PRTSM_DESIGNATED_SYNCED: + /* return; */ + case PRTSM_DESIGNATED_RETIRED: + /* return; */ + case PRTSM_DESIGNATED_FORWARD: + /* return; */ + case PRTSM_DESIGNATED_LEARN: + /* return; */ + case PRTSM_DESIGNATED_DISCARD: + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_PORT(ptp); + return false; + case PRTSM_DESIGNATED_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return false; + if(ptp->reRoot && (0 == ptp->rrWhile)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_RETIRED(ptp); + return false; + } + if((!ptp->learning && !ptp->forwarding && !ptp->synced) + || (ptp->agreed && !ptp->synced) + || (prt->operEdge && !ptp->synced) + || (ptp->sync && ptp->synced) + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_SYNCED(ptp); + return false; + } + if(allSynced && (ptp->proposed || !ptp->agree)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_AGREED(ptp); + return false; + } + if(!ptp->forward && !ptp->agreed && !ptp->proposing + && !prt->operEdge) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_PROPOSE(ptp); + return false; + } + /* Dont transition to learn/forward when BA inconsistent */ + if(((0 == ptp->fdWhile) || ptp->agreed || prt->operEdge) + && ((0 == ptp->rrWhile) || !ptp->reRoot) && !ptp->sync + && !ptp->port->BaInconsistent + ) + { + if(!ptp->learn) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_LEARN(ptp, forwardDelay); + return false; + } + else if(!ptp->forward) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_FORWARD(ptp); + return false; + } + } + /* Transition to discarding when BA inconsistent */ + if(((ptp->sync && !ptp->synced) + || (ptp->reRoot && (0 != ptp->rrWhile)) + || ptp->disputed + || ptp->port->BaInconsistent + ) + && !prt->operEdge && (ptp->learn || ptp->forward) + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_DESIGNATED_DISCARD(ptp, forwardDelay); + return false; + } + return false; + /* AlternatePort and BackupPort role transitions */ + case PRTSM_BLOCK_PORT: + if(ptp->selected && !ptp->updtInfo + && !ptp->learning && !ptp->forwarding + ) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + } + return false; + case PRTSM_BACKUP_PORT: + /* return; */ + case PRTSM_ALTERNATE_PROPOSED: + /* return; */ + case PRTSM_ALTERNATE_AGREED: + if(dry_run) /* state change */ + return true; + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + return false; + case PRTSM_ALTERNATE_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return false; + if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ALTERNATE_AGREED(ptp); + return false; + } + if(ptp->proposed && !ptp->agree) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ALTERNATE_PROPOSED(ptp); + return false; + } + if((ptp->rbWhile != 2 * HelloTime) && (roleBackup == ptp->role)) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_BACKUP_PORT(ptp, HelloTime); + return false; + } + if((ptp->fdWhile != forwardDelay) || ptp->sync || ptp->reRoot + || !ptp->synced) + { + if(dry_run) /* state change */ + return true; + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + return false; + } + return false; + } + + return false; +} + +/* 13.35 Port State Transition state machine */ + +static bool PSTSM_run(per_tree_port_t *ptp, bool dry_run); +#define PSTSM_begin(ptp) PSTSM_to_DISCARDING((ptp), true) + +static void PSTSM_to_DISCARDING(per_tree_port_t *ptp, bool begin) +{ + ptp->PSTSM_state = PSTSM_DISCARDING; + + /* This effectively sets BLOCKING state: + disableLearning(); + disableForwarding(); + */ + if(BR_STATE_BLOCKING != ptp->state) + { + if(!ptp->port->deleted) + MSTP_OUT_set_state(ptp, BR_STATE_BLOCKING); + } + ptp->learning = false; + ptp->forwarding = false; + + if(!begin) + PSTSM_run(ptp, false /* actual run */); +} + +static void PSTSM_to_LEARNING(per_tree_port_t *ptp) +{ + ptp->PSTSM_state = PSTSM_LEARNING; + + /* enableLearning(); */ + if(BR_STATE_LEARNING != ptp->state) + { + if(!ptp->port->deleted) + MSTP_OUT_set_state(ptp, BR_STATE_LEARNING); + } + ptp->learning = true; + + PSTSM_run(ptp, false /* actual run */); +} + +static void PSTSM_to_FORWARDING(per_tree_port_t *ptp) +{ + ptp->PSTSM_state = PSTSM_FORWARDING; + + /* enableForwarding(); */ + if(BR_STATE_FORWARDING != ptp->state) + { + if(!ptp->port->deleted) + MSTP_OUT_set_state(ptp, BR_STATE_FORWARDING); + } + ptp->forwarding = true; + + /* No need to run, no one condition will be met + PSTSM_run(ptp, false); */ +} + +static bool PSTSM_run(per_tree_port_t *ptp, bool dry_run) +{ + switch(ptp->PSTSM_state) + { + case PSTSM_DISCARDING: + if(ptp->learn) + { + if(dry_run) /* state change */ + return true; + PSTSM_to_LEARNING(ptp); + } + return false; + case PSTSM_LEARNING: + if(!ptp->learn) + { + if(dry_run) /* state change */ + return true; + PSTSM_to_DISCARDING(ptp, false); + } + else if(ptp->forward) + { + if(dry_run) /* state change */ + return true; + PSTSM_to_FORWARDING(ptp); + } + return false; + case PSTSM_FORWARDING: + if(!ptp->forward) + { + if(dry_run) /* state change */ + return true; + PSTSM_to_DISCARDING(ptp, false); + } + return false; + } + + return false; +} + +/* 13.36 Topology Change state machine */ + +#define TCSM_begin(ptp) TCSM_to_INACTIVE((ptp), true) + +static void TCSM_to_INACTIVE(per_tree_port_t *ptp, bool begin) +{ + ptp->TCSM_state = TCSM_INACTIVE; + + set_fdbFlush(ptp); + assign(ptp->tcWhile, 0u); + set_TopologyChange(ptp->tree, false, ptp->port); + if(0 == ptp->MSTID) /* CIST */ + ptp->port->tcAck = false; + + if(!begin) + TCSM_run(ptp, false /* actual run */); +} + +static bool TCSM_to_LEARNING(per_tree_port_t *ptp, bool dry_run) +{ + if(dry_run) + { + if((ptp->TCSM_state != TCSM_LEARNING) || ptp->rcvdTc || ptp->tcProp) + return true; + if(0 == ptp->MSTID) /* CIST */ + { + port_t *prt = ptp->port; + if(prt->rcvdTcn || prt->rcvdTcAck) + return true; + } + return false; + } + + ptp->TCSM_state = TCSM_LEARNING; + + if(0 == ptp->MSTID) /* CIST */ + { + port_t *prt = ptp->port; + prt->rcvdTcn = false; + prt->rcvdTcAck = false; + } + ptp->rcvdTc = false; + ptp->tcProp = false; + + TCSM_run(ptp, false /* actual run */); + return false; +} + +static void TCSM_to_DETECTED(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_DETECTED; + + newTcWhile(ptp); + setTcPropTree(ptp); + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + prt->newInfo = true; + else + prt->newInfoMsti = true; + + TCSM_run(ptp, false /* actual run */); +} + +static void TCSM_to_NOTIFIED_TCN(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_NOTIFIED_TCN; + + newTcWhile(ptp); + + TCSM_run(ptp, false /* actual run */); +} + +static void TCSM_to_NOTIFIED_TC(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_NOTIFIED_TC; + + ptp->rcvdTc = false; + if(0 == ptp->MSTID) /* CIST */ + { + port_t *prt = ptp->port; + prt->rcvdTcn = false; + if(roleDesignated == ptp->role) + prt->tcAck = true; + } + setTcPropTree(ptp); + + TCSM_run(ptp, false /* actual run */); +} + +static void TCSM_to_PROPAGATING(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_PROPAGATING; + + newTcWhile(ptp); + set_fdbFlush(ptp); + ptp->tcProp = false; + + TCSM_run(ptp, false /* actual run */); +} + +static void TCSM_to_ACKNOWLEDGED(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_ACKNOWLEDGED; + + assign(ptp->tcWhile, 0u); + set_TopologyChange(ptp->tree, false, ptp->port); + ptp->port->rcvdTcAck = false; + + TCSM_run(ptp, false /* actual run */); +} + +static void TCSM_to_ACTIVE(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_ACTIVE; + + TCSM_run(ptp, false /* actual run */); +} + +static bool TCSM_run(per_tree_port_t *ptp, bool dry_run) +{ + bool active_port; + port_t *prt = ptp->port; + + switch(ptp->TCSM_state) + { + case TCSM_INACTIVE: + if(ptp->learn && !ptp->fdbFlush) + { + if(dry_run) /* state change */ + return true; + TCSM_to_LEARNING(ptp, false /* actual run */); + } + return false; + case TCSM_LEARNING: + active_port = (roleRoot == ptp->role) + || (roleDesignated == ptp->role) + || (roleMaster == ptp->role); + if(active_port && ptp->forward && !prt->operEdge) + { + if(dry_run) /* state change */ + return true; + TCSM_to_DETECTED(ptp); + return false; + } + if(ptp->rcvdTc || prt->rcvdTcn || prt->rcvdTcAck || ptp->tcProp) + { + return TCSM_to_LEARNING(ptp, dry_run); + } + else if(!active_port && !(ptp->learn || ptp->learning)) + { + if(dry_run) /* state change */ + return true; + TCSM_to_INACTIVE(ptp, false); + } + return false; + case TCSM_NOTIFIED_TCN: + if(dry_run) /* state change */ + return true; + TCSM_to_NOTIFIED_TC(ptp); + return false; + case TCSM_DETECTED: + /* return; */ + case TCSM_NOTIFIED_TC: + /* return; */ + case TCSM_PROPAGATING: + /* return; */ + case TCSM_ACKNOWLEDGED: + if(dry_run) /* state change */ + return true; + TCSM_to_ACTIVE(ptp); + return false; + case TCSM_ACTIVE: + active_port = (roleRoot == ptp->role) + || (roleDesignated == ptp->role) + || (roleMaster == ptp->role); + if(!active_port || prt->operEdge) + { + if(dry_run) /* state change */ + return true; + TCSM_to_LEARNING(ptp, false /* actual run */); + return false; + } + if(prt->rcvdTcn) + { + if(dry_run) /* state change */ + return true; + TCSM_to_NOTIFIED_TCN(ptp); + return false; + } + if(ptp->rcvdTc) + { + if(dry_run) /* state change */ + return true; + TCSM_to_NOTIFIED_TC(ptp); + return false; + } + if(ptp->tcProp/* && !prt->operEdge */) + { + if(dry_run) /* state change */ + return true; + TCSM_to_PROPAGATING(ptp); + return false; + } + if(prt->rcvdTcAck) + { + if(dry_run) /* state change */ + return true; + TCSM_to_ACKNOWLEDGED(ptp); + return false; + } + return false; + } + + return false; +} + +/* Execute BEGIN state. We do not define BEGIN variable + * but instead xxx_state_machines_begin execute begin state + * abd do one step out of it + */ + +static void tree_state_machines_begin(tree_t *tree) +{ + bridge_t *br = tree->bridge; + per_tree_port_t *ptp; + + if(!br->bridgeEnabled) + return; + + /* 13.32 Port Information state machine */ + FOREACH_PTP_IN_TREE(ptp, tree) + { + ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ + PISM_begin(ptp); + } + + /* 13.33 Port Role Selection state machine */ + PRSSM_begin(tree); + + /* 13.34 Port Role Transitions state machine */ + FOREACH_PTP_IN_TREE(ptp, tree) + PRTSM_begin(ptp); + /* 13.35 Port State Transition state machine */ + FOREACH_PTP_IN_TREE(ptp, tree) + PSTSM_begin(ptp); + /* 13.36 Topology Change state machine */ + FOREACH_PTP_IN_TREE(ptp, tree) + TCSM_begin(ptp); + + br_state_machines_run(br); +} + +static void prt_state_machines_begin(port_t *prt) +{ + bridge_t *br = prt->bridge; + tree_t *tree; + per_tree_port_t *ptp; + + if(!br->bridgeEnabled) + return; + + /* 13.28 Port Receive state machine */ + PRSM_begin(prt); + /* 13.29 Port Protocol Migration state machine */ + PPMSM_begin(prt); + /* 13.30 Bridge Detection state machine */ + BDSM_begin(prt); + /* 13.31 Port Transmit state machine */ + PTSM_begin(prt); + + /* 13.32 Port Information state machine */ + FOREACH_PTP_IN_PORT(ptp, prt) + { + ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ + PISM_begin(ptp); + } + + /* 13.33 Port Role Selection state machine */ + FOREACH_TREE_IN_BRIDGE(tree, br) + PRSSM_run(tree, false /* actual run */); + + /* 13.34 Port Role Transitions state machine */ + FOREACH_PTP_IN_PORT(ptp, prt) + PRTSM_begin(ptp); + /* 13.35 Port State Transition state machine */ + FOREACH_PTP_IN_PORT(ptp, prt) + PSTSM_begin(ptp); + /* 13.36 Topology Change state machine */ + FOREACH_PTP_IN_PORT(ptp, prt) + TCSM_begin(ptp); + + br_state_machines_run(br); +} + +static void br_state_machines_begin(bridge_t *br) +{ + port_t *prt; + per_tree_port_t *ptp; + tree_t *tree; + + if(!br->bridgeEnabled) + return; + + /* 13.28 Port Receive state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + PRSM_begin(prt); + /* 13.29 Port Protocol Migration state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + PPMSM_begin(prt); + /* 13.30 Bridge Detection state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + BDSM_begin(prt); + /* 13.31 Port Transmit state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + PTSM_begin(prt); + + /* 13.32 Port Information state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + { + ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ + PISM_begin(ptp); + } + } + + /* 13.33 Port Role Selection state machine */ + FOREACH_TREE_IN_BRIDGE(tree, br) + PRSSM_begin(tree); + + /* 13.34 Port Role Transitions state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + PRTSM_begin(ptp); + } + /* 13.35 Port State Transition state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + PSTSM_begin(ptp); + } + /* 13.36 Topology Change state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + TCSM_begin(ptp); + } + + br_state_machines_run(br); +} + +/* Run each state machine. + * Return false iff all state machines in dry run indicate that + * state will not be changed. Otherwise return true. + */ +static bool __br_state_machines_run(bridge_t *br, bool dry_run) +{ + port_t *prt; + per_tree_port_t *ptp; + tree_t *tree; + + /* Check if bridge assurance timer expires */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + if(prt->portEnabled && assurancePort(prt) + && (0 == prt->brAssuRcvdInfoWhile) && !prt->BaInconsistent + ) + { + if(dry_run) /* state change */ + return true; + prt->BaInconsistent = true; + ERROR_PRTNAME(prt->bridge, prt, "Bridge assurance inconsistent"); + } + } + + /* 13.28 Port Receive state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + if(PRSM_run(prt, dry_run) && dry_run) + return true; + } + /* 13.29 Port Protocol Migration state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + if(PPMSM_run(prt, dry_run) && dry_run) + return true; + } + /* 13.30 Bridge Detection state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + if(BDSM_run(prt, dry_run) && dry_run) + return true; + } + /* 13.31 Port Transmit state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + if(PTSM_run(prt, dry_run) && dry_run) + return true; + } + + /* 13.32 Port Information state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(PISM_run(ptp, dry_run) && dry_run) + return true; + } + } + + /* 13.33 Port Role Selection state machine */ + FOREACH_TREE_IN_BRIDGE(tree, br) + { + if(PRSSM_run(tree, dry_run) && dry_run) + return true; + } + + /* 13.34 Port Role Transitions state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(PRTSM_run(ptp, dry_run) && dry_run) + return true; + } + } + /* 13.35 Port State Transition state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(PSTSM_run(ptp, dry_run) && dry_run) + return true; + } + } + /* 13.36 Topology Change state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(TCSM_run(ptp, dry_run) && dry_run) + return true; + } + } + + return false; +} + +/* Run state machines until their state stabilizes. + * Do not consume more than 1 second. + */ +static void br_state_machines_run(bridge_t *br) +{ + struct timespec tv, tv_end; + signed long delta; + + if(!br->bridgeEnabled) + return; + + clock_gettime(CLOCK_MONOTONIC, &tv_end); + ++(tv_end.tv_sec); + + do { + if(!__br_state_machines_run(br, true /* dry run */)) + return; + __br_state_machines_run(br, false /* actual run */); + + /* Check for the timeout */ + clock_gettime(CLOCK_MONOTONIC, &tv); + if(0 < (delta = tv.tv_sec - tv_end.tv_sec)) + return; + if(0 == delta) + { + delta = tv.tv_nsec - tv_end.tv_nsec; + if(0 < delta) + return; + } + } while(true); +} diff --git a/mstp.h b/mstp.h new file mode 100644 index 0000000..7929476 --- /dev/null +++ b/mstp.h @@ -0,0 +1,803 @@ +/* + * mstp.h State machines from IEEE 802.1Q-2005 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Vitalii Demianets + */ + +#ifndef MSTP_H +#define MSTP_H + +#include +#include +#include + +#include "bridge_ctl.h" + +/* #define HMAC_MDS_TEST_FUNCTIONS */ + +/* Useful macro for counting number of elements in array */ +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + +/* + * assign() and cmp() macros that also do strict type-checking. See the + * "unnecessary" pointer comparison. + * NOTE: potential double-evaluation of the first argument in assign macro! + * It is the price for type-safety ;) + */ +#define assign(x, y) ({ \ + typeof(x) _assign1 = (x); \ + typeof(y) _assign2 = (y); \ + (void)(&_assign1 == &_assign2); \ + (x) = _assign2; }) +#define _ncmp(x, y) ({ \ + typeof(x) _cmp1 = (x); \ + typeof(y) _cmp2 = (y); \ + (void)(&_cmp1 == &_cmp2); \ + memcmp(&_cmp1, &_cmp2, sizeof(_cmp1)); }) +#define cmp(x, _op, y) (_ncmp((x), (y)) _op 0) + +/* 13.7, Table 13-1 */ +#define HMAC_KEY {0x13, 0xAC, 0x06, 0xA6, 0x2E, 0x47, 0xFD, 0x51, \ + 0xF9, 0x5D, 0x2B, 0xA2, 0x43, 0xCD, 0x03, 0x46} +extern void hmac_md5(unsigned char * text, int text_len, unsigned char * key, + int key_len, caddr_t digest); +#ifdef HMAC_MDS_TEST_FUNCTIONS +extern bool MD5TestSuite(void); +#endif /* HMAC_MDS_TEST_FUNCTIONS */ + +#define MAX_PORT_NUMBER 4095 +#define MAX_VID 4094 +#define MAX_FID 4095 +#define MAX_MSTID 4094 + +/* MAX_xxx_MSTIS: CIST not counted */ +#define MAX_STANDARD_MSTIS 64 +#define MAX_IMPLEMENTATION_MSTIS 63 + +/* 13.37.1 */ +#define MAX_PATH_COST 200000000u + +typedef union +{ + __u64 u; + struct + { + __be16 priority; + __u8 mac_address[ETH_ALEN]; + } __attribute__((packed)) s; +} bridge_identifier_t; + +typedef __be16 port_identifier_t; + +/* These macros work well for both PortID and BridgeID */ +#define GET_PRIORITY_FROM_IDENTIFIER(id) (((__u8 *)(&(id)))[0] & 0xF0) +#define SET_PRIORITY_IN_IDENTIFIER(pri, id) do{ \ + __u8 *first_octet = (__u8 *)(&(id)); \ + *first_octet &= 0x0F; \ + *first_octet |= (pri) & 0xF0; \ + }while(0) + +#define CONFIGURATION_NAME_LEN 32 +#define CONFIGURATION_DIGEST_LEN 16 +typedef union +{ + __u8 a[1 + CONFIGURATION_NAME_LEN + 2 + CONFIGURATION_DIGEST_LEN]; + struct + { + __u8 selector; /* always 0 */ + __u8 configuration_name[CONFIGURATION_NAME_LEN]; + __be16 revision_level; + __u8 configuration_digest[CONFIGURATION_DIGEST_LEN]; + } __attribute__((packed)) s; +} __attribute__((packed)) mst_configuration_identifier_t; + +typedef struct +{ + bridge_identifier_t RRootID; + __be32 IntRootPathCost; + bridge_identifier_t DesignatedBridgeID; + port_identifier_t DesignatedPortID; + /* not used for MSTIs, only for CIST */ + bridge_identifier_t RootID; + __be32 ExtRootPathCost; +} port_priority_vector_t; + +typedef struct +{ + __u8 remainingHops; + /* not used for MSTIs, only for CIST */ + __u8 Forward_Delay; + __u8 Max_Age; + __u8 Message_Age; + __u8 Hello_Time; +} times_t; + +typedef struct +{ + /* see bpduFlagOffset_t enum for offsets of flag bits */ + __u8 flags; + bridge_identifier_t mstiRRootID; + __be32 mstiIntRootPathCost; + /* only bits 7..4, bits 3..0 are zero on Tx and ignored on Rx */ + __u8 bridgeIdentifierPriority; + /* only bits 7..4, bits 3..0 are zero on Tx and ignored on Rx */ + __u8 portIdentifierPriority; + __u8 remainingHops; +} __attribute__((packed)) msti_configuration_message_t; + +typedef struct +{ + /* always zero for the Spanning Tree BPDUs */ + __be16 protocolIdentifier; + /* protoSTP for the Config and TCN + * protoRSTP for the RST + * protoMSTP for the MST + * (see protocol_version_t enum) */ + __u8 protocolVersion; + /* values are defined in bpduType_t enum */ + __u8 bpduType; + /* TCN BPDU ends here */ + /* see bpduFlagOffset_t enum for offsets of flag bits */ + __u8 flags; + bridge_identifier_t cistRootID; + __be32 cistExtRootPathCost; + bridge_identifier_t cistRRootID; + port_identifier_t cistPortID; + __u8 MessageAge[2]; + __u8 MaxAge[2]; + __u8 HelloTime[2]; + __u8 ForwardDelay[2]; + /* Config BPDU ends here */ + __u8 version1_len; /* always zero */ + /* RST BPDU ends here */ + __be16 version3_len; + mst_configuration_identifier_t mstConfigurationIdentifier; + __be32 cistIntRootPathCost; + bridge_identifier_t cistBridgeID; + __u8 cistRemainingHops; + msti_configuration_message_t mstConfiguration[MAX_STANDARD_MSTIS]; +} __attribute__((packed)) bpdu_t; + +#define TCN_BPDU_SIZE offsetof(bpdu_t, flags) +#define CONFIG_BPDU_SIZE offsetof(bpdu_t, version1_len) +#define RST_BPDU_SIZE offsetof(bpdu_t, version3_len) +#define MST_BPDU_SIZE_WO_MSTI_MSGS offsetof(bpdu_t, mstConfiguration) +#define MST_BPDU_VER3LEN_WO_MSTI_MSGS (MST_BPDU_SIZE_WO_MSTI_MSGS \ + - offsetof(bpdu_t, mstConfigurationIdentifier)) + +typedef enum +{ + OtherInfo, + SuperiorDesignatedInfo, + RepeatedDesignatedInfo, + InferiorDesignatedInfo, + InferiorRootAlternateInfo +} port_info_t; + +typedef enum +{ + ioDisabled, + ioMine, + ioAged, + ioReceived +} port_info_origin_t; + +typedef enum +{ + roleDisabled, + roleRoot, + roleDesignated, + roleAlternate, + roleBackup, + roleMaster +} port_role_t; + +typedef enum +{ + encodedRoleMaster = 0, + encodedRoleAlternateBackup = 1, + encodedRoleRoot = 2, + encodedRoleDesignated = 3 +} port_encoded_role_t; + +typedef enum +{ + protoSTP = 0, + protoRSTP = 2, + protoMSTP = 3 +} protocol_version_t; + +typedef enum +{ + bpduTypeConfig = 0, + bpduTypeRST = 2, + bpduTypeTCN = 128 +} bpduType_t; + +typedef enum +{ + offsetTc = 0, + offsetProposal = 1, + offsetRole = 2, /* actually, role is coded in two-bit field */ + offsetRole1 = 3, /* second bit of two-bit role field */ + offsetLearnig = 4, + offsetForwarding = 5, + offsetAgreement = 6, + offsetTcAck = 7 +/* in MSTI Configuration Message flags bit7 is used for Master flag */ +#define offsetMaster offsetTcAck +} bpduFlagOffset_t; + +#define BPDU_FLAGS_ROLE_SET(role) (((role) & 3) << offsetRole) +#define BPDU_FLAGS_ROLE_GET(flags) (((flags) >> offsetRole) & 3) + +typedef enum +{ + p2pAuto, + p2pForceTrue, + p2pForceFalse +} +admin_p2p_t; + +/* 13.28 Port Receive state machine */ +typedef enum +{ + PRSM_DISCARD, + PRSM_RECEIVE +} PRSM_states_t; + +/* 13.29 Port Protocol Migration state machine */ +typedef enum +{ + PPMSM_CHECKING_RSTP, + PPMSM_SELECTING_STP, + PPMSM_SENSING +} PPMSM_states_t; + +/* 13.30 Bridge Detection state machine */ +typedef enum +{ + BDSM_EDGE, + BDSM_NOT_EDGE +} BDSM_states_t; + +/* 13.31 Port Transmit state machine */ +typedef enum +{ + PTSM_TRANSMIT_INIT, + PTSM_TRANSMIT_CONFIG, + PTSM_TRANSMIT_TCN, + PTSM_TRANSMIT_RSTP, + PTSM_TRANSMIT_PERIODIC, + PTSM_IDLE +} PTSM_states_t; + +/* 13.32 Port Information state machine */ +/* #define PISM_ENABLE_LOG */ +typedef enum +{ + PISM_DISABLED, + PISM_AGED, + PISM_UPDATE, + PISM_SUPERIOR_DESIGNATED, + PISM_REPEATED_DESIGNATED, + PISM_INFERIOR_DESIGNATED, + PISM_NOT_DESIGNATED, + PISM_OTHER, + PISM_CURRENT, + PISM_RECEIVE +} PISM_states_t; + +/* 13.33 Port Role Selection state machine */ +typedef enum +{ + PRSSM_INIT_TREE, + PRSSM_ROLE_SELECTION +} PRSSM_states_t; + +/* 13.34 Port Role Transitions state machine */ +/* #define PRTSM_ENABLE_LOG */ +typedef enum +{ + /* Disabled Port role transitions */ + PRTSM_INIT_PORT, + PRTSM_DISABLE_PORT, + PRTSM_DISABLED_PORT, + /* MasterPort role transitions */ + PRTSM_MASTER_PROPOSED, + PRTSM_MASTER_AGREED, + PRTSM_MASTER_SYNCED, + PRTSM_MASTER_RETIRED, + PRTSM_MASTER_FORWARD, + PRTSM_MASTER_LEARN, + PRTSM_MASTER_DISCARD, + PRTSM_MASTER_PORT, + /* RootPort role transitions */ + PRTSM_ROOT_PROPOSED, + PRTSM_ROOT_AGREED, + PRTSM_ROOT_SYNCED, + PRTSM_REROOT, + PRTSM_ROOT_FORWARD, + PRTSM_ROOT_LEARN, + PRTSM_REROOTED, + PRTSM_ROOT_PORT, + /* DesignatedPort role transitions */ + PRTSM_DESIGNATED_PROPOSE, + PRTSM_DESIGNATED_AGREED, + PRTSM_DESIGNATED_SYNCED, + PRTSM_DESIGNATED_RETIRED, + PRTSM_DESIGNATED_FORWARD, + PRTSM_DESIGNATED_LEARN, + PRTSM_DESIGNATED_DISCARD, + PRTSM_DESIGNATED_PORT, + /* AlternatePort and BackupPort role transitions */ + PRTSM_BLOCK_PORT, + PRTSM_BACKUP_PORT, + PRTSM_ALTERNATE_PROPOSED, + PRTSM_ALTERNATE_AGREED, + PRTSM_ALTERNATE_PORT +} PRTSM_states_t; + +/* 13.35 Port State Transition state machine */ +typedef enum +{ + PSTSM_DISCARDING, + PSTSM_LEARNING, + PSTSM_FORWARDING +} PSTSM_states_t; + +/* 13.36 Topology Change state machine */ +typedef enum +{ + TCSM_INACTIVE, + TCSM_LEARNING, + TCSM_DETECTED, + TCSM_NOTIFIED_TCN, + TCSM_NOTIFIED_TC, + TCSM_PROPAGATING, + TCSM_ACKNOWLEDGED, + TCSM_ACTIVE +} TCSM_states_t; + +/* + * Following standard-defined variables are not defined as variables. + * Their functionality is implemented indirectly by other means: + * - BEGIN, tick, ageingTime. + */ + +typedef struct +{ + struct list_head list; /* anchor in global list of bridges */ + + /* List of all ports */ + struct list_head ports; + /* List of all tree instances, first in list (trees.next) is CIST */ + struct list_head trees; +#define GET_CIST_TREE(br) list_entry((br)->trees.next, tree_t, bridge_list) + + bool bridgeEnabled; + + /* Per-bridge configuration parameters */ + mst_configuration_identifier_t MstConfigId; /* 13.24.b */ + protocol_version_t ForceProtocolVersion; /* 13.22.e */ + __u8 MaxHops; /* 13.22.o */ + __u8 Forward_Delay; /* 13.22.f */ + __u8 Max_Age; /* 13.22.i */ + /* The 802.1Q-2005 (13.22.j) says that this parameter is substituted by + * the per-port Hello Time, but we still need it for compatibility + * with old STP implementations. + */ + __u8 Hello_Time; + unsigned int Transmit_Hold_Count; /* 13.22.g */ + unsigned int Migrate_Time; /* 13.22.h */ + unsigned int Ageing_Time; /* 8.8.3 */ + + __u16 vid2fid[MAX_VID + 1]; + __be16 fid2mstid[MAX_FID + 1]; + + /* not in standard */ + unsigned int uptime; + + sysdep_br_data_t sysdeps; +} bridge_t; + +typedef struct +{ + struct list_head bridge_list; /* anchor in bridge's list of trees */ + bridge_t * bridge; + __be16 MSTID; /* 0 == CIST */ + + /* List of the per-port data structures for this tree instance */ + struct list_head ports; + + /* 13.23.(c,f,g) Per-bridge per-tree variables */ + bridge_identifier_t BridgeIdentifier; + port_identifier_t rootPortId; + port_priority_vector_t rootPriority; + + /* 13.23.d This is totally calculated from BridgeIdentifier */ + port_priority_vector_t BridgePriority; + + /* 13.23.e Some waste of space here, as MSTIs only use + * remainingHops member of the struct times_t, + * but saves extra checks and improves readability */ + times_t BridgeTimes, rootTimes; + + /* 12.8.1.1.3.(b,c,d) */ + unsigned int time_since_topology_change; + unsigned int topology_change_count; + bool topology_change; + char topology_change_port[IFNAMSIZ]; + char last_topology_change_port[IFNAMSIZ]; + + /* State machines */ + PRSSM_states_t PRSSM_state; + +} tree_t; + +typedef struct +{ + struct list_head br_list; /* anchor in bridge's list of ports */ + bridge_t * bridge; + __be16 port_number; + + /* List of all tree instances, first in list (trees.next) is CIST. + * List is sorted by MSTID (by insertion procedure MSTP_IN_create_msti). + */ + struct list_head trees; +#define GET_CIST_PTP_FROM_PORT(prt) \ + list_entry((prt)->trees.next, per_tree_port_t, port_list) + + /* 13.21.(a,b,c) Per-port timers */ + unsigned int mdelayWhile, helloWhen, edgeDelayWhile; + + /* 13.24.(b,c,e,f,g,j,k,l,m,n,o,p,q,r,aw) Per-port variables */ + unsigned int txCount; + bool operEdge, portEnabled, infoInternal, rcvdInternal; + bool mcheck, rcvdBpdu, rcvdRSTP, rcvdSTP, rcvdTcAck, rcvdTcn, sendRSTP; + bool tcAck, newInfo, newInfoMsti; + + /* 6.4.3 */ + bool operPointToPointMAC; + + /* Per-port configuration parameters */ + bool restrictedRole, restrictedTcn; /* 13.24.(h,i) */ + __u32 ExternalPortPathCost; /* 13.22.p */ + __u32 AdminExternalPortPathCost; /* 0 = calculate from speed */ + admin_p2p_t AdminP2P; /* 6.4.3 */ + bool AdminEdgePort; /* 13.22.k */ + bool AutoEdge; /* 13.22.m */ + bool BpduGuardPort; + bool BpduGuardError; + bool NetworkPort; + bool BaInconsistent; + bool dontTxmtBpdu; + bool bpduFilterPort; + + unsigned int rapidAgeingWhile; + unsigned int brAssuRcvdInfoWhile; + + /* State machines */ + PRSM_states_t PRSM_state; + PPMSM_states_t PPMSM_state; + BDSM_states_t BDSM_state; + PTSM_states_t PTSM_state; + + /* Copy of the received BPDU */ + bpdu_t rcvdBpduData; + int rcvdBpduNumOfMstis; + + bool deleted; + + sysdep_if_data_t sysdeps; + unsigned int num_rx_bpdu_filtered; + unsigned int num_rx_bpdu; + unsigned int num_rx_tcn; + unsigned int num_tx_bpdu; + unsigned int num_tx_tcn; + unsigned int num_trans_fwd; + unsigned int num_trans_blk; +} port_t; + +typedef struct +{ + struct list_head port_list; /* anchor in port's list of trees */ + struct list_head tree_list; /* anchor in tree's list of per-port data */ + port_t *port; + tree_t *tree; + __be16 MSTID; /* 0 == CIST */ + + int state; /* BR_STATE_xxx */ + + /* 13.21.(d,e,f,g,h) Per-port per-tree timers */ + unsigned int fdWhile, rrWhile, rbWhile, tcWhile, rcvdInfoWhile; + + /* 13.24.(s,t,u,v,w,x,y,z,aa,ab,ac,ad,ae,af,ag,ai,aj,ak,ap,as,at,au,av) + * Per-port per-tree variables */ + bool agree, agreed, disputed, forward, forwarding, learn, learning; + port_info_t rcvdInfo; + port_info_origin_t infoIs; + bool proposed, proposing, rcvdMsg, rcvdTc, reRoot, reselect, selected; + bool fdbFlush, tcProp, updtInfo, sync, synced; + port_identifier_t portId; + port_role_t role, selectedRole; + + /* 13.24.(al,an,aq) Some waste of space here, as MSTIs don't use + * RootID and ExtRootPathCost members of the struct port_priority_vector_t, + * but saves extra checks and improves readability */ + port_priority_vector_t designatedPriority, msgPriority, portPriority; + + /* 13.24.(am,ao,ar) Some waste of space here, as MSTIs only use + * remainingHops member of the struct times_t, + * but saves extra checks and improves readability */ + times_t designatedTimes, msgTimes, portTimes; + + /* 13.24.(ax,ay) Per-port per-MSTI variables, not applicable to CIST */ + bool master, mastered; + + /* Per-port per-tree configuration parameters */ + __u32 InternalPortPathCost; /* 13.22.q */ + __u32 AdminInternalPortPathCost; /* 0 = calculate from speed */ + + /* not in standard, used for calculation of port uptime */ + unsigned int start_time; + + /* State machines */ + PISM_states_t PISM_state; + PRTSM_states_t PRTSM_state; + PSTSM_states_t PSTSM_state; + TCSM_states_t TCSM_state; + + /* Auxiliary flag, helps preventing infinite recursion */ + bool calledFromFlushRoutine; + + /* Pointer to the corresponding MSTI Configuration Message + * in the port->rcvdBpduData */ + msti_configuration_message_t *rcvdMstiConfig; +} per_tree_port_t; + +/* External events (inputs) */ +bool MSTP_IN_bridge_create(bridge_t *br, __u8 *macaddr); +bool MSTP_IN_port_create_and_add_tail(port_t *prt, __u16 portno); +void MSTP_IN_delete_port(port_t *prt); +void MSTP_IN_delete_bridge(bridge_t *br); +void MSTP_IN_set_bridge_address(bridge_t *br, __u8 *macaddr); +void MSTP_IN_set_bridge_enable(bridge_t *br, bool up); +void MSTP_IN_set_port_enable(port_t *prt, bool up, int speed, int duplex); +void MSTP_IN_one_second(bridge_t *br); +void MSTP_IN_all_fids_flushed(per_tree_port_t *ptp); +void MSTP_IN_rx_bpdu(port_t *prt, bpdu_t *bpdu, int size); + +bool MSTP_IN_set_vid2fid(bridge_t *br, __u16 vid, __u16 fid); +bool MSTP_IN_set_all_vids2fids(bridge_t *br, __u16 *vids2fids); +bool MSTP_IN_set_fid2mstid(bridge_t *br, __u16 fid, __u16 mstid); +bool MSTP_IN_set_all_fids2mstids(bridge_t *br, __u16 *fids2mstids); +bool MSTP_IN_get_mstilist(bridge_t *br, int *num_mstis, __u16 *mstids); +bool MSTP_IN_create_msti(bridge_t *br, __u16 mstid); +bool MSTP_IN_delete_msti(bridge_t *br, __u16 mstid); +void MSTP_IN_set_mst_config_id(bridge_t *br, __u16 revision, __u8 *name); + +/* External actions (outputs) */ +void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state); +void MSTP_OUT_flush_all_fids(per_tree_port_t *ptp); +void MSTP_OUT_set_ageing_time(port_t *prt, unsigned int ageingTime); +void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t *bpdu, int size); +void MSTP_OUT_shutdown_port(port_t *prt); + +/* Structures for communicating with user */ + /* 12.8.1.1 Read CIST Bridge Protocol Parameters */ +typedef struct +{ + bridge_identifier_t bridge_id; + unsigned int time_since_topology_change; + unsigned int topology_change_count; + bool topology_change; + char topology_change_port[IFNAMSIZ]; + char last_topology_change_port[IFNAMSIZ]; + bridge_identifier_t designated_root; + unsigned int root_path_cost; + port_identifier_t root_port_id; + __u8 root_max_age; + __u8 root_forward_delay; + __u8 bridge_max_age; + __u8 bridge_forward_delay; + unsigned int tx_hold_count; + protocol_version_t protocol_version; + bridge_identifier_t regional_root; + unsigned int internal_path_cost; + bool enabled; /* not in standard */ + unsigned int Ageing_Time; + __u8 max_hops; + __u8 bridge_hello_time; +} CIST_BridgeStatus; + +void MSTP_IN_get_cist_bridge_status(bridge_t *br, CIST_BridgeStatus *status); + + /* 12.8.1.2 Read MSTI Bridge Protocol Parameters */ +typedef struct +{ + bridge_identifier_t bridge_id; + unsigned int time_since_topology_change; + unsigned int topology_change_count; + bool topology_change; + char topology_change_port[IFNAMSIZ]; + char last_topology_change_port[IFNAMSIZ]; + bridge_identifier_t regional_root; + unsigned int internal_path_cost; + port_identifier_t root_port_id; +} MSTI_BridgeStatus; + +void MSTP_IN_get_msti_bridge_status(tree_t *tree, MSTI_BridgeStatus *status); + +/* 12.8.1.3 Set CIST Bridge Protocol Parameters */ +typedef struct +{ + __u8 bridge_max_age; + bool set_bridge_max_age; + + __u8 bridge_forward_delay; + bool set_bridge_forward_delay; + + /* Superseded by MSTP_IN_set_msti_bridge_config for the CIST. + * __u8 bridge_priority; + * bool set_bridge_priority; */ + + protocol_version_t protocol_version; + bool set_protocol_version; + + unsigned int tx_hold_count; + bool set_tx_hold_count; + + __u8 max_hops; + bool set_max_hops; + + __u8 bridge_hello_time; + bool set_bridge_hello_time; + + unsigned int bridge_ageing_time; + bool set_bridge_ageing_time; +} CIST_BridgeConfig; + +int MSTP_IN_set_cist_bridge_config(bridge_t *br, CIST_BridgeConfig *cfg); + +/* 12.8.1.4 Set MSTI Bridge Protocol Parameters */ + /* No need in special structure for single parameter Bridge Priority */ + +int MSTP_IN_set_msti_bridge_config(tree_t *tree, __u8 bridge_priority); + +/* 12.8.2.1 Read CIST Port Parameters */ +typedef struct +{ + unsigned int uptime; + int state; /* BR_STATE_xxx */ + port_identifier_t port_id; + __u32 admin_external_port_path_cost; /* not in standard. 0 = auto */ + __u32 external_port_path_cost; + bridge_identifier_t designated_root; /* from portPriority */ + __u32 designated_external_cost; /* from portPriority */ + bridge_identifier_t designated_bridge; /* from portPriority */ + port_identifier_t designated_port; /* from portPriority */ + bool tc_ack; /* tcAck */ + __u8 port_hello_time; /* from portTimes */ + bool admin_edge_port; + bool auto_edge_port; /* not in standard */ + bool oper_edge_port; + /* 802.1Q-2005 wants here MAC_Enabled & MAC_Operational. We don't know + * neither of these. Return portEnabled and feel happy. */ + bool enabled; + admin_p2p_t admin_p2p; + bool oper_p2p; + bool restricted_role; + bool restricted_tcn; + port_role_t role; + bool disputed; + bridge_identifier_t designated_regional_root; /* from portPriority */ + __u32 designated_internal_cost; /* from portPriority */ + __u32 admin_internal_port_path_cost; /* not in standard. 0 = auto */ + __u32 internal_port_path_cost; /* not in standard */ + bool bpdu_guard_port; + bool bpdu_guard_error; + bool bpdu_filter_port; + bool network_port; + bool ba_inconsistent; + unsigned int num_rx_bpdu_filtered; + unsigned int num_rx_bpdu; + unsigned int num_rx_tcn; + unsigned int num_tx_bpdu; + unsigned int num_tx_tcn; + unsigned int num_trans_fwd; + unsigned int num_trans_blk; + bool rcvdBpdu; + bool rcvdRSTP; + bool rcvdSTP; + bool rcvdTcAck; + bool rcvdTcn; + bool sendRSTP; +} CIST_PortStatus; + +void MSTP_IN_get_cist_port_status(port_t *prt, CIST_PortStatus *status); + +/* 12.8.2.2 Read MSTI Port Parameters */ +typedef struct +{ + unsigned int uptime; + int state; /* BR_STATE_xxx */ + port_identifier_t port_id; + __u32 admin_internal_port_path_cost; /* not in standard. 0 = auto */ + __u32 internal_port_path_cost; + bridge_identifier_t designated_regional_root; /* from portPriority */ + __u32 designated_internal_cost; /* from portPriority */ + bridge_identifier_t designated_bridge; /* from portPriority */ + port_identifier_t designated_port; /* from portPriority */ + port_role_t role; + bool disputed; +} MSTI_PortStatus; + +void MSTP_IN_get_msti_port_status(per_tree_port_t *ptp, + MSTI_PortStatus *status); + +/* 12.8.2.3 Set CIST port parameters */ +typedef struct +{ + __u32 admin_external_port_path_cost; /* not in standard. 0 = auto */ + bool set_admin_external_port_path_cost; + + /* Superseded by MSTP_IN_set_msti_port_config for the CIST. + * __u32 admin_internal_port_path_cost; + * bool set_admin_internal_port_path_cost; + * + * __u8 port_priority; + * bool set_port_priority; + */ + + bool admin_edge_port; + bool set_admin_edge_port; + + bool auto_edge_port; /* not in standard */ + bool set_auto_edge_port; + + admin_p2p_t admin_p2p; + bool set_admin_p2p; + + bool restricted_role; + bool set_restricted_role; + + bool restricted_tcn; + bool set_restricted_tcn; + + bool bpdu_guard_port; + bool set_bpdu_guard_port; + + bool network_port; + bool set_network_port; + + bool dont_txmt; + bool set_dont_txmt; + + bool bpdu_filter_port; + bool set_bpdu_filter_port; +} CIST_PortConfig; + +int MSTP_IN_set_cist_port_config(port_t *prt, CIST_PortConfig *cfg); + +/* 12.8.2.4 Set MSTI port parameters */ +typedef struct +{ + __u32 admin_internal_port_path_cost; /* 0 = auto */ + bool set_admin_internal_port_path_cost; + + __u8 port_priority; + bool set_port_priority; +} MSTI_PortConfig; + +int MSTP_IN_set_msti_port_config(per_tree_port_t *ptp, MSTI_PortConfig *cfg); + +/* 12.8.2.5 Force BPDU Migration Check */ +int MSTP_IN_port_mcheck(port_t *prt); + +#endif /* MSTP_H */ diff --git a/netif_utils.c b/netif_utils.c new file mode 100644 index 0000000..66241b2 --- /dev/null +++ b/netif_utils.c @@ -0,0 +1,170 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netif_utils.h" +#include "log.h" + +#ifndef SYSFS_CLASS_NET +#define SYSFS_CLASS_NET "/sys/class/net" +#endif + +static int netsock = -1; + +int netsock_init(void) +{ + netsock = socket(AF_INET, SOCK_DGRAM, 0); + if(0 > netsock) + { + ERROR("Couldn't open inet socket for ioctls: %m\n"); + return -1; + } + return 0; +} + +int get_hwaddr(char *ifname, __u8 *hwaddr) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + if(0 > ioctl(netsock, SIOCGIFHWADDR, &ifr)) + { + ERROR("%s: get hw address failed: %m", ifname); + return -1; + } + memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + return 0; +} + +int get_flags(char *ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + if(0 > ioctl(netsock, SIOCGIFFLAGS, &ifr)) + { + ERROR("%s: get interface flags failed: %m", ifname); + return -1; + } + return ifr.ifr_flags; +} + +int if_shutdown(char *ifname) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + /* TODO: Let's hope -1 is not a valid flag combination */ + if(-1 == (ifr.ifr_flags = get_flags(ifname))) + { + return -1; + } + ifr.ifr_flags &= ~IFF_UP; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + if(0 > ioctl(netsock, SIOCSIFFLAGS, &ifr)) + { + ERROR("%s: set if_down flag failed: %m", ifname); + return -1; + } + return 0; +} + +int ethtool_get_speed_duplex(char *ifname, int *speed, int *duplex) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + struct ethtool_cmd ecmd; + + ecmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)&ecmd; + if(0 > ioctl(netsock, SIOCETHTOOL, &ifr)) + { + INFO("Cannot get speed/duplex for %s: %m\n", ifname); + return -1; + } + *speed = ecmd.speed; /* Ethtool speed is in Mbps */ + *duplex = ecmd.duplex; /* We have same convention as ethtool. + 0 = half, 1 = full */ + return 0; +} + +/********* Sysfs based utility functions *************/ + +/* This sysfs stuff might break with interface renames */ +bool is_bridge(char *if_name) +{ + char path[32 + IFNAMSIZ]; + sprintf(path, SYSFS_CLASS_NET "/%s/bridge", if_name); + return (0 == access(path, R_OK)); +} + +int get_bridge_portno(char *if_name) +{ + char path[32 + IFNAMSIZ]; + sprintf(path, SYSFS_CLASS_NET "/%s/brport/port_no", if_name); + char buf[128]; + int fd; + long res = -1; + TSTM((fd = open(path, O_RDONLY)) >= 0, -1, "%m"); + int l; + TSTM((l = read(fd, buf, sizeof(buf) - 1)) >= 0, -1, "%m"); + if(0 == l) + { + ERROR("Empty port index file"); + goto out; + } + else if((sizeof(buf) - 1) == l) + { + ERROR("port_index file too long"); + goto out; + } + buf[l] = 0; + if('\n' == buf[l - 1]) + buf[l - 1] = 0; + char *end; + res = strtoul(buf, &end, 0); + if(0 != *end || INT_MAX < res) + { + ERROR("Invalid port index %s", buf); + res = -1; + } +out: + close(fd); + return res; +} diff --git a/netif_utils.h b/netif_utils.h new file mode 100644 index 0000000..2da8418 --- /dev/null +++ b/netif_utils.h @@ -0,0 +1,43 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + Copyright (c) 2011 Factor-SPE + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Vitalii Demianets + +******************************************************************************/ + +#ifndef NETIF_UTILS_H +#define NETIF_UTILS_H + +/* An inet socket for everyone to use for ifreqs. */ +int netsock_init(void); + +int get_hwaddr(char *ifname, unsigned char *hwaddr); +int get_flags(char *ifname); +int if_shutdown(char *ifname); + +int ethtool_get_speed_duplex(char *ifname, int *speed, int *duplex); + +bool is_bridge(char *if_name); + +int get_bridge_portno(char *if_name); + +#endif /* NETIF_UTILS_H */ diff --git a/packet.c b/packet.c new file mode 100644 index 0000000..d20e17c --- /dev/null +++ b/packet.c @@ -0,0 +1,217 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + Authors: Stephen Hemminger + +******************************************************************************/ + +/* #define PACKET_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netif_utils.h" +#include "bridge_ctl.h" +#include "packet.h" +#include "log.h" +#include "worker.h" + +static struct uloop_fd ufd; + +#ifdef PACKET_DEBUG +static void dump_packet(const unsigned char *buf, int cc) +{ + int i, j; + for(i = 0; i < cc; i += 16) + { + for(j = 0; j < 16 && i + j < cc; ++j) + printf(" %02x", buf[i + j]); + printf("\n"); + } + printf("\n"); + fflush(stdout); +} +#endif + +/* + * To send/receive Spanning Tree packets we use PF_PACKET because + * it allows the filtering we want but gives raw data + */ +void packet_send(int ifindex, const struct iovec *iov, int iov_count, int len) +{ + int l; + struct sockaddr_ll sl = + { + .sll_family = AF_PACKET, + .sll_protocol = __constant_cpu_to_be16(ETH_P_802_2), + .sll_ifindex = ifindex, + .sll_halen = ETH_ALEN, + }; + + if(iov_count > 0 && iov[0].iov_len > ETH_ALEN) + memcpy(&sl.sll_addr, iov[0].iov_base, ETH_ALEN); + + struct msghdr msg = + { + .msg_name = &sl, + .msg_namelen = sizeof(sl), + .msg_iov = (struct iovec *)iov, + .msg_iovlen = iov_count, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + +#ifdef PACKET_DEBUG + printf("Transmit Dst index %d %02x:%02x:%02x:%02x:%02x:%02x\n", + sl.sll_ifindex, + sl.sll_addr[0], sl.sll_addr[1], sl.sll_addr[2], + sl.sll_addr[3], sl.sll_addr[4], sl.sll_addr[5]); + { + int i; + for(i = 0; i < iov_count; ++i) + dump_packet(iov[i].iov_base, iov[i].iov_len); + } +#endif + + l = sendmsg(ufd.fd, &msg, 0); + + if(l < 0) + { + if(errno != EWOULDBLOCK) + ERROR("send failed: %m"); + } + else if(l != len) + ERROR("short write in sendto: %d instead of %d", l, len); +} + +void packet_rcv(void) +{ + unsigned char buf[2048]; + struct sockaddr_ll sl; + socklen_t salen = sizeof sl; + int cc; + + while (1) { + cc = recvfrom(ufd.fd, &buf, sizeof(buf), 0, (struct sockaddr *) &sl, &salen); + if (cc < 0) { + switch (errno) { + case EINTR: + continue; + case EAGAIN: + return; + default: + cc = 0; + } + } + + if (cc == 0) { + ERROR("recvfrom failed: %m"); + uloop_fd_delete(&ufd); + return; + } + +#ifdef PACKET_DEBUG + printf("Receive Src ifindex %d %02x:%02x:%02x:%02x:%02x:%02x\n", + sl.sll_ifindex, + sl.sll_addr[0], sl.sll_addr[1], sl.sll_addr[2], + sl.sll_addr[3], sl.sll_addr[4], sl.sll_addr[5]); + + dump_packet(buf, cc); +#endif + + bridge_bpdu_rcv(sl.sll_ifindex, buf, cc); + } +} + +/* Berkeley Packet filter code to filter out spanning tree packets. + from tcpdump -s 1152 -dd stp + */ +static struct sock_filter stp_filter[] = { + { 0x28, 0, 0, 0x0000000c }, + { 0x25, 3, 0, 0x000005dc }, + { 0x30, 0, 0, 0x0000000e }, + { 0x15, 0, 1, 0x00000042 }, + { 0x6, 0, 0, 0x00000480 }, + { 0x6, 0, 0, 0x00000000 }, +}; + +static void +packet_event(struct uloop_fd *fd, unsigned int events) +{ + struct worker_event ev = { + .type = WORKER_EV_RECV_PACKET, + }; + + worker_queue_event(&ev); +} + +/* + * Open up a raw packet socket to catch all 802.2 packets. + * and install a packet filter to only see STP (SAP 42) + * + * Since any bridged devices are already in promiscious mode + * no need to add multicast address. + */ +int packet_sock_init(void) +{ + struct sock_fprog prog = + { + .len = sizeof(stp_filter) / sizeof(stp_filter[0]), + .filter = stp_filter, + }; + int s; + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_802_2)); + if(s < 0) + { + ERROR("socket failed: %m"); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) { + ERROR("setsockopt packet filter failed: %m"); + goto out; + } + + if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) { + ERROR("fcntl set nonblock failed: %m"); + goto out; + } + + ufd.fd = s; + ufd.cb = packet_event; + uloop_fd_add(&ufd, ULOOP_READ | ULOOP_EDGE_TRIGGER); + + return 0; + +out: + close(s); + return -1; +} diff --git a/packet.h b/packet.h new file mode 100644 index 0000000..d3401ff --- /dev/null +++ b/packet.h @@ -0,0 +1,34 @@ +/***************************************************************************** + Copyright (c) 2006 EMC Corporation. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + +******************************************************************************/ + +#ifndef PACKET_SOCK_H +#define PACKET_SOCK_H + +#include + +void packet_send(int ifindex, const struct iovec *iov, int iov_count, int len); +int packet_sock_init(void); +void packet_rcv(); + +#endif /* PACKET_SOCK_H */ diff --git a/scripts/bridge-stp b/scripts/bridge-stp new file mode 100755 index 0000000..2af4a27 --- /dev/null +++ b/scripts/bridge-stp @@ -0,0 +1,16 @@ +#!/bin/sh +bridge="$1" +cmd="$2" + +export PATH='/sbin:/usr/sbin:/bin:/usr/bin' + +case "$cmd" in + start) enabled=true ;; + stop) enabled=false ;; + *) + echo "Usage: $0 {start|stop}" + exit 1 + ;; +esac + +ubus call ustp bridge_state '{ "name": "'"$bridge"'", "enabled": '$enabled' }' diff --git a/ubus.c b/ubus.c new file mode 100644 index 0000000..201761c --- /dev/null +++ b/ubus.c @@ -0,0 +1,238 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include "config.h" +#include "mstp.h" +#include "worker.h" +#include "ubus.h" + +struct blob_buf b; + +enum bridge_config_attr { + BRIDGE_CONFIG_NAME, + BRIDGE_CONFIG_PROTO, + BRIDGE_CONFIG_FWD_DELAY, + BRIDGE_CONFIG_HELLO_TIME, + BRIDGE_CONFIG_MAX_AGE, + BRIDGE_CONFIG_AGEING_TIME, + __BRIDGE_CONFIG_MAX +}; + +static const struct blobmsg_policy bridge_config_policy[__BRIDGE_CONFIG_MAX] = { + [BRIDGE_CONFIG_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [BRIDGE_CONFIG_PROTO] = { "proto", BLOBMSG_TYPE_STRING }, + [BRIDGE_CONFIG_FWD_DELAY] = { "forward_delay", BLOBMSG_TYPE_INT32 }, + [BRIDGE_CONFIG_HELLO_TIME] = { "hello_time", BLOBMSG_TYPE_INT32 }, + [BRIDGE_CONFIG_MAX_AGE] = { "max_age", BLOBMSG_TYPE_INT32 }, + [BRIDGE_CONFIG_AGEING_TIME] = { "ageing_time", BLOBMSG_TYPE_INT32 }, +}; + +static bool +ubus_set_bridge_config(struct blob_attr *attr) +{ + struct blob_attr *tb[__BRIDGE_CONFIG_MAX], *cur; + struct bridge_config *cfg; + CIST_BridgeConfig *bc; + + blobmsg_parse(bridge_config_policy, __BRIDGE_CONFIG_MAX, tb, + blobmsg_data(attr), blobmsg_len(attr)); + + cur = tb[BRIDGE_CONFIG_NAME]; + if (!cur) + return false; + + cfg = bridge_config_get(blobmsg_get_string(cur), true); + + bc = &cfg->config; + bc->protocol_version = protoRSTP; + bc->set_protocol_version = true; + + if ((cur = tb[BRIDGE_CONFIG_PROTO]) != NULL) { + const char *proto = blobmsg_get_string(cur); + + if (!strcmp(proto, "mstp")) + bc->protocol_version = protoMSTP; + else if (!strcmp(proto, "stp")) + bc->protocol_version = protoSTP; + } + + if ((cur = tb[BRIDGE_CONFIG_FWD_DELAY]) != NULL) { + bc->bridge_forward_delay = blobmsg_get_u32(cur); + bc->set_bridge_forward_delay = true; + } + + if ((cur = tb[BRIDGE_CONFIG_HELLO_TIME]) != NULL) { + bc->bridge_hello_time = blobmsg_get_u32(cur); + bc->set_bridge_hello_time = true; + } + + if ((cur = tb[BRIDGE_CONFIG_AGEING_TIME]) != NULL) { + bc->bridge_ageing_time = blobmsg_get_u32(cur); + bc->set_bridge_ageing_time = true; + } + + if ((cur = tb[BRIDGE_CONFIG_MAX_AGE]) != NULL) { + bc->bridge_max_age = blobmsg_get_u32(cur); + bc->set_bridge_max_age = true; + } + + return true; +} + +static int +ubus_add_bridge(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + if (!ubus_set_bridge_config(msg)) + return UBUS_STATUS_INVALID_ARGUMENT; + + return 0; +} + +enum bridge_state_attr { + BRIDGE_STATE_NAME, + BRIDGE_STATE_ENABLED, + __BRIDGE_STATE_MAX +}; + +static const struct blobmsg_policy bridge_state_policy[__BRIDGE_STATE_MAX] = { + [BRIDGE_STATE_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [BRIDGE_STATE_ENABLED] = { "enabled", BLOBMSG_TYPE_BOOL }, +}; + +static int +ubus_bridge_state(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__BRIDGE_STATE_MAX]; + struct bridge_config *cfg; + const char *bridge_name; + struct worker_event ev = {}; + + blobmsg_parse(bridge_state_policy, __BRIDGE_STATE_MAX, tb, + blobmsg_data(msg), blobmsg_len(msg)); + + if (!tb[BRIDGE_STATE_NAME] || !tb[BRIDGE_STATE_ENABLED]) + return UBUS_STATUS_INVALID_ARGUMENT; + + bridge_name = blobmsg_get_string(tb[BRIDGE_STATE_NAME]); + ev.bridge_idx = if_nametoindex(bridge_name); + if (!ev.bridge_idx) + return UBUS_STATUS_NOT_FOUND; + + if (blobmsg_get_bool(tb[BRIDGE_STATE_ENABLED])) { + cfg = bridge_config_get(bridge_name, false); + if (!cfg) + return UBUS_STATUS_NOT_FOUND; + + ev.type = WORKER_EV_BRIDGE_ADD; + ev.bridge_config = cfg->config; + } else { + ev.type = WORKER_EV_BRIDGE_REMOVE; + } + + worker_queue_event(&ev); + + return 0; +} + +static const struct ubus_method ustp_methods[] = { + UBUS_METHOD("add_bridge", ubus_add_bridge, bridge_config_policy), + UBUS_METHOD("bridge_state", ubus_bridge_state, bridge_state_policy), +}; + +static struct ubus_object_type ustp_object_type = + UBUS_OBJECT_TYPE("ustp", ustp_methods); + +static struct ubus_object ustp_object = { + .name = "ustp", + .type = &ustp_object_type, + .methods = ustp_methods, + .n_methods = ARRAY_SIZE(ustp_methods), +}; + +static int +netifd_device_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + if (strcmp(method, "stp_init") != 0) + return 0; + + ubus_set_bridge_config(msg); + + return 0; +} + +static struct ubus_auto_conn conn; +static struct ubus_subscriber netifd_sub; + +static void netifd_sub_cb(struct uloop_timeout *t) +{ + uint32_t id; + + if (ubus_lookup_id(&conn.ctx, "network.device", &id) != 0 || + ubus_subscribe(&conn.ctx, &netifd_sub, id) != 0) { + uloop_timeout_set(t, 1000); + return; + } + + blob_buf_init(&b, 0); + ubus_invoke(&conn.ctx, id, "stp_init", b.head, NULL, NULL, 1000); +} + +static struct uloop_timeout netifd_sub_timer = { + .cb = netifd_sub_cb, +}; + +static void +netifd_device_remove_cb(struct ubus_context *ctx, + struct ubus_subscriber *obj, uint32_t id) +{ + uloop_timeout_set(&netifd_sub_timer, 1000); +} + +static struct ubus_subscriber netifd_sub = { + .cb = netifd_device_cb, + .remove_cb = netifd_device_remove_cb, +}; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + ubus_add_object(ctx, &ustp_object); + ubus_register_subscriber(ctx, &netifd_sub); + uloop_timeout_set(&netifd_sub_timer, 1); +} + +void ustp_ubus_init(void) +{ + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); +} + +void ustp_ubus_exit(void) +{ + uint32_t id; + + ubus_remove_object(&conn.ctx, &ustp_object); + ubus_unregister_subscriber(&conn.ctx, &netifd_sub); + blob_buf_init(&b, 0); + if (ubus_lookup_id(&conn.ctx, "network.device", &id) == 0) + ubus_invoke(&conn.ctx, id, "stp_init", b.head, NULL, NULL, 1000); + ubus_auto_shutdown(&conn); +} diff --git a/ubus.h b/ubus.h new file mode 100644 index 0000000..5319984 --- /dev/null +++ b/ubus.h @@ -0,0 +1,20 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __UBUS_H +#define __UBUS_H + +void ustp_ubus_init(void); +void ustp_ubus_exit(void); + +#endif diff --git a/worker.c b/worker.c new file mode 100644 index 0000000..957767e --- /dev/null +++ b/worker.c @@ -0,0 +1,135 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include +#include + +#include "worker.h" +#include "bridge_ctl.h" +#include "bridge_track.h" +#include "packet.h" + +static pthread_t w_thread; +static pthread_mutex_t w_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t w_cond = PTHREAD_COND_INITIALIZER; +static LIST_HEAD(w_queue); +static struct uloop_timeout w_timer; + +struct worker_queued_event { + struct list_head list; + struct worker_event ev; +}; + +static struct worker_event *worker_next_event(void) +{ + struct worker_queued_event *ev; + static struct worker_event ev_data; + + pthread_mutex_lock(&w_lock); + while (list_empty(&w_queue)) + pthread_cond_wait(&w_cond, &w_lock); + + ev = list_first_entry(&w_queue, struct worker_queued_event, list); + list_del(&ev->list); + pthread_mutex_unlock(&w_lock); + + memcpy(&ev_data, &ev->ev, sizeof(ev_data)); + free(ev); + + return &ev_data; +} + +static void +handle_worker_event(struct worker_event *ev) +{ + switch (ev->type) { + case WORKER_EV_ONE_SECOND: + bridge_one_second(); + break; + case WORKER_EV_BRIDGE_EVENT: + bridge_event_handler(); + break; + case WORKER_EV_RECV_PACKET: + packet_rcv(); + break; + case WORKER_EV_BRIDGE_ADD: + bridge_create(ev->bridge_idx, &ev->bridge_config); + break; + case WORKER_EV_BRIDGE_REMOVE: + bridge_delete(ev->bridge_idx); + break; + default: + return; + } +} + +static void *worker_thread_fn(void *arg) +{ + struct worker_event *ev; + + while (1) { + ev = worker_next_event(); + if (ev->type == WORKER_EV_SHUTDOWN) + break; + + handle_worker_event(ev); + } + + return NULL; +} + +static void worker_timer_cb(struct uloop_timeout *t) +{ + struct worker_event ev = { + .type = WORKER_EV_ONE_SECOND, + }; + + uloop_timeout_set(t, 1000); + worker_queue_event(&ev); +} + +int worker_init(void) +{ + w_timer.cb = worker_timer_cb; + uloop_timeout_set(&w_timer, 1000); + + return pthread_create(&w_thread, NULL, worker_thread_fn, NULL); +} + +void worker_cleanup(void) +{ + struct worker_event ev = { + .type = WORKER_EV_SHUTDOWN, + }; + + worker_queue_event(&ev); + pthread_join(w_thread, NULL); +} + +void worker_queue_event(struct worker_event *ev) +{ + struct worker_queued_event *evc; + + evc = malloc(sizeof(*evc)); + memcpy(&evc->ev, ev, sizeof(*ev)); + + pthread_mutex_lock(&w_lock); + list_add_tail(&evc->list, &w_queue); + pthread_mutex_unlock(&w_lock); + + pthread_cond_signal(&w_cond); +} diff --git a/worker.h b/worker.h new file mode 100644 index 0000000..d308112 --- /dev/null +++ b/worker.h @@ -0,0 +1,39 @@ +/* + * ustp - OpenWrt STP/RSTP/MSTP daemon + * Copyright (C) 2021 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __WORKER_H +#define __WORKER_H + +#include "mstp.h" + +enum worker_event_type { + WORKER_EV_SHUTDOWN, + WORKER_EV_RECV_PACKET, + WORKER_EV_BRIDGE_EVENT, + WORKER_EV_BRIDGE_ADD, + WORKER_EV_BRIDGE_REMOVE, + WORKER_EV_ONE_SECOND, +}; + +struct worker_event { + enum worker_event_type type; + + int bridge_idx; + CIST_BridgeConfig bridge_config; +}; + +int worker_init(void); +void worker_cleanup(void); +void worker_queue_event(struct worker_event *ev); + +#endif -- 2.30.2