From: Leonard Zgrablic <lzgrab...@arista.com>

Currently there is only a dump version of RTM_GETNEIGH for PF_UNSPEC in
RTNETLINK that dumps neighbor entries, no non-dump version that can be used to
retrieve a single neighbor entry.

Add support for the non-dump (doit) version of RTM_GETNEIGH for PF_UNSPEC so
that a single neighbor entry can be retrieved.

Signed-off-by: Leonard Zgrablic <lzgrab...@arista.com>
Signed-off-by: Ben McMahon <mcma...@arista.com>
---
 net/core/neighbour.c | 160 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 147 insertions(+), 13 deletions(-)

diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 30f6fd8f68e0..981f1568710b 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -2733,6 +2733,149 @@ static int neigh_dump_info(struct sk_buff *skb, struct 
netlink_callback *cb)
        return skb->len;
 }
 
+static inline size_t neigh_nlmsg_size(void)
+{
+               return NLMSG_ALIGN(sizeof(struct ndmsg))
+                       + nla_total_size(MAX_ADDR_LEN) /* NDA_DST */
+                       + nla_total_size(MAX_ADDR_LEN) /* NDA_LLADDR */
+                       + nla_total_size(sizeof(struct nda_cacheinfo))
+                       + nla_total_size(4); /* NDA_PROBES */
+}
+
+static int neigh_find_fill(struct neigh_table *tbl, const void *pkey,
+                           struct net_device *dev, struct sk_buff *skb, u32 
pid,
+                           u32 seq)
+{
+       struct neighbour *neigh;
+       int key_len = tbl->key_len;
+       u32 hash_val;
+       struct neigh_hash_table *nht;
+       int err;
+       
+       if (dev == NULL)
+               return -EINVAL;
+       
+       NEIGH_CACHE_STAT_INC(tbl, lookups);
+
+       rcu_read_lock_bh();
+   nht = rcu_dereference_bh(tbl->nht);
+       hash_val = tbl->hash(pkey, dev, nht->hash_rnd) >>
+               (32 - nht->hash_shift);
+
+       for (neigh = rcu_dereference_bh(nht->hash_buckets[hash_val]);
+               neigh != NULL;
+               neigh = rcu_dereference_bh(neigh->next)) {
+               if (dev == neigh->dev &&
+                       !memcmp(neigh->primary_key, pkey, key_len)) {
+                               if (!atomic_read(&neigh->refcnt))
+                                       neigh = NULL;
+                               NEIGH_CACHE_STAT_INC(tbl, hits);
+                               break;
+               }
+       }
+       if (neigh == NULL) {
+               err = -ENOENT;
+               goto out_rcu_read_unlock;
+       }
+
+       err = neigh_fill_info(skb, neigh, pid, seq, RTM_NEWNEIGH, 0);
+
+out_rcu_read_unlock:
+       rcu_read_unlock_bh();
+       return err;
+}
+
+static int pneigh_find_fill(struct neigh_table *tbl, const void *pkey,
+                       struct net_device *dev, struct net *net,
+                       struct sk_buff *skb, u32 pid, u32 seq)
+{
+       struct pneigh_entry *pneigh;
+       int key_len = tbl->key_len;
+       u32 hash_val = pneigh_hash(pkey, key_len);
+       int err;
+
+       read_lock_bh(&tbl->lock);
+
+       pneigh = __pneigh_lookup_1(tbl->phash_buckets[hash_val], net, pkey,
+                                   key_len, dev);
+       if (pneigh == NULL) {
+               err = -ENOENT;
+               goto out_read_unlock;
+       }
+
+       err = pneigh_fill_info(skb, pneigh, pid, seq, RTM_NEWNEIGH, 0, tbl);
+
+out_read_unlock:
+       read_unlock_bh(&tbl->lock);
+       return err;
+}
+
+static int neigh_get(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+       struct net *net = sock_net(skb->sk);
+       struct ndmsg *ndm;
+       struct nlattr *dst_attr;
+       struct neigh_table *tbl;
+       struct net_device *dev = NULL;
+
+       ASSERT_RTNL();
+       if (nlmsg_len(nlh) < sizeof(*ndm))
+               return -EINVAL;
+
+       dst_attr = nlmsg_find_attr(nlh, sizeof(*ndm), NDA_DST);
+       if (dst_attr == NULL)
+               return -EINVAL;
+
+       ndm = nlmsg_data(nlh);
+       if (ndm->ndm_ifindex) {
+               dev = __dev_get_by_index(net, ndm->ndm_ifindex);
+               if (dev == NULL)
+                       return -ENODEV;
+       }
+
+       read_lock(&neigh_tbl_lock);
+       for (tbl = neigh_tables; tbl; tbl = tbl->next) {
+               struct sk_buff *nskb;
+               int err;
+
+               if (tbl->family != ndm->ndm_family)
+                       continue;
+
+               read_unlock(&neigh_tbl_lock);
+
+               if (nla_len(dst_attr) < tbl->key_len)
+                       return -EINVAL;
+
+               nskb = nlmsg_new(neigh_nlmsg_size(), GFP_KERNEL);
+               if (nskb == NULL)
+                       return -ENOBUFS;
+
+               if (ndm->ndm_flags & NTF_PROXY)
+                       err = pneigh_find_fill(tbl, nla_data(dst_attr), dev,
+                               net, nskb,
+                               NETLINK_CB(skb).portid,
+                               nlh->nlmsg_seq);
+               else
+                       err = neigh_find_fill(tbl, nla_data(dst_attr), dev,
+                               nskb, NETLINK_CB(skb).portid,
+                               nlh->nlmsg_seq);
+
+               if (err < 0) {
+                       /* -EMSGSIZE implies BUG in neigh_nlmsg_size */
+                       WARN_ON(err == -EMSGSIZE);
+                       kfree_skb(nskb);
+               } else {
+                       err = rtnl_unicast(nskb, net, NETLINK_CB(skb).portid);
+               }
+
+               return err;
+       }
+       read_unlock(&neigh_tbl_lock);
+       return -EAFNOSUPPORT;
+}
+
+
+
 static int neigh_valid_get_req(const struct nlmsghdr *nlh,
                               struct neigh_table **tbl,
                               void **dst, int *dev_idx, u8 *ndm_flags,
@@ -2793,16 +2936,6 @@ static int neigh_valid_get_req(const struct nlmsghdr 
*nlh,
        return 0;
 }
 
-static inline size_t neigh_nlmsg_size(void)
-{
-       return NLMSG_ALIGN(sizeof(struct ndmsg))
-              + nla_total_size(MAX_ADDR_LEN) /* NDA_DST */
-              + nla_total_size(MAX_ADDR_LEN) /* NDA_LLADDR */
-              + nla_total_size(sizeof(struct nda_cacheinfo))
-              + nla_total_size(4)  /* NDA_PROBES */
-              + nla_total_size(1); /* NDA_PROTOCOL */
-}
-
 static int neigh_get_reply(struct net *net, struct neighbour *neigh,
                           u32 pid, u32 seq)
 {
@@ -2827,8 +2960,8 @@ static int neigh_get_reply(struct net *net, struct 
neighbour *neigh,
 static inline size_t pneigh_nlmsg_size(void)
 {
        return NLMSG_ALIGN(sizeof(struct ndmsg))
-              + nla_total_size(MAX_ADDR_LEN) /* NDA_DST */
-              + nla_total_size(1); /* NDA_PROTOCOL */
+               + nla_total_size(MAX_ADDR_LEN) /* NDA_DST */
+               + nla_total_size(1); /* NDA_PROTOCOL */
 }
 
 static int pneigh_get_reply(struct net *net, struct pneigh_entry *neigh,
@@ -3703,7 +3836,8 @@ static int __init neigh_init(void)
 {
        rtnl_register(PF_UNSPEC, RTM_NEWNEIGH, neigh_add, NULL, 0);
        rtnl_register(PF_UNSPEC, RTM_DELNEIGH, neigh_delete, NULL, 0);
-       rtnl_register(PF_UNSPEC, RTM_GETNEIGH, neigh_get, neigh_dump_info, 0);
+       rtnl_register(PF_UNSPEC, RTM_GETNEIGH, neigh_get, neigh_dump_info, 
+               NULL);
 
        rtnl_register(PF_UNSPEC, RTM_GETNEIGHTBL, NULL, neightbl_dump_info,
                      0);
-- 
2.21.0

Reply via email to