Skip to content

Commit

Permalink
bridge: Add vlan support to static neighbors
Browse files Browse the repository at this point in the history
When a user adds bridge neighbors, allow him to specify VLAN id.
If the VLAN id is not specified, the neighbor will be added
for VLANs currently in the ports filter list.  If no VLANs are
configured on the port, we use vlan 0 and only add 1 entry.

Signed-off-by: Vlad Yasevich <[email protected]>
Acked-by: Jitendra Kalsaria <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
Vlad Yasevich authored and davem330 committed Feb 14, 2013
1 parent b0e9a30 commit 1690be6
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 35 deletions.
2 changes: 1 addition & 1 deletion drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -7002,7 +7002,7 @@ static int ixgbe_ndo_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
return err;
}

static int ixgbe_ndo_fdb_del(struct ndmsg *ndm,
static int ixgbe_ndo_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr)
{
Expand Down
1 change: 1 addition & 0 deletions drivers/net/ethernet/mellanox/mlx4/en_netdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,7 @@ static int mlx4_en_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
}

static int mlx4_en_fdb_del(struct ndmsg *ndm,
struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr)
{
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ static int qlcnic_set_mac(struct net_device *netdev, void *p)
return 0;
}

static int qlcnic_fdb_del(struct ndmsg *ndm, struct net_device *netdev,
const unsigned char *addr)
static int qlcnic_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *netdev, const unsigned char *addr)
{
struct qlcnic_adapter *adapter = netdev_priv(netdev);
int err = -EOPNOTSUPP;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/macvlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ static int macvlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
return err;
}

static int macvlan_fdb_del(struct ndmsg *ndm,
static int macvlan_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr)
{
Expand Down
3 changes: 2 additions & 1 deletion drivers/net/vxlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
}

/* Delete entry (via netlink) */
static int vxlan_fdb_delete(struct ndmsg *ndm, struct net_device *dev,
static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
Expand Down
4 changes: 3 additions & 1 deletion include/linux/netdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,8 @@ struct netdev_fcoe_hbainfo {
* struct net_device *dev,
* const unsigned char *addr, u16 flags)
* Adds an FDB entry to dev for addr.
* int (*ndo_fdb_del)(struct ndmsg *ndm, struct net_device *dev,
* int (*ndo_fdb_del)(struct ndmsg *ndm, struct nlattr *tb[],
* struct net_device *dev,
* const unsigned char *addr)
* Deletes the FDB entry from dev coresponding to addr.
* int (*ndo_fdb_dump)(struct sk_buff *skb, struct netlink_callback *cb,
Expand Down Expand Up @@ -1008,6 +1009,7 @@ struct net_device_ops {
const unsigned char *addr,
u16 flags);
int (*ndo_fdb_del)(struct ndmsg *ndm,
struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr);
int (*ndo_fdb_dump)(struct sk_buff *skb,
Expand Down
1 change: 1 addition & 0 deletions include/uapi/linux/neighbour.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum {
NDA_LLADDR,
NDA_CACHEINFO,
NDA_PROBES,
NDA_VLAN,
__NDA_MAX
};

Expand Down
148 changes: 131 additions & 17 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
ci.ndm_refcnt = 0;
if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci))
goto nla_put_failure;

if (nla_put(skb, NDA_VLAN, sizeof(u16), &fdb->vlan_id))
goto nla_put_failure;

return nlmsg_end(skb, nlh);

nla_put_failure:
Expand All @@ -516,6 +520,7 @@ static inline size_t fdb_nlmsg_size(void)
{
return NLMSG_ALIGN(sizeof(struct ndmsg))
+ nla_total_size(ETH_ALEN) /* NDA_LLADDR */
+ nla_total_size(sizeof(u16)) /* NDA_VLAN */
+ nla_total_size(sizeof(struct nda_cacheinfo));
}

Expand Down Expand Up @@ -617,71 +622,180 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
return 0;
}

static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge_port *p,
const unsigned char *addr, u16 nlh_flags, u16 vid)
{
int err = 0;

if (ndm->ndm_flags & NTF_USE) {
rcu_read_lock();
br_fdb_update(p->br, p, addr, vid);
rcu_read_unlock();
} else {
spin_lock_bh(&p->br->hash_lock);
err = fdb_add_entry(p, addr, ndm->ndm_state,
nlh_flags, vid);
spin_unlock_bh(&p->br->hash_lock);
}

return err;
}

/* Add new permanent fdb entry with RTM_NEWNEIGH */
int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr, u16 nlh_flags)
{
struct net_bridge_port *p;
int err = 0;
struct net_port_vlans *pv;
unsigned short vid = VLAN_N_VID;

if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE))) {
pr_info("bridge: RTM_NEWNEIGH with invalid state %#x\n", ndm->ndm_state);
return -EINVAL;
}

if (tb[NDA_VLAN]) {
if (nla_len(tb[NDA_VLAN]) != sizeof(unsigned short)) {
pr_info("bridge: RTM_NEWNEIGH with invalid vlan\n");
return -EINVAL;
}

vid = nla_get_u16(tb[NDA_VLAN]);

if (vid >= VLAN_N_VID) {
pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n",
vid);
return -EINVAL;
}
}

p = br_port_get_rtnl(dev);
if (p == NULL) {
pr_info("bridge: RTM_NEWNEIGH %s not a bridge port\n",
dev->name);
return -EINVAL;
}

if (ndm->ndm_flags & NTF_USE) {
rcu_read_lock();
br_fdb_update(p->br, p, addr, 0);
rcu_read_unlock();
pv = nbp_get_vlan_info(p);
if (vid != VLAN_N_VID) {
if (!pv || !test_bit(vid, pv->vlan_bitmap)) {
pr_info("bridge: RTM_NEWNEIGH with unconfigured "
"vlan %d on port %s\n", vid, dev->name);
return -EINVAL;
}

/* VID was specified, so use it. */
err = __br_fdb_add(ndm, p, addr, nlh_flags, vid);
} else {
spin_lock_bh(&p->br->hash_lock);
err = fdb_add_entry(p, addr, ndm->ndm_state, nlh_flags,
0);
spin_unlock_bh(&p->br->hash_lock);
if (!pv || bitmap_empty(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN)) {
err = __br_fdb_add(ndm, p, addr, nlh_flags, 0);
goto out;
}

/* We have vlans configured on this port and user didn't
* specify a VLAN. To be nice, add/update entry for every
* vlan on this port.
*/
vid = find_first_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN);
while (vid < BR_VLAN_BITMAP_LEN) {
err = __br_fdb_add(ndm, p, addr, nlh_flags, vid);
if (err)
goto out;
vid = find_next_bit(pv->vlan_bitmap,
BR_VLAN_BITMAP_LEN, vid+1);
}
}

out:
return err;
}

static int fdb_delete_by_addr(struct net_bridge_port *p, const u8 *addr)
static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr,
u16 vlan)
{
struct net_bridge *br = p->br;
struct hlist_head *head = &br->hash[br_mac_hash(addr, 0)];
struct hlist_head *head = &br->hash[br_mac_hash(addr, vlan)];
struct net_bridge_fdb_entry *fdb;

fdb = fdb_find(head, addr, 0);
fdb = fdb_find(head, addr, vlan);
if (!fdb)
return -ENOENT;

fdb_delete(p->br, fdb);
fdb_delete(br, fdb);
return 0;
}

static int __br_fdb_delete(struct net_bridge_port *p,
const unsigned char *addr, u16 vid)
{
int err;

spin_lock_bh(&p->br->hash_lock);
err = fdb_delete_by_addr(p->br, addr, vid);
spin_unlock_bh(&p->br->hash_lock);

return err;
}

/* Remove neighbor entry with RTM_DELNEIGH */
int br_fdb_delete(struct ndmsg *ndm, struct net_device *dev,
int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr)
{
struct net_bridge_port *p;
int err;
struct net_port_vlans *pv;
unsigned short vid = VLAN_N_VID;

if (tb[NDA_VLAN]) {
if (nla_len(tb[NDA_VLAN]) != sizeof(unsigned short)) {
pr_info("bridge: RTM_NEWNEIGH with invalid vlan\n");
return -EINVAL;
}

vid = nla_get_u16(tb[NDA_VLAN]);

if (vid >= VLAN_N_VID) {
pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n",
vid);
return -EINVAL;
}
}
p = br_port_get_rtnl(dev);
if (p == NULL) {
pr_info("bridge: RTM_DELNEIGH %s not a bridge port\n",
dev->name);
return -EINVAL;
}

spin_lock_bh(&p->br->hash_lock);
err = fdb_delete_by_addr(p, addr);
spin_unlock_bh(&p->br->hash_lock);
pv = nbp_get_vlan_info(p);
if (vid != VLAN_N_VID) {
if (!pv || !test_bit(vid, pv->vlan_bitmap)) {
pr_info("bridge: RTM_DELNEIGH with unconfigured "
"vlan %d on port %s\n", vid, dev->name);
return -EINVAL;
}

err = __br_fdb_delete(p, addr, vid);
} else {
if (!pv || bitmap_empty(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN)) {
err = __br_fdb_delete(p, addr, 0);
goto out;
}

/* We have vlans configured on this port and user didn't
* specify a VLAN. To be nice, add/update entry for every
* vlan on this port.
*/
err = -ENOENT;
vid = find_first_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN);
while (vid < BR_VLAN_BITMAP_LEN) {
err &= __br_fdb_delete(p, addr, vid);
vid = find_next_bit(pv->vlan_bitmap,
BR_VLAN_BITMAP_LEN, vid+1);
}
}
out:
return err;
}
6 changes: 3 additions & 3 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ extern void br_fdb_update(struct net_bridge *br,
const unsigned char *addr,
u16 vid);

extern int br_fdb_delete(struct ndmsg *ndm,
extern int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr);
extern int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[],
Expand Down Expand Up @@ -577,13 +577,13 @@ extern void nbp_vlan_flush(struct net_bridge_port *port);
static inline struct net_port_vlans *br_get_vlan_info(
const struct net_bridge *br)
{
return rcu_dereference(br->vlan_info);
return rcu_dereference_rtnl(br->vlan_info);
}

static inline struct net_port_vlans *nbp_get_vlan_info(
const struct net_bridge_port *p)
{
return rcu_dereference(p->vlan_info);
return rcu_dereference_rtnl(p->vlan_info);
}

/* Since bridge now depends on 8021Q module, but the time bridge sees the
Expand Down
26 changes: 17 additions & 9 deletions net/core/rtnetlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -2119,13 +2119,17 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct net *net = sock_net(skb->sk);
struct ndmsg *ndm;
struct nlattr *llattr;
struct nlattr *tb[NDA_MAX+1];
struct net_device *dev;
int err = -EINVAL;
__u8 *addr;

if (nlmsg_len(nlh) < sizeof(*ndm))
return -EINVAL;
if (!capable(CAP_NET_ADMIN))
return -EPERM;

err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL);
if (err < 0)
return err;

ndm = nlmsg_data(nlh);
if (ndm->ndm_ifindex == 0) {
Expand All @@ -2139,13 +2143,17 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
return -ENODEV;
}

llattr = nlmsg_find_attr(nlh, sizeof(*ndm), NDA_LLADDR);
if (llattr == NULL || nla_len(llattr) != ETH_ALEN) {
pr_info("PF_BRIGDE: RTM_DELNEIGH with invalid address\n");
if (!tb[NDA_LLADDR] || nla_len(tb[NDA_LLADDR]) != ETH_ALEN) {
pr_info("PF_BRIDGE: RTM_DELNEIGH with invalid address\n");
return -EINVAL;
}

addr = nla_data(tb[NDA_LLADDR]);
if (!is_valid_ether_addr(addr)) {
pr_info("PF_BRIDGE: RTM_DELNEIGH with invalid ether address\n");
return -EINVAL;
}

addr = nla_data(llattr);
err = -EOPNOTSUPP;

/* Support fdb on master device the net/bridge default case */
Expand All @@ -2155,7 +2163,7 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
const struct net_device_ops *ops = br_dev->netdev_ops;

if (ops->ndo_fdb_del)
err = ops->ndo_fdb_del(ndm, dev, addr);
err = ops->ndo_fdb_del(ndm, tb, dev, addr);

if (err)
goto out;
Expand All @@ -2165,7 +2173,7 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)

/* Embedded bridge, macvlan, and any other device support */
if ((ndm->ndm_flags & NTF_SELF) && dev->netdev_ops->ndo_fdb_del) {
err = dev->netdev_ops->ndo_fdb_del(ndm, dev, addr);
err = dev->netdev_ops->ndo_fdb_del(ndm, tb, dev, addr);

if (!err) {
rtnl_fdb_notify(dev, addr, RTM_DELNEIGH);
Expand Down

0 comments on commit 1690be6

Please sign in to comment.