summaryrefslogtreecommitdiff
path: root/lib/route/link/bridge.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/route/link/bridge.c')
-rw-r--r--lib/route/link/bridge.c478
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)