Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[deliverable/linux.git] / net / bridge / br_fdb.c
index cc4d3c5ab1c62dcb9a3cfe66343e6adbdfe6f463..e0dfbc151dd76cb4d9680d8bfd38e2cf73314d9c 100644 (file)
@@ -28,6 +28,7 @@
 static struct kmem_cache *br_fdb_cache __read_mostly;
 static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                      const unsigned char *addr);
+static void fdb_notify(const struct net_bridge_fdb_entry *, int);
 
 static u32 fdb_salt __read_mostly;
 
@@ -62,7 +63,7 @@ static inline int has_expired(const struct net_bridge *br,
                                  const struct net_bridge_fdb_entry *fdb)
 {
        return !fdb->is_static &&
-               time_before_eq(fdb->ageing_timer + hold_time(br), jiffies);
+               time_before_eq(fdb->updated + hold_time(br), jiffies);
 }
 
 static inline int br_mac_hash(const unsigned char *mac)
@@ -81,6 +82,7 @@ static void fdb_rcu_free(struct rcu_head *head)
 
 static inline void fdb_delete(struct net_bridge_fdb_entry *f)
 {
+       fdb_notify(f, RTM_DELNEIGH);
        hlist_del_rcu(&f->hlist);
        call_rcu(&f->rcu, fdb_rcu_free);
 }
@@ -140,7 +142,7 @@ void br_fdb_cleanup(unsigned long _data)
                        unsigned long this_timer;
                        if (f->is_static)
                                continue;
-                       this_timer = f->ageing_timer + delay;
+                       this_timer = f->updated + delay;
                        if (time_before_eq(this_timer, jiffies))
                                fdb_delete(f);
                        else if (time_before(this_timer, next_timer))
@@ -293,7 +295,7 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
 
                        fe->is_local = f->is_local;
                        if (!f->is_static)
-                               fe->ageing_timer_value = jiffies_to_clock_t(jiffies - f->ageing_timer);
+                               fe->ageing_timer_value = jiffies_to_clock_t(jiffies - f->updated);
                        ++fe;
                        ++num;
                }
@@ -305,8 +307,21 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
        return num;
 }
 
-static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
-                                                   const unsigned char *addr)
+static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
+                                            const unsigned char *addr)
+{
+       struct hlist_node *h;
+       struct net_bridge_fdb_entry *fdb;
+
+       hlist_for_each_entry(fdb, h, head, hlist) {
+               if (!compare_ether_addr(fdb->addr.addr, addr))
+                       return fdb;
+       }
+       return NULL;
+}
+
+static struct net_bridge_fdb_entry *fdb_find_rcu(struct hlist_head *head,
+                                                const unsigned char *addr)
 {
        struct hlist_node *h;
        struct net_bridge_fdb_entry *fdb;
@@ -320,8 +335,7 @@ static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
 
 static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
                                               struct net_bridge_port *source,
-                                              const unsigned char *addr,
-                                              int is_local)
+                                              const unsigned char *addr)
 {
        struct net_bridge_fdb_entry *fdb;
 
@@ -329,11 +343,11 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
        if (fdb) {
                memcpy(fdb->addr.addr, addr, ETH_ALEN);
                fdb->dst = source;
-               fdb->is_local = is_local;
-               fdb->is_static = is_local;
-               fdb->ageing_timer = jiffies;
-
+               fdb->is_local = 0;
+               fdb->is_static = 0;
+               fdb->updated = fdb->used = jiffies;
                hlist_add_head_rcu(&fdb->hlist, head);
+               fdb_notify(fdb, RTM_NEWNEIGH);
        }
        return fdb;
 }
@@ -360,12 +374,15 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                fdb_delete(fdb);
        }
 
-       if (!fdb_create(head, source, addr, 1))
+       fdb = fdb_create(head, source, addr);
+       if (!fdb)
                return -ENOMEM;
 
+       fdb->is_local = fdb->is_static = 1;
        return 0;
 }
 
+/* Add entry for local address of interface */
 int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                  const unsigned char *addr)
 {
@@ -392,7 +409,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
              source->state == BR_STATE_FORWARDING))
                return;
 
-       fdb = fdb_find(head, addr);
+       fdb = fdb_find_rcu(head, addr);
        if (likely(fdb)) {
                /* attempt to update an entry for a local interface */
                if (unlikely(fdb->is_local)) {
@@ -403,15 +420,277 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
                } else {
                        /* fastpath: update of existing entry */
                        fdb->dst = source;
-                       fdb->ageing_timer = jiffies;
+                       fdb->updated = jiffies;
                }
        } else {
                spin_lock(&br->hash_lock);
-               if (!fdb_find(head, addr))
-                       fdb_create(head, source, addr, 0);
+               if (likely(!fdb_find(head, addr)))
+                       fdb_create(head, source, addr);
+
                /* else  we lose race and someone else inserts
                 * it first, don't bother updating
                 */
                spin_unlock(&br->hash_lock);
        }
 }
+
+static int fdb_to_nud(const struct net_bridge_fdb_entry *fdb)
+{
+       if (fdb->is_local)
+               return NUD_PERMANENT;
+       else if (fdb->is_static)
+               return NUD_NOARP;
+       else if (has_expired(fdb->dst->br, fdb))
+               return NUD_STALE;
+       else
+               return NUD_REACHABLE;
+}
+
+static int fdb_fill_info(struct sk_buff *skb,
+                        const struct net_bridge_fdb_entry *fdb,
+                        u32 pid, u32 seq, int type, unsigned int flags)
+{
+       unsigned long now = jiffies;
+       struct nda_cacheinfo ci;
+       struct nlmsghdr *nlh;
+       struct ndmsg *ndm;
+
+       nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ndm), flags);
+       if (nlh == NULL)
+               return -EMSGSIZE;
+
+
+       ndm = nlmsg_data(nlh);
+       ndm->ndm_family  = AF_BRIDGE;
+       ndm->ndm_pad1    = 0;
+       ndm->ndm_pad2    = 0;
+       ndm->ndm_flags   = 0;
+       ndm->ndm_type    = 0;
+       ndm->ndm_ifindex = fdb->dst->dev->ifindex;
+       ndm->ndm_state   = fdb_to_nud(fdb);
+
+       NLA_PUT(skb, NDA_LLADDR, ETH_ALEN, &fdb->addr);
+
+       ci.ndm_used      = jiffies_to_clock_t(now - fdb->used);
+       ci.ndm_confirmed = 0;
+       ci.ndm_updated   = jiffies_to_clock_t(now - fdb->updated);
+       ci.ndm_refcnt    = 0;
+       NLA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci);
+
+       return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+       nlmsg_cancel(skb, nlh);
+       return -EMSGSIZE;
+}
+
+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(struct nda_cacheinfo));
+}
+
+static void fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
+{
+       struct net *net = dev_net(fdb->dst->dev);
+       struct sk_buff *skb;
+       int err = -ENOBUFS;
+
+       skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC);
+       if (skb == NULL)
+               goto errout;
+
+       err = fdb_fill_info(skb, fdb, 0, 0, type, 0);
+       if (err < 0) {
+               /* -EMSGSIZE implies BUG in fdb_nlmsg_size() */
+               WARN_ON(err == -EMSGSIZE);
+               kfree_skb(skb);
+               goto errout;
+       }
+       rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC);
+       return;
+errout:
+       if (err < 0)
+               rtnl_set_sk_err(net, RTNLGRP_NEIGH, err);
+}
+
+/* Dump information about entries, in response to GETNEIGH */
+int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct net *net = sock_net(skb->sk);
+       struct net_device *dev;
+       int idx = 0;
+
+       rcu_read_lock();
+       for_each_netdev_rcu(net, dev) {
+               struct net_bridge *br = netdev_priv(dev);
+               int i;
+
+               if (!(dev->priv_flags & IFF_EBRIDGE))
+                       continue;
+
+               for (i = 0; i < BR_HASH_SIZE; i++) {
+                       struct hlist_node *h;
+                       struct net_bridge_fdb_entry *f;
+
+                       hlist_for_each_entry_rcu(f, h, &br->hash[i], hlist) {
+                               if (idx < cb->args[0])
+                                       goto skip;
+
+                               if (fdb_fill_info(skb, f,
+                                                 NETLINK_CB(cb->skb).pid,
+                                                 cb->nlh->nlmsg_seq,
+                                                 RTM_NEWNEIGH,
+                                                 NLM_F_MULTI) < 0)
+                                       break;
+skip:
+                               ++idx;
+                       }
+               }
+       }
+       rcu_read_unlock();
+
+       cb->args[0] = idx;
+
+       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;
+}
This page took 0.028458 seconds and 5 git commands to generate.