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