tcp/dccp: do not touch listener sk_refcnt under synflood
When a SYNFLOOD targets a non SO_REUSEPORT listener, multiple cpus contend on sk->sk_refcnt and sk->sk_wmem_alloc changes. By letting listeners use SOCK_RCU_FREE infrastructure, we can relax TCP_LISTEN lookup rules and avoid touching sk_refcnt Note that we still use SLAB_DESTROY_BY_RCU rules for other sockets, only listeners are impacted by this change. Peak performance under SYNFLOOD is increased by ~33% : On my test machine, I could process 3.2 Mpps instead of 2.4 Mpps Most consuming functions are now skb_set_owner_w() and sock_wfree() contending on sk->sk_wmem_alloc when cooking SYNACK and freeing them. Signed-off-by: Eric Dumazet <edumazet@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

parent
3a5d1c0e7c
commit
3b24d854cb
@@ -120,6 +120,7 @@ static inline int compute_score(struct sock *sk, struct net *net,
|
||||
return score;
|
||||
}
|
||||
|
||||
/* called with rcu_read_lock() */
|
||||
struct sock *inet6_lookup_listener(struct net *net,
|
||||
struct inet_hashinfo *hashinfo,
|
||||
struct sk_buff *skb, int doff,
|
||||
@@ -127,38 +128,27 @@ struct sock *inet6_lookup_listener(struct net *net,
|
||||
const __be16 sport, const struct in6_addr *daddr,
|
||||
const unsigned short hnum, const int dif)
|
||||
{
|
||||
struct sock *sk;
|
||||
const struct hlist_nulls_node *node;
|
||||
struct sock *result;
|
||||
int score, hiscore, matches = 0, reuseport = 0;
|
||||
bool select_ok = true;
|
||||
u32 phash = 0;
|
||||
unsigned int hash = inet_lhashfn(net, hnum);
|
||||
struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash];
|
||||
int score, hiscore = 0, matches = 0, reuseport = 0;
|
||||
struct sock *sk, *result = NULL;
|
||||
u32 phash = 0;
|
||||
|
||||
begin:
|
||||
result = NULL;
|
||||
hiscore = 0;
|
||||
sk_nulls_for_each(sk, node, &ilb->head) {
|
||||
sk_for_each(sk, &ilb->head) {
|
||||
score = compute_score(sk, net, hnum, daddr, dif);
|
||||
if (score > hiscore) {
|
||||
hiscore = score;
|
||||
result = sk;
|
||||
reuseport = sk->sk_reuseport;
|
||||
if (reuseport) {
|
||||
phash = inet6_ehashfn(net, daddr, hnum,
|
||||
saddr, sport);
|
||||
if (select_ok) {
|
||||
struct sock *sk2;
|
||||
sk2 = reuseport_select_sock(sk, phash,
|
||||
skb, doff);
|
||||
if (sk2) {
|
||||
result = sk2;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
result = reuseport_select_sock(sk, phash,
|
||||
skb, doff);
|
||||
if (result)
|
||||
return result;
|
||||
matches = 1;
|
||||
}
|
||||
result = sk;
|
||||
reuseport = sk->sk_reuseport;
|
||||
} else if (score == hiscore && reuseport) {
|
||||
matches++;
|
||||
if (reciprocal_scale(phash, matches) == 0)
|
||||
@@ -166,24 +156,6 @@ begin:
|
||||
phash = next_pseudo_random32(phash);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* if the nulls value we got at the end of this lookup is
|
||||
* not the expected one, we must restart lookup.
|
||||
* We probably met an item that was moved to another chain.
|
||||
*/
|
||||
if (get_nulls_value(node) != hash + LISTENING_NULLS_BASE)
|
||||
goto begin;
|
||||
if (result) {
|
||||
found:
|
||||
if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt)))
|
||||
result = NULL;
|
||||
else if (unlikely(compute_score(result, net, hnum, daddr,
|
||||
dif) < hiscore)) {
|
||||
sock_put(result);
|
||||
select_ok = false;
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(inet6_lookup_listener);
|
||||
@@ -195,10 +167,12 @@ struct sock *inet6_lookup(struct net *net, struct inet_hashinfo *hashinfo,
|
||||
const int dif)
|
||||
{
|
||||
struct sock *sk;
|
||||
bool refcounted;
|
||||
|
||||
sk = __inet6_lookup(net, hashinfo, skb, doff, saddr, sport, daddr,
|
||||
ntohs(dport), dif);
|
||||
|
||||
ntohs(dport), dif, &refcounted);
|
||||
if (sk && !refcounted && !atomic_inc_not_zero(&sk->sk_refcnt))
|
||||
sk = NULL;
|
||||
return sk;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(inet6_lookup);
|
||||
|
Reference in New Issue
Block a user