bridge: allow creating/deleting fdb entries via netlink
authorstephen hemminger <shemminger@vyatta.com>
Mon, 4 Apr 2011 14:03:31 +0000 (14:03 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 5 Apr 2011 00:22:28 +0000 (17:22 -0700)
Use RTM_NEWNEIGH and RTM_DELNEIGH to allow updating of entries
in bridge forwarding table. This allows manipulating static entries
which is not possible with existing tools.

Example (using bridge extensions to iproute2)
   # br fdb add 00:02:03:04:05:06 dev eth0

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/bridge/br_fdb.c
net/bridge/br_netlink.c
net/bridge/br_private.h

index 5e19e61396c39beecd5d9fe1ba64e043e4ba3e04..498f47c1c4535f2373012efbd003eb095c77294c 100644 (file)
@@ -555,3 +555,142 @@ skip:
 
        return skb->len;
 }
+
+/* Create new static fdb entry */
+static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
+                        __u16 state)
+{
+       struct net_bridge *br = source->br;
+       struct hlist_head *head = &br->hash[br_mac_hash(addr)];
+       struct net_bridge_fdb_entry *fdb;
+
+       fdb = fdb_find(head, addr);
+       if (fdb)
+               return -EEXIST;
+
+       fdb = fdb_create(head, source, addr);
+       if (!fdb)
+               return -ENOMEM;
+
+       if (state & NUD_PERMANENT)
+               fdb->is_local = fdb->is_static = 1;
+       else if (state & NUD_NOARP)
+               fdb->is_static = 1;
+       return 0;
+}
+
+/* Add new permanent fdb entry with RTM_NEWNEIGH */
+int br_fdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+       struct net *net = sock_net(skb->sk);
+       struct ndmsg *ndm;
+       struct nlattr *tb[NDA_MAX+1];
+       struct net_device *dev;
+       struct net_bridge_port *p;
+       const __u8 *addr;
+       int err;
+
+       ASSERT_RTNL();
+       err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL);
+       if (err < 0)
+               return err;
+
+       ndm = nlmsg_data(nlh);
+       if (ndm->ndm_ifindex == 0) {
+               pr_info("bridge: RTM_NEWNEIGH with invalid ifindex\n");
+               return -EINVAL;
+       }
+
+       dev = __dev_get_by_index(net, ndm->ndm_ifindex);
+       if (dev == NULL) {
+               pr_info("bridge: RTM_NEWNEIGH with unknown ifindex\n");
+               return -ENODEV;
+       }
+
+       if (!tb[NDA_LLADDR] || nla_len(tb[NDA_LLADDR]) != ETH_ALEN) {
+               pr_info("bridge: RTM_NEWNEIGH with invalid address\n");
+               return -EINVAL;
+       }
+
+       addr = nla_data(tb[NDA_LLADDR]);
+       if (!is_valid_ether_addr(addr)) {
+               pr_info("bridge: RTM_NEWNEIGH with invalid ether address\n");
+               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;
+       }
+
+       spin_lock_bh(&p->br->hash_lock);
+       err = fdb_add_entry(p, addr, ndm->ndm_state);
+       spin_unlock_bh(&p->br->hash_lock);
+
+       return err;
+}
+
+static int fdb_delete_by_addr(struct net_bridge_port *p, const u8 *addr)
+{
+       struct net_bridge *br = p->br;
+       struct hlist_head *head = &br->hash[br_mac_hash(addr)];
+       struct net_bridge_fdb_entry *fdb;
+
+       fdb = fdb_find(head, addr);
+       if (!fdb)
+               return -ENOENT;
+
+       fdb_delete(fdb);
+       return 0;
+}
+
+/* Remove neighbor entry with RTM_DELNEIGH */
+int br_fdb_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+       struct net *net = sock_net(skb->sk);
+       struct ndmsg *ndm;
+       struct net_bridge_port *p;
+       struct nlattr *llattr;
+       const __u8 *addr;
+       struct net_device *dev;
+       int err;
+
+       ASSERT_RTNL();
+       if (nlmsg_len(nlh) < sizeof(*ndm))
+               return -EINVAL;
+
+       ndm = nlmsg_data(nlh);
+       if (ndm->ndm_ifindex == 0) {
+               pr_info("bridge: RTM_DELNEIGH with invalid ifindex\n");
+               return -EINVAL;
+       }
+
+       dev = __dev_get_by_index(net, ndm->ndm_ifindex);
+       if (dev == NULL) {
+               pr_info("bridge: RTM_DELNEIGH with unknown ifindex\n");
+               return -ENODEV;
+       }
+
+       llattr = nlmsg_find_attr(nlh, sizeof(*ndm), NDA_LLADDR);
+       if (llattr == NULL || nla_len(llattr) != ETH_ALEN) {
+               pr_info("bridge: RTM_DELNEIGH with invalid address\n");
+               return -EINVAL;
+       }
+
+       addr = nla_data(llattr);
+
+       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);
+
+       return err;
+}
index cedcafd115f3c3c99b59d07a46902eee9ce29c45..fb7d5a7478fe1f9a0f3bc05d88dd6aa555922910 100644 (file)
@@ -196,6 +196,9 @@ int __init br_netlink_init(void)
 
        /* Only the first call to __rtnl_register can fail */
        __rtnl_register(PF_BRIDGE, RTM_SETLINK, br_rtm_setlink, NULL);
+
+       __rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, br_fdb_add, NULL);
+       __rtnl_register(PF_BRIDGE, RTM_DELNEIGH, br_fdb_delete, NULL);
        __rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, br_fdb_dump);
 
        return 0;
index 884d245a205ac8a0a6dcf865b6aebdefc8648cf2..4bbe0d14c9a2bbd538bbccd02eba140a5aeea8a2 100644 (file)
@@ -355,6 +355,8 @@ extern void br_fdb_update(struct net_bridge *br,
                          struct net_bridge_port *source,
                          const unsigned char *addr);
 extern int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb);
+extern int br_fdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg);
+extern int br_fdb_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg);
 
 /* br_forward.c */
 extern void br_deliver(const struct net_bridge_port *to,