diff options
Diffstat (limited to 'lib/route/link/bridge.c')
-rw-r--r-- | lib/route/link/bridge.c | 478 |
1 files changed, 472 insertions, 6 deletions
diff --git a/lib/route/link/bridge.c b/lib/route/link/bridge.c index 4ca5f6e6..2d95faf8 100644 --- a/lib/route/link/bridge.c +++ b/lib/route/link/bridge.c @@ -25,11 +25,16 @@ #include <netlink-private/route/link/api.h> #include <linux/if_bridge.h> +#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_SELF (1 << 6) #define PRIV_FLAG_NEW_ATTRS (1 << 0) @@ -37,13 +42,38 @@ 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_self; /* here for comparison reasons */ 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; }; +static void set_bit(unsigned nr, uint32_t *addr) +{ + if (nr < RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX) + addr[nr / 32] |= (((uint32_t) 1) << (nr % 32)); +} + +static int find_next_bit(int i, uint32_t x) +{ + int j; + + if (i >= 32) + return -1; + + /* find first bit */ + if (i < 0) + return __builtin_ffs(x); + + /* mask off prior finds to get next */ + j = __builtin_ffs(x >> i); + return j ? j + i : 0; +} + static struct rtnl_link_af_ops bridge_ops; #define IS_BRIDGE_LINK_ASSERT(link) \ @@ -85,6 +115,9 @@ static struct nla_policy br_attrs_policy[IFLA_BRPORT_MAX+1] = { [IFLA_BRPORT_GUARD] = { .type = NLA_U8 }, [IFLA_BRPORT_PROTECT] = { .type = NLA_U8 }, [IFLA_BRPORT_FAST_LEAVE] = { .type = NLA_U8 }, + [IFLA_BRPORT_LEARNING] = { .type = NLA_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NLA_U8 }, + [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NLA_U8 }, }; static void check_flag(struct rtnl_link *link, struct nlattr *attrs[], @@ -137,10 +170,228 @@ static int bridge_parse_protinfo(struct rtnl_link *link, struct nlattr *attr, check_flag(link, br_attrs, IFLA_BRPORT_GUARD, RTNL_BRIDGE_BPDU_GUARD); check_flag(link, br_attrs, IFLA_BRPORT_PROTECT, RTNL_BRIDGE_ROOT_BLOCK); check_flag(link, br_attrs, IFLA_BRPORT_FAST_LEAVE, RTNL_BRIDGE_FAST_LEAVE); + check_flag(link, br_attrs, IFLA_BRPORT_UNICAST_FLOOD, + RTNL_BRIDGE_UNICAST_FLOOD); + check_flag(link, br_attrs, IFLA_BRPORT_LEARNING, RTNL_BRIDGE_LEARNING); + check_flag(link, br_attrs, IFLA_BRPORT_LEARNING_SYNC, + RTNL_BRIDGE_LEARNING_SYNC); + + return 0; +} + +static int bridge_parse_af_full(struct rtnl_link *link, struct nlattr *attr_full, + void *data) +{ + struct bridge_data *bd = data; + struct bridge_vlan_info *vinfo = NULL; + uint16_t vid_range_start = 0; + uint16_t vid_range_flags = -1; + + struct nlattr *attr; + int remaining; + + nla_for_each_nested(attr, attr_full, remaining) { + + if (nla_type(attr) == IFLA_BRIDGE_MODE) { + bd->b_hwmode = nla_get_u16(attr); + bd->ce_mask |= BRIDGE_ATTR_HWMODE; + } else if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO) + continue; + + if (nla_len(attr) != sizeof(struct bridge_vlan_info)) + return -EINVAL; + + vinfo = nla_data(attr); + if (!vinfo->vid || vinfo->vid >= VLAN_VID_MASK) + return -EINVAL; + + + if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { + vid_range_start = vinfo->vid; + vid_range_flags = (vinfo->flags ^ BRIDGE_VLAN_INFO_RANGE_BEGIN); + continue; + } + + if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END) { + /* sanity check the range flags */ + if (vid_range_flags != (vinfo->flags ^ BRIDGE_VLAN_INFO_RANGE_END)) { + NL_DBG(1, "VLAN range flags differ; can not handle it.\n"); + return -EINVAL; + } + } else { + vid_range_start = vinfo->vid; + } + + for (; vid_range_start <= vinfo->vid; vid_range_start++) { + if (vinfo->flags & BRIDGE_VLAN_INFO_PVID) + bd->vlan_info.pvid = vinfo->vid; + + if (vinfo->flags & BRIDGE_VLAN_INFO_UNTAGGED) + set_bit(vid_range_start, bd->vlan_info.untagged_bitmap); + + set_bit(vid_range_start, bd->vlan_info.vlan_bitmap); + bd->ce_mask |= BRIDGE_ATTR_PORT_VLAN; + } + + vid_range_flags = -1; + } return 0; } +static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg, + void *data) +{ + struct bridge_data *bd = data; + + if ((bd->ce_mask & BRIDGE_ATTR_SELF)||(bd->ce_mask & BRIDGE_ATTR_HWMODE)) + NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); + + if (bd->ce_mask & BRIDGE_ATTR_HWMODE) + NLA_PUT_U16(msg, IFLA_BRIDGE_MODE, bd->b_hwmode); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static int bridge_fill_pi(struct rtnl_link *link, struct nl_msg *msg, + void *data) +{ + struct bridge_data *bd = data; + + if (bd->ce_mask & BRIDGE_ATTR_FLAGS) { + if (bd->b_flags_mask & RTNL_BRIDGE_BPDU_GUARD) { + NLA_PUT_U8(msg, IFLA_BRPORT_GUARD, + bd->b_flags & RTNL_BRIDGE_BPDU_GUARD); + } + if (bd->b_flags_mask & RTNL_BRIDGE_HAIRPIN_MODE) { + NLA_PUT_U8(msg, IFLA_BRPORT_MODE, + bd->b_flags & RTNL_BRIDGE_HAIRPIN_MODE); + } + if (bd->b_flags_mask & RTNL_BRIDGE_FAST_LEAVE) { + NLA_PUT_U8(msg, IFLA_BRPORT_FAST_LEAVE, + bd->b_flags & RTNL_BRIDGE_FAST_LEAVE); + } + if (bd->b_flags_mask & RTNL_BRIDGE_ROOT_BLOCK) { + NLA_PUT_U8(msg, IFLA_BRPORT_PROTECT, + bd->b_flags & RTNL_BRIDGE_ROOT_BLOCK); + } + if (bd->b_flags_mask & RTNL_BRIDGE_UNICAST_FLOOD) { + NLA_PUT_U8(msg, IFLA_BRPORT_UNICAST_FLOOD, + bd->b_flags & RTNL_BRIDGE_UNICAST_FLOOD); + } + if (bd->b_flags_mask & RTNL_BRIDGE_LEARNING) { + NLA_PUT_U8(msg, IFLA_BRPORT_LEARNING, + bd->b_flags & RTNL_BRIDGE_LEARNING); + } + if (bd->b_flags_mask & RTNL_BRIDGE_LEARNING_SYNC) { + NLA_PUT_U8(msg, IFLA_BRPORT_LEARNING_SYNC, + bd->b_flags & RTNL_BRIDGE_LEARNING_SYNC); + } + } + + if (bd->ce_mask & BRIDGE_ATTR_COST) + NLA_PUT_U32(msg, IFLA_BRPORT_COST, bd->b_cost); + + if (bd->ce_mask & BRIDGE_ATTR_PRIORITY) + NLA_PUT_U16(msg, IFLA_BRPORT_PRIORITY, bd->b_priority); + + if (bd->ce_mask & BRIDGE_ATTR_PORT_STATE) + NLA_PUT_U8(msg, IFLA_BRPORT_STATE, bd->b_port_state); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static int bridge_override_rtm(struct rtnl_link *link) { + struct bridge_data *bd; + + if (!rtnl_link_is_bridge(link)) + return 0; + + bd = bridge_data(link); + + if (bd->ce_mask & BRIDGE_ATTR_FLAGS) + return 1; + + return 0; +} + +static int bridge_get_af(struct nl_msg *msg, uint32_t *ext_filter_mask) +{ + *ext_filter_mask |= RTEXT_FILTER_BRVLAN; + return 0; +} + +static void dump_bitmap(struct nl_dump_params *p, const uint32_t *b) +{ + int i = -1, j, k; + int start = -1, prev = -1; + int done, found = 0; + + for (k = 0; k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; k++) { + int base_bit; + uint32_t a = b[k]; + + base_bit = k * 32; + i = -1; + done = 0; + while (!done) { + j = find_next_bit(i, a); + if (j > 0) { + /* first hit of any bit */ + if (start < 0 && prev < 0) { + start = prev = j - 1 + base_bit; + goto next; + } + /* this bit is a continuation of prior bits */ + if (j - 2 + base_bit == prev) { + prev++; + goto next; + } + } else + done = 1; + + if (start >= 0) { + found++; + if (done && k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN - 1) + break; + + nl_dump(p, " %d", start); + if (start != prev) + nl_dump(p, "-%d", prev); + + if (done) + break; + } + if (j > 0) + start = prev = j - 1 + base_bit; +next: + i = j; + } + } + if (!found) + nl_dump(p, " <none>"); + + return; +} + +static void rtnl_link_bridge_dump_vlans(struct nl_dump_params *p, + struct bridge_data *bd) +{ + nl_dump(p, "pvid %u", bd->vlan_info.pvid); + + nl_dump(p, " all vlans:"); + dump_bitmap(p, bd->vlan_info.vlan_bitmap); + + nl_dump(p, " untagged vlans:"); + dump_bitmap(p, bd->vlan_info.untagged_bitmap); +} + static void bridge_dump_details(struct rtnl_link *link, struct nl_dump_params *p, void *data) { @@ -157,6 +408,24 @@ static void bridge_dump_details(struct rtnl_link *link, if (bd->ce_mask & BRIDGE_ATTR_COST) nl_dump(p, "cost %u ", bd->b_cost); + if (bd->ce_mask & BRIDGE_ATTR_HWMODE) { + char hbuf[32]; + + rtnl_link_bridge_hwmode2str(bd->b_hwmode, hbuf, sizeof(hbuf)); + nl_dump(p, "hwmode %s", hbuf); + } + + if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) + rtnl_link_bridge_dump_vlans(p, bd); + + if (bd->ce_mask & BRIDGE_ATTR_FLAGS) { + char buf[256]; + + rtnl_link_bridge_flags2str(bd->b_flags & bd->b_flags_mask, + buf, sizeof(buf)); + nl_dump(p, "%s", buf); + } + nl_dump(p, "\n"); } @@ -171,6 +440,10 @@ static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b, diff |= BRIDGE_DIFF(PORT_STATE, a->b_port_state != b->b_port_state); diff |= BRIDGE_DIFF(PRIORITY, a->b_priority != b->b_priority); diff |= BRIDGE_DIFF(COST, a->b_cost != b->b_cost); + diff |= BRIDGE_DIFF(PORT_VLAN, memcmp(&a->vlan_info, &b->vlan_info, + sizeof(struct rtnl_link_bridge_vlan))); + diff |= BRIDGE_DIFF(HWMODE, a->b_hwmode != b->b_hwmode); + diff |= BRIDGE_DIFF(SELF, a->b_self != b->b_self); if (flags & LOOSE_COMPARISON) diff |= BRIDGE_DIFF(FLAGS, @@ -203,8 +476,8 @@ struct rtnl_link *rtnl_link_bridge_alloc(void) return link; } - -/** + +/** * Create a new kernel bridge device * @arg sk netlink socket * @arg name name of the bridge device or NULL @@ -440,6 +713,9 @@ int rtnl_link_bridge_unset_flags(struct rtnl_link *link, unsigned int flags) * - RTNL_BRIDGE_BPDU_GUARD * - RTNL_BRIDGE_ROOT_BLOCK * - RTNL_BRIDGE_FAST_LEAVE + * - RTNL_BRIDGE_UNICAST_FLOOD + * - RTNL_BRIDGE_LEARNING + * - RTNL_BRIDGE_LEARNING_SYNC * * @see rtnl_link_bridge_unset_flags() * @see rtnl_link_bridge_get_flags() @@ -479,11 +755,99 @@ int rtnl_link_bridge_get_flags(struct rtnl_link *link) return bd->b_flags; } +/** + * Set link change type to self + * @arg link Link Object of type bridge + * + * This will set the bridge change flag to self, meaning that changes to + * be applied with this link object will be applied directly to the physical + * device in a bridge instead of the virtual device. + * + * @return 0 on success or negative error code + * @return -NLE_OPNOTSUP Link is not a bridge + */ +int rtnl_link_bridge_set_self(struct rtnl_link *link) +{ + struct bridge_data *bd = bridge_data(link); + + IS_BRIDGE_LINK_ASSERT(link); + + bd->b_self |= 1; + bd->ce_mask |= BRIDGE_ATTR_SELF; + + return 0; +} + +/** + * Get hardware mode + * @arg link Link object of type bridge + * @arg hwmode Output argument. + * + * @see rtnl_link_bridge_set_hwmode() + * + * @return 0 if hardware mode is present and returned in hwmode + * @return -NLE_NOATTR if hardware mode is not present + * @return -NLE_OPNOTSUP Link is not a bridge + */ +int rtnl_link_bridge_get_hwmode(struct rtnl_link *link, uint16_t *hwmode) +{ + struct bridge_data *bd = bridge_data(link); + + IS_BRIDGE_LINK_ASSERT(link); + + if (!(bd->ce_mask & BRIDGE_ATTR_HWMODE)) + return -NLE_NOATTR; + + *hwmode = bd->b_hwmode; + return 0; +} + +/** + * Set hardware mode + * @arg link Link object of type bridge + * @arg hwmode Hardware mode to set on link + * + * This will set the hardware mode of a link when it supports hardware + * offloads for bridging. + * @see rtnl_link_bridge_get_hwmode() + * + * Valid modes are: + * - RTNL_BRIDGE_HWMODE_VEB + * - RTNL_BRIDGE_HWMODE_VEPA + * + * When setting hardware mode, the change type will be set to self. + * @see rtnl_link_bridge_set_self() + * + * @return 0 on success or negative error code + * @return -NLE_OPNOTSUP Link is not a bridge + * @return -NLE_INVAL when specified hwmode is unsupported. + */ +int rtnl_link_bridge_set_hwmode(struct rtnl_link *link, uint16_t hwmode) +{ + int err; + struct bridge_data *bd = bridge_data(link); + + if (hwmode > RTNL_BRIDGE_HWMODE_MAX) + return -NLE_INVAL; + + if ((err = rtnl_link_bridge_set_self(link)) < 0) + return err; + + bd->b_hwmode = hwmode; + bd->ce_mask |= BRIDGE_ATTR_HWMODE; + + return 0; +} + + static const struct trans_tbl bridge_flags[] = { - __ADD(RTNL_BRIDGE_HAIRPIN_MODE, hairpin_mode) - __ADD(RTNL_BRIDGE_BPDU_GUARD, bpdu_guard) - __ADD(RTNL_BRIDGE_ROOT_BLOCK, root_block) - __ADD(RTNL_BRIDGE_FAST_LEAVE, fast_leave) + __ADD(RTNL_BRIDGE_HAIRPIN_MODE, hairpin_mode), + __ADD(RTNL_BRIDGE_BPDU_GUARD, bpdu_guard), + __ADD(RTNL_BRIDGE_ROOT_BLOCK, root_block), + __ADD(RTNL_BRIDGE_FAST_LEAVE, fast_leave), + __ADD(RTNL_BRIDGE_UNICAST_FLOOD, flood), + __ADD(RTNL_BRIDGE_LEARNING, learning), + __ADD(RTNL_BRIDGE_LEARNING_SYNC, learning_sync), }; /** @@ -503,6 +867,101 @@ int rtnl_link_bridge_str2flags(const char *name) /** @} */ +static const struct trans_tbl port_states[] = { + __ADD(BR_STATE_DISABLED, disabled), + __ADD(BR_STATE_LISTENING, listening), + __ADD(BR_STATE_LEARNING, learning), + __ADD(BR_STATE_FORWARDING, forwarding), + __ADD(BR_STATE_BLOCKING, blocking), +}; + +/** + * @name Port State Translation + * @{ + */ + +char *rtnl_link_bridge_portstate2str(int st, char *buf, size_t len) +{ + return __type2str(st, buf, len, port_states, ARRAY_SIZE(port_states)); +} + +int rtnl_link_bridge_str2portstate(const char *name) +{ + return __str2type(name, port_states, ARRAY_SIZE(port_states)); +} + +/** @} */ + +static const struct trans_tbl hw_modes[] = { + __ADD(RTNL_BRIDGE_HWMODE_VEB, veb), + __ADD(RTNL_BRIDGE_HWMODE_VEPA, vepa), + __ADD(RTNL_BRIDGE_HWMODE_UNDEF, undef), +}; + +/** + * @name Hardware Mode Translation + * @{ + */ + +char *rtnl_link_bridge_hwmode2str(uint16_t st, char *buf, size_t len) { + return __type2str(st, buf, len, hw_modes, ARRAY_SIZE(hw_modes)); +} + +uint16_t rtnl_link_bridge_str2hwmode(const char *name) +{ + return __str2type(name, hw_modes, ARRAY_SIZE(hw_modes)); +} + +/** @} */ + +int rtnl_link_bridge_pvid(struct rtnl_link *link) +{ + struct bridge_data *bd; + + IS_BRIDGE_LINK_ASSERT(link); + + bd = link->l_af_data[AF_BRIDGE]; + if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) + return (int) bd->vlan_info.pvid; + + return -EINVAL; +} + +int rtnl_link_bridge_has_vlan(struct rtnl_link *link) +{ + struct bridge_data *bd; + int i; + + IS_BRIDGE_LINK_ASSERT(link); + + bd = link->l_af_data[AF_BRIDGE]; + if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) { + if (bd->vlan_info.pvid) + return 1; + + for (i = 0; i < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; ++i) { + if (bd->vlan_info.vlan_bitmap[i] || + bd->vlan_info.untagged_bitmap[i]) + return 1; + } + } + return 0; +} + +struct rtnl_link_bridge_vlan *rtnl_link_bridge_get_port_vlan(struct rtnl_link *link) +{ + struct bridge_data *data; + + if (!rtnl_link_is_bridge(link)) + return NULL; + + data = link->l_af_data[AF_BRIDGE]; + if (data && (data->ce_mask & BRIDGE_ATTR_PORT_VLAN)) + return &data->vlan_info; + + return NULL; +} + static struct rtnl_link_af_ops bridge_ops = { .ao_family = AF_BRIDGE, .ao_alloc = &bridge_alloc, @@ -511,6 +970,13 @@ static struct rtnl_link_af_ops bridge_ops = { .ao_parse_protinfo = &bridge_parse_protinfo, .ao_dump[NL_DUMP_DETAILS] = &bridge_dump_details, .ao_compare = &bridge_compare, + .ao_parse_af_full = &bridge_parse_af_full, + .ao_get_af = &bridge_get_af, + .ao_fill_af = &bridge_fill_af, + .ao_fill_pi = &bridge_fill_pi, + .ao_fill_pi_flags = NLA_F_NESTED, + .ao_override_rtm = &bridge_override_rtm, + .ao_fill_af_no_nest = 1, }; static void __init bridge_init(void) |