Commit c1d2ecdf authored by Julian Anastasov's avatar Julian Anastasov Committed by David S. Miller

neigh: make sure used and confirmed times are valid

Entries can linger in cache without timer for days, thanks to
the gc_thresh1 limit. As result, without traffic, the confirmed
time can be outdated and to appear to be in the future. Later,
on traffic, NUD_STALE entries can switch to NUD_DELAY and start
the timer which can see the invalid confirmed time and wrongly
switch to NUD_REACHABLE state instead of NUD_PROBE. As result,
timer is set many days in the future. This is more visible on
32-bit platforms, with higher HZ value.

Why this is a problem? While we expect unused entries to expire,
such entries stay in REACHABLE state for too long, locked in
cache. They are not expired normally, only when cache is full.

Problem and the wrong state change reported by Zhang Changzhong:

172.16.1.18 dev bond0 lladdr 0a:0e:0f:01:12:01 ref 1 used 350521/15994171/350520 probes 4 REACHABLE

350520 seconds have elapsed since this entry was last updated, but it is
still in the REACHABLE state (base_reachable_time_ms is 30000),
preventing lladdr from being updated through probe.

Fix it by ensuring timer is started with valid used/confirmed
times. Considering the valid time range is LONG_MAX jiffies,
we try not to go too much in the past while we are in
DELAY/PROBE state. There are also places that need
used/updated times to be validated while timer is not running.
Reported-by: default avatarZhang Changzhong <zhangchangzhong@huawei.com>
Signed-off-by: default avatarJulian Anastasov <ja@ssi.bg>
Tested-by: default avatarZhang Changzhong <zhangchangzhong@huawei.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent a05e7a67
...@@ -269,7 +269,7 @@ static int neigh_forced_gc(struct neigh_table *tbl) ...@@ -269,7 +269,7 @@ static int neigh_forced_gc(struct neigh_table *tbl)
(n->nud_state == NUD_NOARP) || (n->nud_state == NUD_NOARP) ||
(tbl->is_multicast && (tbl->is_multicast &&
tbl->is_multicast(n->primary_key)) || tbl->is_multicast(n->primary_key)) ||
time_after(tref, n->updated)) !time_in_range(n->updated, tref, jiffies))
remove = true; remove = true;
write_unlock(&n->lock); write_unlock(&n->lock);
...@@ -289,7 +289,17 @@ static int neigh_forced_gc(struct neigh_table *tbl) ...@@ -289,7 +289,17 @@ static int neigh_forced_gc(struct neigh_table *tbl)
static void neigh_add_timer(struct neighbour *n, unsigned long when) static void neigh_add_timer(struct neighbour *n, unsigned long when)
{ {
/* Use safe distance from the jiffies - LONG_MAX point while timer
* is running in DELAY/PROBE state but still show to user space
* large times in the past.
*/
unsigned long mint = jiffies - (LONG_MAX - 86400 * HZ);
neigh_hold(n); neigh_hold(n);
if (!time_in_range(n->confirmed, mint, jiffies))
n->confirmed = mint;
if (time_before(n->used, n->confirmed))
n->used = n->confirmed;
if (unlikely(mod_timer(&n->timer, when))) { if (unlikely(mod_timer(&n->timer, when))) {
printk("NEIGH: BUG, double timer add, state is %x\n", printk("NEIGH: BUG, double timer add, state is %x\n",
n->nud_state); n->nud_state);
...@@ -1001,12 +1011,14 @@ static void neigh_periodic_work(struct work_struct *work) ...@@ -1001,12 +1011,14 @@ static void neigh_periodic_work(struct work_struct *work)
goto next_elt; goto next_elt;
} }
if (time_before(n->used, n->confirmed)) if (time_before(n->used, n->confirmed) &&
time_is_before_eq_jiffies(n->confirmed))
n->used = n->confirmed; n->used = n->confirmed;
if (refcount_read(&n->refcnt) == 1 && if (refcount_read(&n->refcnt) == 1 &&
(state == NUD_FAILED || (state == NUD_FAILED ||
time_after(jiffies, n->used + NEIGH_VAR(n->parms, GC_STALETIME)))) { !time_in_range_open(jiffies, n->used,
n->used + NEIGH_VAR(n->parms, GC_STALETIME)))) {
*np = n->next; *np = n->next;
neigh_mark_dead(n); neigh_mark_dead(n);
write_unlock(&n->lock); write_unlock(&n->lock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment