summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCordell O'Leary <cordell.oleary@alliedtelesis.co.nz>2024-01-08 01:39:24 (GMT)
committerThomas Haller <thaller@redhat.com>2024-02-29 20:00:29 (GMT)
commit4f324f7303fddf736f8b71504e48efe6cca72ace (patch)
tree27e8be9ed427d8ff0d8d8994eddd7f452f542528
parentbf071f2b84f436b7182da925181f48d1c8a7a5a9 (diff)
downloadlibnl-4f324f7303fddf736f8b71504e48efe6cca72ace.zip
libnl-4f324f7303fddf736f8b71504e48efe6cca72ace.tar.gz
libnl-4f324f7303fddf736f8b71504e48efe6cca72ace.tar.bz2
route: add support for vlan filtering on bridge ports.
-rw-r--r--include/netlink/route/link/bridge.h5
-rw-r--r--include/netlink/route/link/bridge_info.h5
-rw-r--r--lib/route/link/bridge.c331
-rw-r--r--lib/route/link/bridge_info.c63
-rw-r--r--libnl-route-3.sym6
5 files changed, 402 insertions, 8 deletions
diff --git a/include/netlink/route/link/bridge.h b/include/netlink/route/link/bridge.h
index e606bd4..0de59c4 100644
--- a/include/netlink/route/link/bridge.h
+++ b/include/netlink/route/link/bridge.h
@@ -61,6 +61,7 @@ extern int rtnl_link_bridge_set_flags(struct rtnl_link *, unsigned int);
extern int rtnl_link_bridge_get_flags(struct rtnl_link *);
extern int rtnl_link_bridge_set_self(struct rtnl_link *);
+extern int rtnl_link_bridge_set_master(struct rtnl_link *);
extern int rtnl_link_bridge_get_hwmode(struct rtnl_link *, uint16_t *);
extern int rtnl_link_bridge_set_hwmode(struct rtnl_link *, uint16_t);
@@ -76,6 +77,10 @@ extern uint16_t rtnl_link_bridge_str2hwmode(const char *);
extern int rtnl_link_bridge_add(struct nl_sock *sk, const char *name);
+extern int rtnl_link_bridge_enable_vlan(struct rtnl_link *link);
+extern int rtnl_link_bridge_set_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end, int untagged);
+extern int rtnl_link_bridge_unset_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end);
+extern int rtnl_link_bridge_set_port_vlan_pvid (struct rtnl_link *link, uint16_t pvid);
extern int rtnl_link_bridge_pvid(struct rtnl_link *link);
extern int rtnl_link_bridge_has_vlan(struct rtnl_link *link);
diff --git a/include/netlink/route/link/bridge_info.h b/include/netlink/route/link/bridge_info.h
index d315486..e8448a4 100644
--- a/include/netlink/route/link/bridge_info.h
+++ b/include/netlink/route/link/bridge_info.h
@@ -28,6 +28,11 @@ extern void rtnl_link_bridge_set_vlan_protocol(struct rtnl_link *link,
extern int rtnl_link_bridge_get_vlan_protocol(struct rtnl_link *link,
uint16_t *vlan_protocol);
+extern void rtnl_link_bridge_set_vlan_default_pvid(struct rtnl_link *link,
+ uint16_t default_pvid);
+extern int rtnl_link_bridge_get_vlan_default_pvid(struct rtnl_link *link,
+ uint16_t *default_pvid);
+
extern void rtnl_link_bridge_set_vlan_stats_enabled(struct rtnl_link *link,
uint8_t vlan_stats_enabled);
extern int rtnl_link_bridge_get_vlan_stats_enabled(struct rtnl_link *link,
diff --git a/lib/route/link/bridge.c b/lib/route/link/bridge.c
index 5b44164..3d186be 100644
--- a/lib/route/link/bridge.c
+++ b/lib/route/link/bridge.c
@@ -33,7 +33,7 @@
#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 BRIDGE_ATTR_CONFIG_MODE (1 << 6)
#define PRIV_FLAG_NEW_ATTRS (1 << 0)
@@ -43,7 +43,7 @@ struct bridge_data
uint8_t b_priv_flags; /* internal flags */
uint16_t b_hwmode;
uint16_t b_priority;
- uint16_t b_self; /* here for comparison reasons */
+ uint16_t b_config_mode;
uint32_t b_cost;
uint32_t b_flags;
uint32_t b_flags_mask;
@@ -57,6 +57,24 @@ static void set_bit(unsigned nr, uint32_t *addr)
addr[nr / 32] |= (((uint32_t) 1) << (nr % 32));
}
+static void unset_bit(unsigned nr, uint32_t *addr)
+{
+ if (nr < RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX)
+ addr[nr / 32] &= ~(((uint32_t) 1) << (nr % 32));
+}
+
+static bool vlan_id_untagged(struct rtnl_link_bridge_vlan *vlan_info, uint16_t vid)
+{
+ uint32_t mask, bit;
+
+ _nl_assert(vid / 32u < ARRAY_SIZE(vlan_info->untagged_bitmap));
+
+ mask = vlan_info->untagged_bitmap[vid / 32];
+ bit = (((uint32_t) 1) << vid % 32);
+
+ return mask & bit;
+}
+
static int find_next_bit(int i, uint32_t x)
{
int j;
@@ -239,16 +257,150 @@ static int bridge_parse_af_full(struct rtnl_link *link, struct nlattr *attr_full
return 0;
}
+static int bridge_fill_vlan_info(struct nl_msg *msg, struct rtnl_link_bridge_vlan * vlan_info)
+{
+ struct bridge_vlan_info vinfo;
+ int i = -1, j, k;
+ int start = -1, prev = -1;
+ int done;
+ bool untagged = false;
+
+ for (k = 0; k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; k++)
+ {
+ int base_bit;
+ uint32_t a = vlan_info->vlan_bitmap[k];
+
+ base_bit = k * 32;
+ i = -1;
+ done = 0;
+ while (!done)
+ {
+ j = find_next_bit(i, a);
+ if (j > 0)
+ {
+ /* Skip if id equal to pvid */
+ if (vlan_info->pvid != 0 && j - 1 + base_bit == vlan_info->pvid)
+ goto nxt;
+ /* first hit of any bit */
+ if (start < 0 && prev < 0)
+ {
+ start = prev = j - 1 + base_bit;
+ /* Start range attribute */
+ untagged = vlan_id_untagged(vlan_info,start);
+ vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = start;
+ goto nxt;
+ }
+ /* this bit is a continuation of prior bits */
+ if (j - 2 + base_bit == prev)
+ {
+ prev++;
+ /* Hit end of untagged/tagged range */
+ if (untagged != vlan_id_untagged(vlan_info,prev))
+ {
+ /* put vlan into attributes */
+ if (start == prev-1)
+ {
+ /* only 1 vid in range */
+ vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+ }
+ else
+ {
+ /* end of untagged/tagged range */
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+
+ vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = prev-1;
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+ }
+ /* start of new range */
+ untagged = !untagged;
+ vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = prev;
+ }
+ goto nxt;
+ }
+ }
+ else
+ done = 1;
+
+ if (start >= 0)
+ {
+ if (done && k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN - 1)
+ break;
+
+ if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN && start != prev)
+ {
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+
+ vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = prev;
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+ }
+ else if (start == prev)
+ {
+ vinfo.flags = untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = start;
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+ }
+
+ if (done)
+ break;
+ }
+ if (j > 0)
+ {
+ start = prev = j - 1 + base_bit;
+ untagged = vlan_id_untagged(vlan_info,start);
+ vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = start;
+ }
+nxt:
+ i = j;
+ }
+ }
+
+ if (vlan_info->pvid != 0)
+ {
+ untagged = vlan_id_untagged(vlan_info,vlan_info->pvid);
+ vinfo.flags = BRIDGE_VLAN_INFO_PVID;
+ vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+ vinfo.vid = vlan_info->pvid;
+ NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+ }
+
+ return 0;
+
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
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);
+ bd->b_config_mode = BRIDGE_FLAGS_SELF;
+ bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
+ }
+
+ if (bd->ce_mask & BRIDGE_ATTR_CONFIG_MODE)
+ NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, bd->b_config_mode);
+
+ if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN)
+ {
+ if(bridge_fill_vlan_info(msg,&bd->vlan_info)){
+ goto nla_put_failure;
+ }
+ }
return 0;
@@ -445,7 +597,7 @@ static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b,
memcmp(&a->vlan_info, &b->vlan_info,
sizeof(struct rtnl_link_bridge_vlan)));
diff |= _DIFF(BRIDGE_ATTR_HWMODE, a->b_hwmode != b->b_hwmode);
- diff |= _DIFF(BRIDGE_ATTR_SELF, a->b_self != b->b_self);
+ diff |= _DIFF(BRIDGE_ATTR_CONFIG_MODE, a->b_config_mode != b->b_config_mode);
if (flags & LOOSE_COMPARISON)
diff |= _DIFF(BRIDGE_ATTR_FLAGS,
@@ -773,8 +925,31 @@ int rtnl_link_bridge_set_self(struct rtnl_link *link)
IS_BRIDGE_LINK_ASSERT(link);
- bd->b_self |= 1;
- bd->ce_mask |= BRIDGE_ATTR_SELF;
+ bd->b_config_mode = BRIDGE_FLAGS_SELF;
+ bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
+
+ return 0;
+}
+
+/**
+ * Set link change type to master
+ * @arg link Link Object of type bridge
+ *
+ * This will set the bridge change flag to master, meaning that changes to
+ * be applied with this link object will be applied directly to the virtual
+ * device in a bridge instead of the physical device.
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ */
+int rtnl_link_bridge_set_master(struct rtnl_link *link)
+{
+ struct bridge_data *bd = bridge_data(link);
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd->b_config_mode = BRIDGE_FLAGS_MASTER;
+ bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
return 0;
}
@@ -915,6 +1090,146 @@ uint16_t rtnl_link_bridge_str2hwmode(const char *name)
/** @} */
+/**
+ * Enable the ability to set vlan info
+ * @arg link Link object of type bridge
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ */
+int rtnl_link_bridge_enable_vlan(struct rtnl_link *link)
+{
+ struct bridge_data *bd = bridge_data(link);
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ bd->ce_mask |= BRIDGE_ATTR_PORT_VLAN;
+
+ return 0;
+}
+
+/**
+ * @name Quality of Service
+ * @{
+ */
+
+/**
+ * Set port vlan membership range
+ * @arg link Link object of type bridge
+ * @arg start Start of membership range.
+ * @arg end End of membership range.
+ * @arg untagged Set membership range to be untagged.
+ *
+ * This will set the vlan membership range for a bridge port.
+ * This will unset the untagged membership if untagged is false.
+ * Supported range is 1-4094
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR if port vlan attribute not present
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_INVAL range is not in supported range.
+ */
+int rtnl_link_bridge_set_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end, int untagged)
+{
+ struct rtnl_link_bridge_vlan * vinfo;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+ if (!vinfo)
+ return -NLE_NOATTR;
+
+ if (start == 0 || start > end || end >= VLAN_VID_MASK)
+ return -NLE_INVAL;
+
+ for (uint16_t i = start; i <= end; i++)
+ {
+ set_bit(i,vinfo->vlan_bitmap);
+ if (untagged) {
+ set_bit(i,vinfo->untagged_bitmap);
+ } else {
+ unset_bit(i,vinfo->untagged_bitmap);
+ }
+ }
+ return 0;
+}
+
+/**
+ * Unset port vlan membership range
+ * @arg link Link object of type bridge
+ * @arg start Start of membership range.
+ * @arg end End of membership range.
+ *
+ * This will unset the vlan membership range for a bridge port
+ * for both tagged and untagged membership.
+ * Supported range is 1-4094
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR if port vlan attribute not present
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_INVAL range is not in supported range.
+ */
+int rtnl_link_bridge_unset_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end)
+{
+ struct rtnl_link_bridge_vlan * vinfo;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+ if (!vinfo)
+ return -NLE_NOATTR;
+
+ if (start == 0 || start > end || end >= VLAN_VID_MASK)
+ return -NLE_INVAL;
+
+ for (uint16_t i = start; i <= end; i++)
+ {
+ unset_bit(i,vinfo->vlan_bitmap);
+ unset_bit(i,vinfo->untagged_bitmap);
+ }
+ return 0;
+}
+
+/**
+ * Set port primary vlan id
+ * @arg link Link object of type bridge
+ * @arg pvid PVID to set.
+ * @arg untagged Set vlan id to be untagged.
+ *
+ * This will set the primary vlan id for a bridge port.
+ * Supported range is 0-4094, Setting pvid to 0 will unset it.
+ * You will most likely want to set/unset pvid in the vlan map.
+ * @see rtnl_link_bridge_set_port_vlan_map_range()
+ * @see rtnl_link_bridge_unset_port_vlan_map_range()
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR if port vlan attribute not present
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ * @return -NLE_INVAL PVID is above supported range.
+ */
+int rtnl_link_bridge_set_port_vlan_pvid (struct rtnl_link *link, uint16_t pvid)
+{
+ struct rtnl_link_bridge_vlan * vinfo;
+
+ IS_BRIDGE_LINK_ASSERT(link);
+
+ vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+ if (!vinfo)
+ return -NLE_NOATTR;
+
+ if (pvid >= VLAN_VID_MASK)
+ return -NLE_INVAL;
+
+ vinfo->pvid = pvid;
+
+ return 0;
+}
+
+/** @} */
+
int rtnl_link_bridge_pvid(struct rtnl_link *link)
{
struct bridge_data *bd;
diff --git a/lib/route/link/bridge_info.c b/lib/route/link/bridge_info.c
index 41ea670..311e947 100644
--- a/lib/route/link/bridge_info.c
+++ b/lib/route/link/bridge_info.c
@@ -22,17 +22,20 @@
#define BRIDGE_ATTR_VLAN_PROTOCOL (1 << 1)
#define BRIDGE_ATTR_VLAN_STATS_ENABLED (1 << 2)
#define BRIDGE_ATTR_AGEING_TIME (1 << 3)
+#define BRIDGE_ATTR_VLAN_DEFAULT_PVID (1 << 4)
struct bridge_info {
uint32_t ce_mask; /* to support attr macros */
uint32_t b_ageing_time;
uint16_t b_vlan_protocol;
+ uint16_t b_vlan_default_pvid;
uint8_t b_vlan_filtering;
uint8_t b_vlan_stats_enabled;
};
static const struct nla_policy bi_attrs_policy[IFLA_BR_MAX + 1] = {
[IFLA_BR_AGEING_TIME] = { .type = NLA_U32 },
+ [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 },
[IFLA_BR_VLAN_FILTERING] = { .type = NLA_U8 },
[IFLA_BR_VLAN_PROTOCOL] = { .type = NLA_U16 },
[IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 },
@@ -83,6 +86,12 @@ static int bridge_info_parse(struct rtnl_link *link, struct nlattr *data,
bi->ce_mask |= BRIDGE_ATTR_AGEING_TIME;
}
+ if (tb[IFLA_BR_VLAN_DEFAULT_PVID]) {
+ bi->b_vlan_default_pvid =
+ nla_get_u16(tb[IFLA_BR_VLAN_DEFAULT_PVID]);
+ bi->ce_mask |= BRIDGE_ATTR_VLAN_DEFAULT_PVID;
+ }
+
if (tb[IFLA_BR_VLAN_FILTERING]) {
bi->b_vlan_filtering = nla_get_u8(tb[IFLA_BR_VLAN_FILTERING]);
bi->ce_mask |= BRIDGE_ATTR_VLAN_FILTERING;
@@ -118,6 +127,10 @@ static int bridge_info_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
if (bi->ce_mask & BRIDGE_ATTR_VLAN_FILTERING)
NLA_PUT_U8(msg, IFLA_BR_VLAN_FILTERING, bi->b_vlan_filtering);
+ if (bi->ce_mask & BRIDGE_ATTR_VLAN_DEFAULT_PVID)
+ NLA_PUT_U16(msg, IFLA_BR_VLAN_DEFAULT_PVID,
+ bi->b_vlan_default_pvid);
+
if (bi->ce_mask & BRIDGE_ATTR_VLAN_PROTOCOL)
NLA_PUT_U16(msg, IFLA_BR_VLAN_PROTOCOL,
htons(bi->b_vlan_protocol));
@@ -302,6 +315,56 @@ int rtnl_link_bridge_get_vlan_protocol(struct rtnl_link *link,
}
/**
+ * Set VLAN default pvid
+ * @arg link Link object of type bridge
+ * @arg default pvid VLAN default pvid to set.
+ *
+ * @see rtnl_link_bridge_get_vlan_default_pvid()
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_vlan_default_pvid(struct rtnl_link *link,
+ uint16_t default_pvid)
+{
+ struct bridge_info *bi = bridge_info(link);
+
+ IS_BRIDGE_INFO_ASSERT(link);
+
+ bi->b_vlan_default_pvid = default_pvid;
+
+ bi->ce_mask |= BRIDGE_ATTR_VLAN_DEFAULT_PVID;
+}
+
+/**
+ * Get VLAN default pvid
+ * @arg link Link object of type bridge
+ * @arg default_pvid Output argument.
+ *
+ * @see rtnl_link_bridge_set_vlan_default_pvid()
+ *
+ * @return Zero on success, otherwise a negative error code.
+ * @retval -NLE_NOATTR
+ * @retval -NLE_INVAL
+ */
+int rtnl_link_bridge_get_vlan_default_pvid(struct rtnl_link *link,
+ uint16_t *default_pvid)
+{
+ struct bridge_info *bi = bridge_info(link);
+
+ IS_BRIDGE_INFO_ASSERT(link);
+
+ if (!(bi->ce_mask & BRIDGE_ATTR_VLAN_DEFAULT_PVID))
+ return -NLE_NOATTR;
+
+ if (!default_pvid)
+ return -NLE_INVAL;
+
+ *default_pvid = bi->b_vlan_default_pvid;
+
+ return 0;
+}
+
+/**
* Set VLAN stats enabled flag
* @arg link Link object of type bridge
* @arg vlan_stats_enabled VLAN stats enabled flag to set
diff --git a/libnl-route-3.sym b/libnl-route-3.sym
index 85782d8..eb4752a 100644
--- a/libnl-route-3.sym
+++ b/libnl-route-3.sym
@@ -1317,6 +1317,12 @@ global:
libnl_3_10 {
global:
+ rtnl_link_bridge_enable_vlan;
rtnl_link_bridge_get_ageing_time;
+ rtnl_link_bridge_get_vlan_default_pvid;
rtnl_link_bridge_set_ageing_time;
+ rtnl_link_bridge_set_master;
+ rtnl_link_bridge_set_port_vlan_map_range;
+ rtnl_link_bridge_set_port_vlan_pvid;
+ rtnl_link_bridge_unset_port_vlan_map_range;
} libnl_3_9;