Initial import
authorFelix Fietkau <nbd@nbd.name>
Thu, 19 Aug 2021 06:59:56 +0000 (08:59 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 24 Aug 2021 15:32:43 +0000 (17:32 +0200)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
25 files changed:
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
bridge_ctl.h [new file with mode: 0644]
bridge_track.c [new file with mode: 0644]
bridge_track.h [new file with mode: 0644]
brmon.c [new file with mode: 0644]
config.c [new file with mode: 0644]
config.h [new file with mode: 0644]
driver.h [new file with mode: 0644]
hmac_md5.c [new file with mode: 0644]
libnetlink.c [new file with mode: 0644]
libnetlink.h [new file with mode: 0644]
log.h [new file with mode: 0644]
main.c [new file with mode: 0644]
mstp.c [new file with mode: 0644]
mstp.h [new file with mode: 0644]
netif_utils.c [new file with mode: 0644]
netif_utils.h [new file with mode: 0644]
packet.c [new file with mode: 0644]
packet.h [new file with mode: 0644]
scripts/bridge-stp [new file with mode: 0755]
ubus.c [new file with mode: 0644]
ubus.h [new file with mode: 0644]
worker.c [new file with mode: 0644]
worker.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0c9da2b
--- /dev/null
@@ -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 (file)
index 0000000..bb96cab
--- /dev/null
@@ -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 (file)
index 0000000..3cd1b7f
--- /dev/null
@@ -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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+
+#ifndef BRIDGE_CTL_H
+#define BRIDGE_CTL_H
+
+#include <stdbool.h>
+#include <net/if.h>
+#include <linux/if_ether.h>
+
+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 (file)
index 0000000..9b9f02f
--- /dev/null
@@ -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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+#define _GNU_SOURCE
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <linux/param.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <dirent.h>
+
+#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 (file)
index 0000000..6c0bd4c
--- /dev/null
@@ -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 (file)
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 <shemminger@osdl.org>
+ * Modified by Srinivas Aji <Aji_Srinivas@emc.com>
+ *    for use in RSTP daemon. - 2006-09-01
+ * Modified by Vitalii Demianets <dvitasgs@gmail.com>
+ *    for use in MSTP daemon. - 2011-07-18
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <libubox/uloop.h>
+
+#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 (file)
index 0000000..e424318
--- /dev/null
+++ b/config.c
@@ -0,0 +1,68 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <string.h>
+
+#include <libubox/avl-cmp.h>
+#include <libubox/utils.h>
+
+#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 (file)
index 0000000..e28b463
--- /dev/null
+++ b/config.h
@@ -0,0 +1,32 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <libubox/avl.h>
+#include <stdint.h>
+#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 (file)
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 <dvitasgs@gmail.com>
+ */
+
+#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 (file)
index 0000000..a799407
--- /dev/null
@@ -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 <string.h>
+#include <sys/types.h>
+#include <libubox/md5.h>
+
+#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 (file)
index 0000000..95162ec
--- /dev/null
@@ -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, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <net/if_arp.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/uio.h>
+
+#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 (file)
index 0000000..7621596
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef __LIBNETLINK_H__
+#define __LIBNETLINK_H__
+
+#include <stdio.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <sys/types.h>
+
+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 (file)
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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#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 (file)
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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+
+/* #define MISC_TEST_FUNCS */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <syslog.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <libubox/uloop.h>
+
+#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 <stdarg.h>
+#include <time.h>
+
+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 (file)
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 <dvitasgs@gmail.com>
+ */
+
+/* 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 <config.h>
+
+#include <string.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <asm/byteorder.h>
+#include <time.h>
+#include <sys/time.h>
+
+#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 (file)
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 <dvitasgs@gmail.com>
+ */
+
+#ifndef MSTP_H
+#define MSTP_H
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <libubox/list.h>
+
+#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 (file)
index 0000000..66241b2
--- /dev/null
@@ -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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <linux/if_ether.h>
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+
+#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 (file)
index 0000000..2da8418
--- /dev/null
@@ -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 <Aji_Srinivas@emc.com>
+  Authors: Vitalii Demianets <dvitasgs@gmail.com>
+
+******************************************************************************/
+
+#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 (file)
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 <Aji_Srinivas@emc.com>
+  Authors: Stephen Hemminger <shemminger@linux-foundation.org>
+
+******************************************************************************/
+
+/* #define PACKET_DEBUG */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
+#include <asm/byteorder.h>
+#include <libubox/uloop.h>
+
+#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 (file)
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 <Aji_Srinivas@emc.com>
+
+******************************************************************************/
+
+#ifndef PACKET_SOCK_H
+#define PACKET_SOCK_H
+
+#include <sys/uio.h>
+
+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 (executable)
index 0000000..2af4a27
--- /dev/null
@@ -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 <bridge> {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 (file)
index 0000000..201761c
--- /dev/null
+++ b/ubus.c
@@ -0,0 +1,238 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <libubus.h>
+#include <libubox/uloop.h>
+#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 (file)
index 0000000..5319984
--- /dev/null
+++ b/ubus.h
@@ -0,0 +1,20 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 (file)
index 0000000..957767e
--- /dev/null
+++ b/worker.c
@@ -0,0 +1,135 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <pthread.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libubox/uloop.h>
+#include <libubox/utils.h>
+
+#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 (file)
index 0000000..d308112
--- /dev/null
+++ b/worker.h
@@ -0,0 +1,39 @@
+/*
+ * ustp - OpenWrt STP/RSTP/MSTP daemon
+ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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