summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRonan Dalton <ronan.dalton@alliedtelesis.co.nz>2024-10-07 23:05:53 (GMT)
committerThomas Haller <thaller@redhat.com>2024-10-10 08:00:34 (GMT)
commitb75e27deb8957a04fd84539cd9a1f772930d17d2 (patch)
treeef2e06ea24856c5776073119312901e0a8aed70f
parent83f29cf93a25e41b53c36ba6b2959fbe65150e70 (diff)
downloadlibnl-b75e27deb8957a04fd84539cd9a1f772930d17d2.zip
libnl-b75e27deb8957a04fd84539cd9a1f772930d17d2.tar.gz
libnl-b75e27deb8957a04fd84539cd9a1f772930d17d2.tar.bz2
lib/route: add support for bridge msti
https://github.com/thom311/libnl/pull/409
-rw-r--r--include/netlink/route/link/bridge.h7
-rw-r--r--lib/route/link/bridge.c521
-rw-r--r--libnl-route-3.sym5
3 files changed, 506 insertions, 27 deletions
diff --git a/include/netlink/route/link/bridge.h b/include/netlink/route/link/bridge.h
index bd051b2..f51bdca 100644
--- a/include/netlink/route/link/bridge.h
+++ b/include/netlink/route/link/bridge.h
@@ -96,6 +96,13 @@ extern int rtnl_link_bridge_pvid(struct rtnl_link *link);
extern int rtnl_link_bridge_has_vlan(struct rtnl_link *link);
extern struct rtnl_link_bridge_vlan *rtnl_link_bridge_get_port_vlan(struct rtnl_link *link);
+
+extern int rtnl_link_bridge_set_mst_port_state(struct rtnl_link *link, uint16_t instance, uint8_t state);
+extern int rtnl_link_bridge_get_mst_port_state(struct rtnl_link *link, uint16_t instance);
+extern int rtnl_link_bridge_del_mst_port_state(struct rtnl_link *link, uint16_t instance);
+extern int rtnl_link_bridge_clear_mst_port_state_info(struct rtnl_link *link);
+extern int rtnl_link_bridge_foreach_mst_entry(struct rtnl_link *link, void (*cb)(uint16_t instance, uint8_t state, void *arg), void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/route/link/bridge.c b/lib/route/link/bridge.c
index 9523a49..2df8d85 100644
--- a/lib/route/link/bridge.c
+++ b/lib/route/link/bridge.c
@@ -14,11 +14,13 @@
#include "nl-default.h"
#include <linux/if_bridge.h>
+#include <linux/rtnetlink.h>
#include <netlink/netlink.h>
#include <netlink/attr.h>
#include <netlink/route/rtnl.h>
#include <netlink/route/link/bridge.h>
+#include <netlink/list.h>
#include "nl-route.h"
#include "link-api.h"
@@ -28,28 +30,36 @@
#define VLAN_VID_MASK 0x0fff /* VLAN Identifier */
/** @cond SKIP */
-#define BRIDGE_ATTR_PORT_STATE (1 << 0)
-#define BRIDGE_ATTR_PRIORITY (1 << 1)
-#define BRIDGE_ATTR_COST (1 << 2)
-#define BRIDGE_ATTR_FLAGS (1 << 3)
-#define BRIDGE_ATTR_PORT_VLAN (1 << 4)
-#define BRIDGE_ATTR_HWMODE (1 << 5)
-#define BRIDGE_ATTR_CONFIG_MODE (1 << 6)
+#define BRIDGE_ATTR_PORT_STATE (1UL << 0)
+#define BRIDGE_ATTR_PRIORITY (1UL << 1)
+#define BRIDGE_ATTR_COST (1UL << 2)
+#define BRIDGE_ATTR_FLAGS (1UL << 3)
+#define BRIDGE_ATTR_PORT_VLAN (1UL << 4)
+#define BRIDGE_ATTR_HWMODE (1UL << 5)
+#define BRIDGE_ATTR_CONFIG_MODE (1UL << 6)
+#define BRIDGE_ATTR_MST (1UL << 7)
-#define PRIV_FLAG_NEW_ATTRS (1 << 0)
+#define PRIV_FLAG_NEW_ATTRS (1UL << 0)
struct bridge_data
{
- uint8_t b_port_state;
- uint8_t b_priv_flags; /* internal flags */
- uint16_t b_hwmode;
- uint16_t b_priority;
- uint16_t b_config_mode;
- uint32_t b_cost;
- uint32_t b_flags;
- uint32_t b_flags_mask;
- uint32_t ce_mask; /* HACK to support attr macros */
- struct rtnl_link_bridge_vlan vlan_info;
+ uint8_t b_port_state;
+ uint8_t b_priv_flags; /* internal flags */
+ uint16_t b_hwmode;
+ uint16_t b_priority;
+ uint16_t b_config_mode;
+ uint32_t b_cost;
+ uint32_t b_flags;
+ uint32_t b_flags_mask;
+ uint32_t ce_mask; /* HACK to support attr macros */
+ struct rtnl_link_bridge_vlan vlan_info;
+ struct nl_list_head mst_list;
+};
+
+struct mst_state_entry {
+ struct nl_list_head list_node;
+ uint16_t msti; /* unique within a list */
+ uint8_t state;
};
static void set_bit(unsigned nr, uint32_t *addr)
@@ -107,22 +117,116 @@ static inline struct bridge_data *bridge_data(struct rtnl_link *link)
static void *bridge_alloc(struct rtnl_link *link)
{
- return calloc(1, sizeof(struct bridge_data));
+ struct bridge_data *bridge_data = calloc(1, sizeof(struct bridge_data));
+
+ if (bridge_data == NULL)
+ return NULL;
+
+ nl_init_list_head(&bridge_data->mst_list);
+
+ return bridge_data;
}
-static void *bridge_clone(struct rtnl_link *link, void *data)
+static struct mst_state_entry *mst_state_entry_alloc(void)
{
- struct bridge_data *bd;
+ struct mst_state_entry *entry;
- if ((bd = bridge_alloc(link)))
- memcpy(bd, data, sizeof(*bd));
+ entry = calloc(1, sizeof(*entry));
+ if (entry == NULL)
+ return NULL;
+
+ nl_init_list_head(&entry->list_node);
- return bd;
+ return entry;
+}
+
+static struct mst_state_entry *mst_state_entry_create(uint16_t msti,
+ uint8_t state)
+{
+ struct mst_state_entry *entry;
+
+ entry = mst_state_entry_alloc();
+ if (entry == NULL)
+ return NULL;
+
+ entry->msti = msti;
+ entry->state = state;
+
+ return entry;
+}
+
+static void mst_state_entry_del(struct mst_state_entry *entry)
+{
+ nl_list_del(&entry->list_node);
+ free(entry);
+}
+
+static void mst_list_clear(struct nl_list_head *mst_list)
+{
+ struct mst_state_entry *entry;
+ struct mst_state_entry *entry_safe;
+
+ nl_list_for_each_entry_safe(entry, entry_safe, mst_list, list_node)
+ mst_state_entry_del(entry);
+}
+
+static void bridge_data_free(struct bridge_data *bd)
+{
+ mst_list_clear(&bd->mst_list);
+ free(bd);
}
static void bridge_free(struct rtnl_link *link, void *data)
{
- free(data);
+ bridge_data_free(data);
+}
+
+static struct mst_state_entry *find_mst_state_entry(struct bridge_data *bd,
+ uint16_t msti)
+{
+ struct mst_state_entry *entry;
+
+ nl_list_for_each_entry(entry, &bd->mst_list, list_node) {
+ if (entry->msti == msti)
+ return entry;
+ }
+
+ return NULL;
+}
+
+static struct mst_state_entry *
+mst_state_entry_clone(struct mst_state_entry *src)
+{
+ return mst_state_entry_create(src->msti, src->state);
+}
+
+static void *bridge_clone(struct rtnl_link *link, void *data)
+{
+ struct bridge_data *src_bd = (struct bridge_data *)data;
+ struct bridge_data *dst_bd;
+ struct mst_state_entry *entry;
+
+ dst_bd = calloc(1, sizeof(*dst_bd));
+ if (!dst_bd)
+ return NULL;
+
+ memcpy(dst_bd, src_bd, sizeof(*dst_bd));
+
+ nl_init_list_head(&dst_bd->mst_list);
+
+ nl_list_for_each_entry(entry, &src_bd->mst_list, list_node) {
+ struct mst_state_entry *entry_copy =
+ mst_state_entry_clone(entry);
+
+ if (!entry_copy) {
+ bridge_data_free(dst_bd);
+ return NULL;
+ }
+
+ nl_list_add_tail(&entry_copy->list_node, &dst_bd->mst_list);
+ }
+
+ return dst_bd;
}
static struct nla_policy br_attrs_policy[IFLA_BRPORT_MAX+1] = {
@@ -226,6 +330,70 @@ static int bridge_parse_protinfo(struct rtnl_link *link, struct nlattr *attr,
return 0;
}
+static const struct nla_policy br_mst_entry_policy[IFLA_BRIDGE_MST_ENTRY_MAX + 1] = {
+ [IFLA_BRIDGE_MST_ENTRY_MSTI] = { .type = NLA_U16 },
+ [IFLA_BRIDGE_MST_ENTRY_STATE] = { .type = NLA_U8 },
+};
+
+static int bridge_parse_mst_state_entry(struct nlattr *attr,
+ struct bridge_data *bd)
+{
+ struct nlattr *tb[IFLA_BRIDGE_MST_ENTRY_MAX + 1];
+ struct mst_state_entry *new_entry;
+ struct mst_state_entry *existing_entry;
+ uint16_t msti;
+ uint8_t state;
+
+ if (nla_parse_nested(tb, IFLA_BRIDGE_MST_ENTRY_MAX, attr,
+ br_mst_entry_policy) < 0)
+ return -EINVAL;
+
+ if (!tb[IFLA_BRIDGE_MST_ENTRY_MSTI] ||
+ !tb[IFLA_BRIDGE_MST_ENTRY_STATE]) {
+ return -EINVAL;
+ }
+
+ msti = nla_get_u16(tb[IFLA_BRIDGE_MST_ENTRY_MSTI]);
+ state = nla_get_u8(tb[IFLA_BRIDGE_MST_ENTRY_STATE]);
+
+ existing_entry = find_mst_state_entry(bd, msti);
+ if (existing_entry) {
+ existing_entry->state = state;
+ return 0;
+ }
+
+ new_entry = mst_state_entry_create(msti, state);
+ if (!new_entry)
+ return -ENOMEM;
+
+ nl_list_add_tail(&new_entry->list_node, &bd->mst_list);
+ bd->ce_mask |= BRIDGE_ATTR_MST;
+
+ return 0;
+}
+
+static int bridge_parse_mst(struct nlattr *mst_attr, struct bridge_data *bd) {
+ struct nlattr *attr;
+ int remaining;
+
+ nla_for_each_nested(attr, mst_attr, remaining) {
+ int err = 0;
+
+ switch (nla_type(attr)) {
+ case IFLA_BRIDGE_MST_ENTRY:
+ err = bridge_parse_mst_state_entry(attr, bd);
+ break;
+ default:
+ continue;
+ }
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static int bridge_parse_af_full(struct rtnl_link *link, struct nlattr *attr_full,
void *data)
{
@@ -243,6 +411,11 @@ static int bridge_parse_af_full(struct rtnl_link *link, struct nlattr *attr_full
bd->b_hwmode = nla_get_u16(attr);
bd->ce_mask |= BRIDGE_ATTR_HWMODE;
continue;
+ } else if (nla_type(attr) == IFLA_BRIDGE_MST) {
+ int err = bridge_parse_mst(attr, bd);
+ if (err < 0)
+ return err;
+ continue;
} else if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO)
continue;
@@ -410,6 +583,41 @@ nla_put_failure:
return -NLE_MSGSIZE;
}
+static int bridge_fill_mst(struct nl_msg *msg, struct nl_list_head *mst_list) {
+ struct nlattr *attr = NULL;
+ struct nlattr *entry_attr = NULL;
+ struct mst_state_entry *entry;
+
+ if (nl_list_empty(mst_list))
+ return 0;
+
+ attr = nla_nest_start(msg, IFLA_BRIDGE_MST);
+ if (!attr)
+ goto err_out;
+
+ nl_list_for_each_entry(entry, mst_list, list_node) {
+ entry_attr = nla_nest_start(msg, IFLA_BRIDGE_MST_ENTRY);
+ if (!entry_attr)
+ goto err_out_nest_cancel_attr;
+
+ NLA_PUT_U16(msg, IFLA_BRIDGE_MST_ENTRY_MSTI, entry->msti);
+ NLA_PUT_U8(msg, IFLA_BRIDGE_MST_ENTRY_STATE, entry->state);
+
+ nla_nest_end(msg, entry_attr);
+ }
+
+ nla_nest_end(msg, attr);
+
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(msg, entry_attr);
+err_out_nest_cancel_attr:
+ nla_nest_cancel(msg, attr);
+err_out:
+ return -NLE_MSGSIZE;
+}
+
static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg,
void *data)
{
@@ -422,6 +630,13 @@ static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg,
bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
}
+ if (bd->ce_mask & BRIDGE_ATTR_MST)
+ {
+ if (bridge_fill_mst(msg, &bd->mst_list) < 0) {
+ goto nla_put_failure;
+ }
+ }
+
if (bd->ce_mask & BRIDGE_ATTR_CONFIG_MODE)
NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, bd->b_config_mode);
@@ -543,7 +758,7 @@ static int bridge_override_rtm(struct rtnl_link *link) {
bd = bridge_data(link);
- if (bd->ce_mask & BRIDGE_ATTR_FLAGS)
+ if (bd->ce_mask & (BRIDGE_ATTR_FLAGS | BRIDGE_ATTR_MST))
return 1;
return 0;
@@ -551,7 +766,7 @@ static int bridge_override_rtm(struct rtnl_link *link) {
static int bridge_get_af(struct nl_msg *msg, uint32_t *ext_filter_mask)
{
- *ext_filter_mask |= RTEXT_FILTER_BRVLAN;
+ *ext_filter_mask |= RTEXT_FILTER_BRVLAN | RTEXT_FILTER_MST;
return 0;
}
@@ -620,6 +835,21 @@ static void rtnl_link_bridge_dump_vlans(struct nl_dump_params *p,
dump_bitmap(p, bd->vlan_info.untagged_bitmap);
}
+static const char *const br_port_state_names[] = {
+ [BR_STATE_DISABLED] = "disabled",
+ [BR_STATE_LISTENING] = "listening",
+ [BR_STATE_LEARNING] = "learning",
+ [BR_STATE_FORWARDING] = "forwarding",
+ [BR_STATE_BLOCKING] = "blocking",
+};
+
+static const char *stp_state2str(uint8_t state) {
+ if (state > BR_STATE_BLOCKING)
+ return "unknown";
+
+ return br_port_state_names[state];
+}
+
static void bridge_dump_details(struct rtnl_link *link,
struct nl_dump_params *p, void *data)
{
@@ -655,6 +885,61 @@ static void bridge_dump_details(struct rtnl_link *link,
}
nl_dump(p, "\n");
+
+ if (bd->ce_mask & BRIDGE_ATTR_MST && !nl_list_empty(&bd->mst_list)) {
+ struct mst_state_entry *entry;
+
+ nl_dump_line(p, " mst:\n");
+
+ nl_list_for_each_entry(entry, &bd->mst_list, list_node) {
+ nl_dump_line(p, " instance %u: %s\n",
+ entry->msti, stp_state2str(entry->state));
+ }
+ }
+}
+
+static bool mst_state_entries_are_equal(const struct mst_state_entry *a,
+ const struct mst_state_entry *b)
+{
+ return a->msti == b->msti && a->state == b->state;
+}
+
+/**
+ * Compares two MST lists for equality
+ * @arg list_1 The first list
+ * @arg list_2 The second list
+ *
+ * This comparison checks that the MST lists have the same entries and that they
+ * are in the same order (although the ordering is not significant to the
+ * kernel).
+ *
+ * @return true if the lists are equal, false otherwise
+ */
+static bool msts_lists_are_equal(struct nl_list_head *list_1,
+ struct nl_list_head *list_2)
+{
+ struct mst_state_entry *entry_a;
+ struct mst_state_entry *entry_b;
+
+ entry_a =
+ nl_list_entry(list_1->next, struct mst_state_entry, list_node);
+ entry_b =
+ nl_list_entry(list_2->next, struct mst_state_entry, list_node);
+
+ /* while both lists have items left to process */
+ while (&entry_a->list_node != list_1 && &entry_b->list_node != list_2) {
+ if (!mst_state_entries_are_equal(entry_a, entry_b)) {
+ return false;
+ }
+
+ entry_a = nl_list_entry(entry_a->list_node.next,
+ struct mst_state_entry, list_node);
+ entry_b = nl_list_entry(entry_b->list_node.next,
+ struct mst_state_entry, list_node);
+ }
+
+ /* return true only if both lists were the same length */
+ return &entry_a->list_node == list_1 && &entry_b->list_node == list_2;
}
static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b,
@@ -674,6 +959,8 @@ static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b,
sizeof(struct rtnl_link_bridge_vlan)));
diff |= _DIFF(BRIDGE_ATTR_HWMODE, a->b_hwmode != b->b_hwmode);
diff |= _DIFF(BRIDGE_ATTR_CONFIG_MODE, a->b_config_mode != b->b_config_mode);
+ diff |= _DIFF(BRIDGE_ATTR_MST,
+ !msts_lists_are_equal(&a->mst_list, &b->mst_list));
if (flags & LOOSE_COMPARISON)
diff |= _DIFF(BRIDGE_ATTR_FLAGS,
@@ -1376,6 +1663,186 @@ struct rtnl_link_bridge_vlan *rtnl_link_bridge_get_port_vlan(struct rtnl_link *l
return NULL;
}
+/**
+ * Set the Multiple Spanning Tree (MST) port state for a given MST instance
+ * @arg link Link object of type bridge
+ * @arg instance MST instance number
+ * @arg state Port state to set (BR_STATE_*)
+ *
+ * @return 0 on success or a negative error code
+ * @return -NLE_INVAL link is NULL
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_NOMEM Memory allocation failed
+ */
+int rtnl_link_bridge_set_mst_port_state(struct rtnl_link *link,
+ uint16_t instance, uint8_t state)
+{
+ struct bridge_data *bd;
+ struct mst_state_entry *existing_entry;
+ struct mst_state_entry *new_entry;
+
+ if (link == NULL)
+ return -NLE_INVAL;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd = bridge_data(link);
+ if (bd == NULL)
+ return -NLE_OPNOTSUPP;
+
+ existing_entry = find_mst_state_entry(bd, instance);
+
+ if (existing_entry != NULL) {
+ existing_entry->state = state;
+ return 0;
+ }
+
+ new_entry = mst_state_entry_create(instance, state);
+ if (new_entry == NULL)
+ return -NLE_NOMEM;
+
+ nl_list_add_tail(&new_entry->list_node, &bd->mst_list);
+ bd->ce_mask |= BRIDGE_ATTR_MST;
+
+ return 0;
+}
+
+/**
+ * Get the Multiple Spanning Tree (MST) port state for a given MST instance
+ * @arg link Link object of type bridge
+ * @arg instance MST instance number
+ *
+ * @return The state (BR_STATE_*) on success, or a negative error code
+ * @return -NLE_INVAL link is NULL
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_OBJ_NOTFOUND MST instance not found
+ */
+int rtnl_link_bridge_get_mst_port_state(struct rtnl_link *link,
+ uint16_t instance)
+{
+ struct bridge_data *bd;
+ struct mst_state_entry *entry;
+
+ if (link == NULL)
+ return -NLE_INVAL;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd = bridge_data(link);
+ if (bd == NULL)
+ return -NLE_OPNOTSUPP;
+
+ entry = find_mst_state_entry(bd, instance);
+
+ if (entry == NULL)
+ return -NLE_OBJ_NOTFOUND;
+
+ return entry->state;
+}
+
+/**
+ * Delete the Multiple Spanning Tree (MST) port state for a given MST instance
+ * @arg link Link object of type bridge
+ * @arg instance MST instance number
+ *
+ * @return 0 on success or a negative error code
+ * @return -NLE_INVAL link is NULL
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_OBJ_NOTFOUND MST instance not found
+ */
+int rtnl_link_bridge_del_mst_port_state(struct rtnl_link *link,
+ uint16_t instance)
+{
+ struct bridge_data *bd;
+ struct mst_state_entry *entry;
+
+ if (link == NULL)
+ return -NLE_INVAL;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd = bridge_data(link);
+ if (bd == NULL)
+ return -NLE_OPNOTSUPP;
+
+ entry = find_mst_state_entry(bd, instance);
+
+ if (entry == NULL)
+ return -NLE_OBJ_NOTFOUND;
+
+ mst_state_entry_del(entry);
+
+ if (nl_list_empty(&bd->mst_list))
+ bd->ce_mask &= ~BRIDGE_ATTR_MST;
+
+ return 0;
+}
+
+/**
+ * Delete all Multiple Spanning Tree (MST) port state information
+ * @arg link Link object of type bridge
+ *
+ * @return 0 on success or a negative error code
+ * @return -NLE_INVAL link is NULL
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ */
+int rtnl_link_bridge_clear_mst_port_state_info(struct rtnl_link *link)
+{
+ struct bridge_data *bd;
+
+ if (link == NULL)
+ return -NLE_INVAL;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd = bridge_data(link);
+ if (bd == NULL)
+ return -NLE_OPNOTSUPP;
+
+ mst_list_clear(&bd->mst_list);
+ bd->ce_mask &= ~BRIDGE_ATTR_MST;
+
+ return 0;
+}
+
+/**
+ * Iterate over all Multiple Spanning Tree (MST) port state entries
+ * @arg link Link object of type bridge
+ * @arg cb Callback function
+ * @arg arg User provided data argument to pass to the callback
+ * function
+ *
+ * The callback function is called for each MST entry. It is passed the MST
+ * instance ID, state (BR_STATE_*), and an optional user provided data argument.
+ * MST entries should not be added or removed by the callback function.
+ *
+ * @return 0 on success or a negative error code
+ * @return -NLE_INVAL link or cb is NULL
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ */
+int rtnl_link_bridge_foreach_mst_entry(
+ struct rtnl_link *link,
+ void (*cb)(uint16_t instance, uint8_t state, void *arg), void *arg)
+{
+ struct bridge_data *bd;
+ struct mst_state_entry *entry;
+
+ if (link == NULL || cb == NULL)
+ return -NLE_INVAL;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd = bridge_data(link);
+ if (bd == NULL)
+ return -NLE_OPNOTSUPP;
+
+ nl_list_for_each_entry(entry, &bd->mst_list, list_node) {
+ cb(entry->msti, entry->state, arg);
+ }
+
+ return 0;
+}
+
static struct rtnl_link_af_ops bridge_ops = {
.ao_family = AF_BRIDGE,
.ao_alloc = &bridge_alloc,
diff --git a/libnl-route-3.sym b/libnl-route-3.sym
index d8e3e0c..7588332 100644
--- a/libnl-route-3.sym
+++ b/libnl-route-3.sym
@@ -1340,9 +1340,13 @@ global:
rtnl_link_bond_get_miimon;
rtnl_link_bond_get_min_links;
rtnl_link_bond_get_mode;
+ rtnl_link_bridge_clear_mst_port_state_info;
+ rtnl_link_bridge_del_mst_port_state;
+ rtnl_link_bridge_foreach_mst_entry;
rtnl_link_bridge_get_boolopt;
rtnl_link_bridge_get_mcast_router;
rtnl_link_bridge_get_mcast_snooping;
+ rtnl_link_bridge_get_mst_port_state;
rtnl_link_bridge_get_nf_call_arptables;
rtnl_link_bridge_get_nf_call_ip6tables;
rtnl_link_bridge_get_nf_call_iptables;
@@ -1350,6 +1354,7 @@ global:
rtnl_link_bridge_set_boolopt;
rtnl_link_bridge_set_mcast_router;
rtnl_link_bridge_set_mcast_snooping;
+ rtnl_link_bridge_set_mst_port_state;
rtnl_link_bridge_set_stp_state;
rtnl_link_bridge_set_vlan_default_pvid;
rtnl_link_get_perm_addr;