diff --git a/include/netlink/route/link/bridge.h b/include/netlink/route/link/bridge.h index bd051b2f..f51bdcad 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 9523a49e..2df8d85e 100644 --- a/lib/route/link/bridge.c +++ b/lib/route/link/bridge.c @@ -14,11 +14,13 @@ #include "nl-default.h" #include +#include #include #include #include #include +#include #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 @@ int _nl_bridge_fill_vlan_info(struct nl_msg *msg, struct rtnl_link_bridge_vlan * 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 d8e3e0c0..75883323 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;