Skip to content

Commit

Permalink
route: add support for vlan filtering on bridge ports.
Browse files Browse the repository at this point in the history
  • Loading branch information
Cordell-O committed Jan 8, 2024
1 parent 4fa899c commit 44b24f4
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 0 deletions.
5 changes: 5 additions & 0 deletions include/netlink/route/link/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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, bool 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, bool untagged);
extern int rtnl_link_bridge_pvid(struct rtnl_link *link);
extern int rtnl_link_bridge_has_vlan(struct rtnl_link *link);

Expand Down
5 changes: 5 additions & 0 deletions include/netlink/route/link/bridge_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
293 changes: 293 additions & 0 deletions lib/route/link/bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#define BRIDGE_ATTR_PORT_VLAN (1 << 4)
#define BRIDGE_ATTR_HWMODE (1 << 5)
#define BRIDGE_ATTR_SELF (1 << 6)
#define BRIDGE_ATTR_MASTER (1 << 7)

#define PRIV_FLAG_NEW_ATTRS (1 << 0)

Expand All @@ -44,6 +45,7 @@ struct bridge_data
uint16_t b_hwmode;
uint16_t b_priority;
uint16_t b_self; /* here for comparison reasons */
uint16_t b_master; /* here for comparison reasons */
uint32_t b_cost;
uint32_t b_flags;
uint32_t b_flags_mask;
Expand All @@ -57,6 +59,23 @@ 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 = vlan_info->untagged_bitmap[vid / 32];
if (!mask)
return false;

uint32_t bit = (((uint32_t) 1) << vid % 32);

return mask & bit;
}

static int find_next_bit(int i, uint32_t x)
{
int j;
Expand Down Expand Up @@ -243,13 +262,136 @@ static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg,
void *data)
{
struct bridge_data *bd = data;
struct bridge_vlan_info vinfo;
struct rtnl_link_bridge_vlan * vlan_info = NULL;

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_MASTER)
NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_MASTER);

if (bd->ce_mask & BRIDGE_ATTR_HWMODE)
NLA_PUT_U16(msg, IFLA_BRIDGE_MODE, bd->b_hwmode);

if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN)
{
int i = -1, j, k;
int start = -1, prev = -1;
int done;
bool untagged = false;
vlan_info = &bd->vlan_info;

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:
Expand Down Expand Up @@ -446,6 +588,7 @@ 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_SELF, a->b_self != b->b_self);
diff |= _DIFF(BRIDGE_ATTR_MASTER, a->b_master != b->b_master);

if (flags & LOOSE_COMPARISON)
diff |= _DIFF(BRIDGE_ATTR_FLAGS,
Expand Down Expand Up @@ -774,11 +917,38 @@ int rtnl_link_bridge_set_self(struct rtnl_link *link)
IS_BRIDGE_LINK_ASSERT(link);

bd->b_self |= 1;
bd->b_master = 0;
bd->ce_mask &= ~BRIDGE_ATTR_MASTER;
bd->ce_mask |= BRIDGE_ATTR_SELF;

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_master |= 1;
bd->b_self = 0;
bd->ce_mask &= ~BRIDGE_ATTR_SELF;
bd->ce_mask |= BRIDGE_ATTR_MASTER;

return 0;
}

/**
* Get hardware mode
* @arg link Link object of type bridge
Expand Down Expand Up @@ -915,6 +1085,129 @@ uint16_t rtnl_link_bridge_str2hwmode(const char *name)

/** @} */

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 1;
}

/**
* @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.
* Supported range is 0-4095
*
* @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, bool untagged)
{
IS_BRIDGE_LINK_ASSERT(link);

struct rtnl_link_bridge_vlan * vinfo = rtnl_link_bridge_get_port_vlan(link);
if (!vinfo)
return -NLE_NOATTR;

if (start > end || end >= RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX)
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);
}
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.
* Supported range is 0-4095
*
* @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)
{
IS_BRIDGE_LINK_ASSERT(link);

struct rtnl_link_bridge_vlan * vinfo = rtnl_link_bridge_get_port_vlan(link);
if (!vinfo)
return -NLE_NOATTR;

if (start > end || end >= RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX)
return -NLE_INVAL;

for (uint16_t i = start; i <= end; i++)
{
if (i != vinfo->pvid)
{
unset_bit(i,vinfo->vlan_bitmap);
unset_bit(i,vinfo->untagged_bitmap);
}
}
return 1;
}

/**
* 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-4095
*
* @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, bool untagged)
{
IS_BRIDGE_LINK_ASSERT(link);

struct rtnl_link_bridge_vlan * vinfo = rtnl_link_bridge_get_port_vlan(link);
if (!vinfo)
return -NLE_NOATTR;

if (pvid >= RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX)
return -NLE_INVAL;

vinfo->pvid = pvid;

set_bit(pvid,vinfo->vlan_bitmap);
if (untagged)
set_bit(pvid,vinfo->untagged_bitmap);

return 0;
}

/** @} */

int rtnl_link_bridge_pvid(struct rtnl_link *link)
{
struct bridge_data *bd;
Expand Down
Loading

0 comments on commit 44b24f4

Please sign in to comment.