Commit b334b924 authored by Valentin Schneider's avatar Valentin Schneider Committed by David S. Miller

net: tcp/dccp: prepare for tw_timer un-pinning

The TCP timewait timer is proving to be problematic for setups where
scheduler CPU isolation is achieved at runtime via cpusets (as opposed to
statically via isolcpus=domains).

What happens there is a CPU goes through tcp_time_wait(), arming the
time_wait timer, then gets isolated. TCP_TIMEWAIT_LEN later, the timer
fires, causing interference for the now-isolated CPU. This is conceptually
similar to the issue described in commit e02b9312 ("workqueue: Unbind
kworkers before sending them to exit()")

Move inet_twsk_schedule() to within inet_twsk_hashdance(), with the ehash
lock held. Expand the lock's critical section from inet_twsk_kill() to
inet_twsk_deschedule_put(), serializing the scheduling vs descheduling of
the timer. IOW, this prevents the following race:

			     tcp_time_wait()
			       inet_twsk_hashdance()
  inet_twsk_deschedule_put()
    del_timer_sync()
			       inet_twsk_schedule()

Thanks to Paolo Abeni for suggesting to leverage the ehash lock.

This also restores a comment from commit ec94c269 ("tcp/dccp: avoid
one atomic operation for timewait hashdance") as inet_twsk_hashdance() had
a "Step 1" and "Step 3" comment, but the "Step 2" had gone missing.

inet_twsk_deschedule_put() now acquires the ehash spinlock to synchronize
with inet_twsk_hashdance_schedule().

To ease possible regression search, actual un-pin is done in next patch.

Link: https://lore.kernel.org/all/ZPhpfMjSiHVjQkTk@localhost.localdomain/Reviewed-by: default avatarEric Dumazet <edumazet@google.com>
Signed-off-by: default avatarValentin Schneider <vschneid@redhat.com>
Co-developed-by: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 8d466c8f
...@@ -93,8 +93,10 @@ struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, ...@@ -93,8 +93,10 @@ struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,
struct inet_timewait_death_row *dr, struct inet_timewait_death_row *dr,
const int state); const int state);
void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk, void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
struct inet_hashinfo *hashinfo); struct sock *sk,
struct inet_hashinfo *hashinfo,
int timeo);
void __inet_twsk_schedule(struct inet_timewait_sock *tw, int timeo, void __inet_twsk_schedule(struct inet_timewait_sock *tw, int timeo,
bool rearm); bool rearm);
......
...@@ -59,11 +59,10 @@ void dccp_time_wait(struct sock *sk, int state, int timeo) ...@@ -59,11 +59,10 @@ void dccp_time_wait(struct sock *sk, int state, int timeo)
* we complete the initialization. * we complete the initialization.
*/ */
local_bh_disable(); local_bh_disable();
inet_twsk_schedule(tw, timeo);
/* Linkage updates. /* Linkage updates.
* Note that access to tw after this point is illegal. * Note that access to tw after this point is illegal.
*/ */
inet_twsk_hashdance(tw, sk, &dccp_hashinfo); inet_twsk_hashdance_schedule(tw, sk, &dccp_hashinfo, timeo);
local_bh_enable(); local_bh_enable();
} else { } else {
/* Sorry, if we're out of memory, just CLOSE this /* Sorry, if we're out of memory, just CLOSE this
......
...@@ -96,9 +96,13 @@ static void inet_twsk_add_node_rcu(struct inet_timewait_sock *tw, ...@@ -96,9 +96,13 @@ static void inet_twsk_add_node_rcu(struct inet_timewait_sock *tw,
* Enter the time wait state. This is called with locally disabled BH. * Enter the time wait state. This is called with locally disabled BH.
* Essentially we whip up a timewait bucket, copy the relevant info into it * Essentially we whip up a timewait bucket, copy the relevant info into it
* from the SK, and mess with hash chains and list linkage. * from the SK, and mess with hash chains and list linkage.
*
* The caller must not access @tw anymore after this function returns.
*/ */
void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk, void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
struct inet_hashinfo *hashinfo) struct sock *sk,
struct inet_hashinfo *hashinfo,
int timeo)
{ {
const struct inet_sock *inet = inet_sk(sk); const struct inet_sock *inet = inet_sk(sk);
const struct inet_connection_sock *icsk = inet_csk(sk); const struct inet_connection_sock *icsk = inet_csk(sk);
...@@ -129,26 +133,33 @@ void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk, ...@@ -129,26 +133,33 @@ void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
spin_lock(lock); spin_lock(lock);
/* Step 2: Hash TW into tcp ehash chain */
inet_twsk_add_node_rcu(tw, &ehead->chain); inet_twsk_add_node_rcu(tw, &ehead->chain);
/* Step 3: Remove SK from hash chain */ /* Step 3: Remove SK from hash chain */
if (__sk_nulls_del_node_init_rcu(sk)) if (__sk_nulls_del_node_init_rcu(sk))
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1); sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
spin_unlock(lock);
/* Ensure above writes are committed into memory before updating the
* refcount.
* Provides ordering vs later refcount_inc().
*/
smp_wmb();
/* tw_refcnt is set to 3 because we have : /* tw_refcnt is set to 3 because we have :
* - one reference for bhash chain. * - one reference for bhash chain.
* - one reference for ehash chain. * - one reference for ehash chain.
* - one reference for timer. * - one reference for timer.
* We can use atomic_set() because prior spin_lock()/spin_unlock()
* committed into memory all tw fields.
* Also note that after this point, we lost our implicit reference * Also note that after this point, we lost our implicit reference
* so we are not allowed to use tw anymore. * so we are not allowed to use tw anymore.
*/ */
refcount_set(&tw->tw_refcnt, 3); refcount_set(&tw->tw_refcnt, 3);
inet_twsk_schedule(tw, timeo);
spin_unlock(lock);
} }
EXPORT_SYMBOL_GPL(inet_twsk_hashdance); EXPORT_SYMBOL_GPL(inet_twsk_hashdance_schedule);
static void tw_timer_handler(struct timer_list *t) static void tw_timer_handler(struct timer_list *t)
{ {
...@@ -217,7 +228,34 @@ EXPORT_SYMBOL_GPL(inet_twsk_alloc); ...@@ -217,7 +228,34 @@ EXPORT_SYMBOL_GPL(inet_twsk_alloc);
*/ */
void inet_twsk_deschedule_put(struct inet_timewait_sock *tw) void inet_twsk_deschedule_put(struct inet_timewait_sock *tw)
{ {
if (del_timer_sync(&tw->tw_timer)) struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
/* inet_twsk_purge() walks over all sockets, including tw ones,
* and removes them via inet_twsk_deschedule_put() after a
* refcount_inc_not_zero().
*
* inet_twsk_hashdance_schedule() must (re)init the refcount before
* arming the timer, i.e. inet_twsk_purge can obtain a reference to
* a twsk that did not yet schedule the timer.
*
* The ehash lock synchronizes these two:
* After acquiring the lock, the timer is always scheduled (else
* timer_shutdown returns false), because hashdance_schedule releases
* the ehash lock only after completing the timer initialization.
*
* Without grabbing the ehash lock, we get:
* 1) cpu x sets twsk refcount to 3
* 2) cpu y bumps refcount to 4
* 3) cpu y calls inet_twsk_deschedule_put() and shuts timer down
* 4) cpu x tries to start timer, but mod_timer is a noop post-shutdown
* -> timer refcount is never decremented.
*/
spin_lock(lock);
/* Makes sure hashdance_schedule() has completed */
spin_unlock(lock);
if (timer_shutdown_sync(&tw->tw_timer))
inet_twsk_kill(tw); inet_twsk_kill(tw);
inet_twsk_put(tw); inet_twsk_put(tw);
} }
......
...@@ -157,7 +157,7 @@ int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp) ...@@ -157,7 +157,7 @@ int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
if (ts_recent_stamp && if (ts_recent_stamp &&
(!twp || (reuse && time_after32(ktime_get_seconds(), (!twp || (reuse && time_after32(ktime_get_seconds(),
ts_recent_stamp)))) { ts_recent_stamp)))) {
/* inet_twsk_hashdance() sets sk_refcnt after putting twsk /* inet_twsk_hashdance_schedule() sets sk_refcnt after putting twsk
* and releasing the bucket lock. * and releasing the bucket lock.
*/ */
if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt))) if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt)))
......
...@@ -350,11 +350,10 @@ void tcp_time_wait(struct sock *sk, int state, int timeo) ...@@ -350,11 +350,10 @@ void tcp_time_wait(struct sock *sk, int state, int timeo)
* we complete the initialization. * we complete the initialization.
*/ */
local_bh_disable(); local_bh_disable();
inet_twsk_schedule(tw, timeo);
/* Linkage updates. /* Linkage updates.
* Note that access to tw after this point is illegal. * Note that access to tw after this point is illegal.
*/ */
inet_twsk_hashdance(tw, sk, net->ipv4.tcp_death_row.hashinfo); inet_twsk_hashdance_schedule(tw, sk, net->ipv4.tcp_death_row.hashinfo, timeo);
local_bh_enable(); local_bh_enable();
} else { } else {
/* Sorry, if we're out of memory, just CLOSE this /* Sorry, if we're out of memory, just CLOSE this
......
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