mirror of
https://github.com/torvalds/linux.git
synced 2024-09-24 08:53:09 +00:00
neigh: Really delete an arp/neigh entry on "ip neigh delete" or "arp -d"
The command
# arp -s 62.2.0.1 a🅱️c:d:e:f dev eth2
adds an entry like the following (listed by "arp -an")
? (62.2.0.1) at 0a:0b:0c:0d:0e:0f [ether] PERM on eth2
but the symmetric deletion command
# arp -i eth2 -d 62.2.0.1
does not remove the PERM entry from the table, and instead leaves behind
? (62.2.0.1) at <incomplete> on eth2
The reason is that there is a refcnt of 1 for the arp_tbl itself
(neigh_alloc starts off the entry with a refcnt of 1), thus
the neigh_release() call from arp_invalidate() will (at best) just
decrement the ref to 1, but will never actually free it from the
table.
To fix this, we need to do something like neigh_forced_gc: if
the refcnt is 1 (i.e., on the table's ref), remove the entry from
the table and free it. This patch refactors and shares common code
between neigh_forced_gc and the newly added neigh_remove_one.
A similar issue exists for IPv6 Neighbor Cache entries, and is fixed
in a similar manner by this patch.
Signed-off-by: Sowmini Varadhan <sowmini.varadhan@oracle.com>
Reviewed-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
030a89028d
commit
5071034e4a
|
@ -317,6 +317,7 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb);
|
||||||
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags,
|
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags,
|
||||||
u32 nlmsg_pid);
|
u32 nlmsg_pid);
|
||||||
void __neigh_set_probe_once(struct neighbour *neigh);
|
void __neigh_set_probe_once(struct neighbour *neigh);
|
||||||
|
bool neigh_remove_one(struct neighbour *ndel, struct neigh_table *tbl);
|
||||||
void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev);
|
void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev);
|
||||||
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
|
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
|
||||||
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb);
|
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb);
|
||||||
|
|
|
@ -118,6 +118,50 @@ unsigned long neigh_rand_reach_time(unsigned long base)
|
||||||
EXPORT_SYMBOL(neigh_rand_reach_time);
|
EXPORT_SYMBOL(neigh_rand_reach_time);
|
||||||
|
|
||||||
|
|
||||||
|
static bool neigh_del(struct neighbour *n, __u8 state,
|
||||||
|
struct neighbour __rcu **np, struct neigh_table *tbl)
|
||||||
|
{
|
||||||
|
bool retval = false;
|
||||||
|
|
||||||
|
write_lock(&n->lock);
|
||||||
|
if (atomic_read(&n->refcnt) == 1 && !(n->nud_state & state)) {
|
||||||
|
struct neighbour *neigh;
|
||||||
|
|
||||||
|
neigh = rcu_dereference_protected(n->next,
|
||||||
|
lockdep_is_held(&tbl->lock));
|
||||||
|
rcu_assign_pointer(*np, neigh);
|
||||||
|
n->dead = 1;
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
write_unlock(&n->lock);
|
||||||
|
if (retval)
|
||||||
|
neigh_cleanup_and_release(n);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool neigh_remove_one(struct neighbour *ndel, struct neigh_table *tbl)
|
||||||
|
{
|
||||||
|
struct neigh_hash_table *nht;
|
||||||
|
void *pkey = ndel->primary_key;
|
||||||
|
u32 hash_val;
|
||||||
|
struct neighbour *n;
|
||||||
|
struct neighbour __rcu **np;
|
||||||
|
|
||||||
|
nht = rcu_dereference_protected(tbl->nht,
|
||||||
|
lockdep_is_held(&tbl->lock));
|
||||||
|
hash_val = tbl->hash(pkey, ndel->dev, nht->hash_rnd);
|
||||||
|
hash_val = hash_val >> (32 - nht->hash_shift);
|
||||||
|
|
||||||
|
np = &nht->hash_buckets[hash_val];
|
||||||
|
while ((n = rcu_dereference_protected(*np,
|
||||||
|
lockdep_is_held(&tbl->lock)))) {
|
||||||
|
if (n == ndel)
|
||||||
|
return neigh_del(n, 0, np, tbl);
|
||||||
|
np = &n->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int neigh_forced_gc(struct neigh_table *tbl)
|
static int neigh_forced_gc(struct neigh_table *tbl)
|
||||||
{
|
{
|
||||||
int shrunk = 0;
|
int shrunk = 0;
|
||||||
|
@ -140,19 +184,10 @@ static int neigh_forced_gc(struct neigh_table *tbl)
|
||||||
* - nobody refers to it.
|
* - nobody refers to it.
|
||||||
* - it is not permanent
|
* - it is not permanent
|
||||||
*/
|
*/
|
||||||
write_lock(&n->lock);
|
if (neigh_del(n, NUD_PERMANENT, np, tbl)) {
|
||||||
if (atomic_read(&n->refcnt) == 1 &&
|
shrunk = 1;
|
||||||
!(n->nud_state & NUD_PERMANENT)) {
|
|
||||||
rcu_assign_pointer(*np,
|
|
||||||
rcu_dereference_protected(n->next,
|
|
||||||
lockdep_is_held(&tbl->lock)));
|
|
||||||
n->dead = 1;
|
|
||||||
shrunk = 1;
|
|
||||||
write_unlock(&n->lock);
|
|
||||||
neigh_cleanup_and_release(n);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
write_unlock(&n->lock);
|
|
||||||
np = &n->next;
|
np = &n->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1649,7 +1684,10 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
NEIGH_UPDATE_F_OVERRIDE |
|
NEIGH_UPDATE_F_OVERRIDE |
|
||||||
NEIGH_UPDATE_F_ADMIN,
|
NEIGH_UPDATE_F_ADMIN,
|
||||||
NETLINK_CB(skb).portid);
|
NETLINK_CB(skb).portid);
|
||||||
|
write_lock_bh(&tbl->lock);
|
||||||
neigh_release(neigh);
|
neigh_release(neigh);
|
||||||
|
neigh_remove_one(neigh, tbl);
|
||||||
|
write_unlock_bh(&tbl->lock);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
return err;
|
return err;
|
||||||
|
|
|
@ -1113,13 +1113,17 @@ static int arp_invalidate(struct net_device *dev, __be32 ip)
|
||||||
{
|
{
|
||||||
struct neighbour *neigh = neigh_lookup(&arp_tbl, &ip, dev);
|
struct neighbour *neigh = neigh_lookup(&arp_tbl, &ip, dev);
|
||||||
int err = -ENXIO;
|
int err = -ENXIO;
|
||||||
|
struct neigh_table *tbl = &arp_tbl;
|
||||||
|
|
||||||
if (neigh) {
|
if (neigh) {
|
||||||
if (neigh->nud_state & ~NUD_NOARP)
|
if (neigh->nud_state & ~NUD_NOARP)
|
||||||
err = neigh_update(neigh, NULL, NUD_FAILED,
|
err = neigh_update(neigh, NULL, NUD_FAILED,
|
||||||
NEIGH_UPDATE_F_OVERRIDE|
|
NEIGH_UPDATE_F_OVERRIDE|
|
||||||
NEIGH_UPDATE_F_ADMIN, 0);
|
NEIGH_UPDATE_F_ADMIN, 0);
|
||||||
|
write_lock_bh(&tbl->lock);
|
||||||
neigh_release(neigh);
|
neigh_release(neigh);
|
||||||
|
neigh_remove_one(neigh, tbl);
|
||||||
|
write_unlock_bh(&tbl->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user