Fix race condition in early drop
Subsystems (2)
Pablo Neira Ayuso
maintainerPATCH 3/3 Fix race condition in early drop
[CONNTRACK] Fix race condition in early drop
On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition.
CPU A CPU B
atomic_read() ...
early_drop() ...
... atomic_read()
allocate conntrack allocate conntrack
atomic_inc() atomic_inc()
This patch uses an optimistic approach to solve the concurrency problem.
Signature · 1 line
The dawn of the fourth age of Linux firewalling is coming; a time of great struggle and heroic deeds -- J.Kadlecsik got inspired by J.Morris
[CONNTRACK] Fix race condition in early drop
On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition.
CPU A CPU B
atomic_read() ...
early_drop() ...
... atomic_read()
allocate conntrack allocate conntrack
atomic_inc() atomic_inc()
This patch uses an optimistic approach to solve the concurrency problem.
net/ipv4/netfilter/ip_conntrack_core.c +19 -10
@@ -642,21 +642,32 @@ struct ip_conntrack *ip_conntrack_alloc( }if (ip_conntrack_max - &&atomic_read (&ip_conntrack_count) >= ip_conntrack_max) { + && !atomic_add_unless (&ip_conntrack_count,1 , ip_conntrack_max)) {unsigned int hash =hash_conntrack (orig);/* Try dropping from this hash chain. */ -if (!early_drop (&ip_conntrack_hash[hash])) { -if (net_ratelimit ()) -printk (KERN_WARNING -"ip_conntrack: table full, dropping" -" packet.\n" ); -return ERR_PTR (-ENOMEM ); - } +do { +if (!early_drop (&ip_conntrack_hash[hash])) { +if (net_ratelimit ()) +printk (KERN_WARNING +"ip_conntrack: table full, " +"dropping packet.\n" ); +return ERR_PTR (-ENOMEM ); + } +/* +* On SMP environments, if the table is full and we +* early drop a conntrack to make some place for this +* new one then we have to ensure that no other +* conntrack slips through. +*/ + }while (!atomic_add_unless (&ip_conntrack_count, +1 , + ip_conntrack_max)); } conntrack =kmem_cache_alloc (ip_conntrack_cachep,GFP_ATOMIC );if (!conntrack) {DEBUGP ("Can't allocate conntrack.\n" ); +atomic_dec (&ip_conntrack_count);return ERR_PTR (-ENOMEM ); }
@@ -670,8 +681,6 @@ struct ip_conntrack *ip_conntrack_alloc( conntrack->timeout.data = (unsigned long )conntrack; conntrack->timeout.function = death_by_timeout; -atomic_inc (&ip_conntrack_count); -return conntrack; }
net/netfilter/nf_conntrack_core.c +22 -9
@@ -868,16 +868,26 @@ __nf_conntrack_alloc(const struct nf_con }if (nf_conntrack_max - &&atomic_read (&nf_conntrack_count) >= nf_conntrack_max) { + && !atomic_add_unless (&nf_conntrack_count,1 , nf_conntrack_max)) {unsigned int hash =hash_conntrack (orig);/* Try dropping from this hash chain. */ -if (!early_drop (&nf_conntrack_hash[hash])) { -if (net_ratelimit ()) -printk (KERN_WARNING -"nf_conntrack: table full, dropping" -" packet.\n" ); -return ERR_PTR (-ENOMEM ); - } +do { +if (!early_drop (&nf_conntrack_hash[hash])) { +if (net_ratelimit ()) +printk (KERN_WARNING +"ip_conntrack: table full, " +"dropping packet.\n" ); +return ERR_PTR (-ENOMEM ); + } +/* +* On SMP environments, if the table is full and we +* early drop a conntrack to make some place for this +* new one then we have to ensure that no other +* conntrack slips through. +*/ + }while (!atomic_add_unless (&nf_conntrack_count, +1 , + nf_conntrack_max)); }/* find features needed by this conntrack. */
@@ -923,9 +933,12 @@ __nf_conntrack_alloc(const struct nf_con conntrack->timeout.data = (unsigned long )conntrack; conntrack->timeout.function = death_by_timeout; -atomic_inc (&nf_conntrack_count); +read_unlock_bh (&nf_ct_cache_lock); +return conntrack; + out:read_unlock_bh (&nf_ct_cache_lock); +atomic_dec (&nf_conntrack_count);return conntrack; }
Hi, Pablo,
From: Pablo Neira Ayuso <[email protected]>
Date: Mon, 21 Aug 2006 10:47:49 +0200
Quoted 5 lines
[CONNTRACK] Fix race condition in early drop
On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition.
Quoted 8 lines
CPU A CPU B
atomic_read()...
early_drop()...
...atomic_read()
allocate conntrack allocate conntrack
atomic_inc()atomic_inc()This patch uses an optimistic approach to solve the concurrency problem.
Good catch!
Quoted 27 lines
Index:
net-2.6/net/netfilter/nf_conntrack_core.c
===================================================================net/netfilter/nf_conntrack_core.c +9
-8@@ -868,16 +868,26 @@ __nf_conntrack_alloc(const struct nf_con }if (nf_conntrack_max - &&atomic_read (&nf_conntrack_count) >= nf_conntrack_max) { + && !atomic_add_unless (&nf_conntrack_count,1 , nf_conntrack_max)) {unsigned int hash =hash_conntrack (orig);/* Try dropping from this hash chain. */ -if (!early_drop (&nf_conntrack_hash[hash])) { -if (net_ratelimit ()) -printk (KERN_WARNING -"nf_conntrack: table full, dropping" -" packet.\n" ); -return ERR_PTR (-ENOMEM ); - } + do { + if (!early_drop(&nf_conntrack_hash[hash])) { + if (net_ratelimit()) + printk(KERN_WARNING + "ip_conntrack: table full, " + "dropping packet.\n"); + return ERR_PTR(-ENOMEM); + }
net/netfilter/nf_conntrack_core.c __nf_conntrack_alloc(const struct nf_con · line 891
Quoted 10 lines
net/netfilter/nf_conntrack_core.c +9
-0+/* +* On SMP environments, if the table is full and we +* early drop a conntrack to make some place for this +* new one then we have to ensure that no other +* conntrack slips through. +*/ + }while (!atomic_add_unless (&nf_conntrack_count, +1 , + nf_conntrack_max)); }
I think there is unfair case like following.
CPU A CPU B
atomic_add_unless() == 0
early_drop() ...
... atomic_add_unless() == 1
atomic_add_unless() == 0
early_drop()The right to allocate conntrack is stolen by CPU B in this case. And there is no assurance that CPU A can exits this loop in short time.
How about incrementing {ip,nf}_conntrack_count at first ?
1. atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) then early_drop()
3. if early_drop() failed, atomic_dec()
-- Yasuyuki Kozakai
A message that is not in the archive (sent off-list or not yet received): [email protected]
Pablo Neira Ayuso
maintainerHi Yasuyuki,
Quoted 15 lines · Yasuyuki KOZAKAI
From: Pablo Neira Ayuso <[email protected]>
Date: Mon, 21 Aug 2006 10:47:49 +0200Quoted 12 lines
[CONNTRACK] Fix race condition in early drop
On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition.
CPU A CPU B
atomic_read()...
early_drop()...
...atomic_read()
allocate conntrack allocate conntrack
atomic_inc()atomic_inc()
[snip]
Quoted 11 lines
I think there is unfair case like following.
CPU A CPU B atomic_add_unless() == 0 early_drop() ... ... atomic_add_unless() == 1 atomic_add_unless() == 0 early_drop()The right to allocate conntrack is stolen by CPU B in this case.
Yes, but we're under stress so I'm not sure if fairness is important here.
Quoted 1 line
And there is no assurance that CPU A can exits this loop in short time.
You are right, this seems important. Instead of looping we can just give up if we lose race.
Quoted 6 lines
How about incrementing {ip,nf}_conntrack_count at first ?
1.
atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) thenearly_drop()
3. ifearly_drop()failed,atomic_dec()
I thought about this possibility but then we can't guarantee the fixed maximum number of conntracks in the system.
Any comments?
Signature · 1 line
The dawn of the fourth age of Linux firewalling is coming; a time of great struggle and heroic deeds -- J.Kadlecsik got inspired by J.Morris
Pablo Neira Ayuso
maintainerQuoted 9 lines · Pablo Neira Ayuso
Quoted 6 lines
How about incrementing {ip,nf}_conntrack_count at first ?
1.
atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) thenearly_drop()
3. ifearly_drop()failed,atomic_dec()I thought about this possibility but then we can't guarantee the fixed maximum number of conntracks in the system.
Hm, actually this is wrong, we can guarantee the maximum number but aren't we somehow fooling the counter? I mean, the counter can reach values higher than conntrack_max during a short period.
Signature · 1 line
The dawn of the fourth age of Linux firewalling is coming; a time of great struggle and heroic deeds -- J.Kadlecsik got inspired by J.Morris
Hi,
From: Pablo Neira Ayuso <[email protected]>
Date: Tue, 22 Aug 2006 16:39:23 +0200
Quoted 9 lines
Quoted 8 lines · Pablo Neira Ayuso
Quoted 6 lines
How about incrementing {ip,nf}_conntrack_count at first ?
1.
atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) thenearly_drop()
3. ifearly_drop()failed,atomic_dec()
Quoted 6 lines
Quoted 2 lines
I thought about this possibility but then we can't guarantee the fixed maximum number of conntracks in the system.
Hm, actually this is wrong, we can guarantee the maximum number but aren't we somehow fooling the counter? I mean, the counter can reach values higher than
conntrack_maxduring a short period.
good point. I don't mind fooling the counter in this short period, indeed someone might mind that. Then,
From: Pablo Neira Ayuso <[email protected]>
Date: Tue, 22 Aug 2006 15:46:50 +0200
Quoted 4 lines
Quoted 1 line
And there is no assurance that CPU A can exits this loop in short time.
You are right, this seems important. Instead of looping we can just give up if we lose race.
Now I think this is better.
-- Yasuyuki Kozakai
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 13 lines · Yasuyuki KOZAKAI
Quoted 13 lines
Quoted 10 lines · Pablo Neira Ayuso
Quoted 6 lines
How about incrementing {ip,nf}_conntrack_count at first ?
1.
atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) thenearly_drop()
3. ifearly_drop()failed,atomic_dec()I thought about this possibility but then we can't guarantee the fixed maximum number of conntracks in the system.
Quoted 6 lines
Quoted 3 lines
Hm, actually this is wrong, we can guarantee the maximum number but aren't we somehow fooling the counter? I mean, the counter can reach values higher than
conntrack_maxduring a short period.good point. I don't mind fooling the counter in this short period,
Me neither. We can already be off by more than one since early_drop just removes a conntrack from the hash tables, but it is not necessarily destroyed immediately (at which point the counter is decremented). This is a reason why we can't loop while waiting for the counter to decrement.
Quoted 39 lines · Pablo Neira Ayuso
Hi Yasuyuki,
Quoted 15 lines · Yasuyuki KOZAKAI
From: Pablo Neira Ayuso <[email protected]>
Date: Mon, 21 Aug 2006 10:47:49 +0200Quoted 12 lines
[CONNTRACK] Fix race condition in early drop
On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition.
CPU A CPU B
atomic_read()...
early_drop()...
...atomic_read()
allocate conntrack allocate conntrack
atomic_inc()atomic_inc()[snip]
Quoted 11 lines
I think there is unfair case like following.
CPU A CPU B atomic_add_unless() == 0 early_drop() ... ... atomic_add_unless() == 1 atomic_add_unless() == 0 early_drop()The right to allocate conntrack is stolen by CPU B in this case.
Yes, but we're under stress so I'm not sure if fairness is important here.
Quoted 1 line
And there is no assurance that CPU A can exits this loop in short time.
You are right, this seems important. Instead of looping we can just give up if we lose race.
Quoted 2 lines
How about incrementing {ip,nf}_conntrack_count at first ?
Quoted 8 lines
Quoted 4 lines
1.
atomic_add()
2. if {ip,nf}_conntrack_count > {ip,nf}_conntrack_max (not '>=' ) thenearly_drop()
3. ifearly_drop()failed,atomic_dec()I thought about this possibility but then we can't guarantee the fixed maximum number of conntracks in the system.
Any comments?
Sorry, maybe I'm to fresh, but if you say "any"... Maybe something simpler? I attach a proposal.
Jarek P.
net/netfilter//nf_conntrack_core.c renamed from net/netfilter/nf_conntrack_core.c- +7 -1
@@ -871,6 +871,7 @@unsigned int hash =hash_conntrack (orig);/* Try dropping from this hash chain. */ if (!early_drop (&nf_conntrack_hash[hash])) { +atomic_dec (&nf_conntrack_count);if (net_ratelimit ())printk (KERN_WARNING "nf_conntrack: table full, dropping"
@@ -905,6 +906,12 @@ goto out; } + if (!atomic_add_unless(&nf_conntrack_count, 1, nf_conntrack_max) { +kmem_cache_free (nf_ct_cache[features].cachep, conntrack); + conntrack =NULL ; + goto out; + } +memset (conntrack,0 , nf_ct_cache[features].size); conntrack->features = features;if (helper) {
@@ -922,7 +929,6 @@ conntrack->timeout.data = (unsigned long )conntrack; conntrack->timeout.function = death_by_timeout; -atomic_inc (&nf_conntrack_count); out:read_unlock_bh (&nf_ct_cache_lock);return conntrack;
On 24-08-2006 13:47, Jarek Poplawski wrote:
...
Sorry again, I'm definitely too fresh. It should be even shorter:
Jarek P.
net/netfilter//nf_conntrack_core.c renamed from net/netfilter/nf_conntrack_core.c- +6 -1
@@ -905,6 +906,12 @@ goto out; } + if (!atomic_add_unless(&nf_conntrack_count, 1, nf_conntrack_max) { +kmem_cache_free (nf_ct_cache[features].cachep, conntrack); + conntrack =NULL ; + goto out; + } +memset (conntrack,0 , nf_ct_cache[features].size); conntrack->features = features;if (helper) {
@@ -922,7 +929,6 @@ conntrack->timeout.data = (unsigned long )conntrack; conntrack->timeout.function = death_by_timeout; -atomic_inc (&nf_conntrack_count); out:read_unlock_bh (&nf_ct_cache_lock);return conntrack;