aboutsummaryrefslogtreecommitdiffstats
path: root/net/dccp
diff options
context:
space:
mode:
authorEric Dumazet <edumazet@google.com>2015-10-22 11:20:46 -0400
committerDavid S. Miller <davem@davemloft.net>2015-10-23 08:42:21 -0400
commit5e0724d027f0548511a2165a209572d48fe7a4c8 (patch)
tree709301be9b56652004047b89f4467b3c917814cd /net/dccp
parent7b1311807f3d3eb8bef3ccc53127838b3bea3771 (diff)
tcp/dccp: fix hashdance race for passive sessions
Multiple cpus can process duplicates of incoming ACK messages matching a SYN_RECV request socket. This is a rare event under normal operations, but definitely can happen. Only one must win the race, otherwise corruption would occur. To fix this without adding new atomic ops, we use logic in inet_ehash_nolisten() to detect the request was present in the same ehash bucket where we try to insert the new child. If request socket was not found, we have to undo the child creation. This actually removes a spin_lock()/spin_unlock() pair in reqsk_queue_unlink() for the fast path. Fixes: e994b2f0fb92 ("tcp: do not lock listener to process SYN packets") Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table") Signed-off-by: Eric Dumazet <edumazet@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/dccp')
-rw-r--r--net/dccp/dccp.h4
-rw-r--r--net/dccp/ipv4.c6
-rw-r--r--net/dccp/ipv6.c9
-rw-r--r--net/dccp/minisocks.c14
4 files changed, 20 insertions, 13 deletions
diff --git a/net/dccp/dccp.h b/net/dccp/dccp.h
index 923f5a180134..b0e28d24e1a7 100644
--- a/net/dccp/dccp.h
+++ b/net/dccp/dccp.h
@@ -278,7 +278,9 @@ int dccp_v4_do_rcv(struct sock *sk, struct sk_buff *skb);
278 278
279struct sock *dccp_v4_request_recv_sock(const struct sock *sk, struct sk_buff *skb, 279struct sock *dccp_v4_request_recv_sock(const struct sock *sk, struct sk_buff *skb,
280 struct request_sock *req, 280 struct request_sock *req,
281 struct dst_entry *dst); 281 struct dst_entry *dst,
282 struct request_sock *req_unhash,
283 bool *own_req);
282struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb, 284struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb,
283 struct request_sock *req); 285 struct request_sock *req);
284 286
diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c
index 59bc180b02d8..5684e14932bd 100644
--- a/net/dccp/ipv4.c
+++ b/net/dccp/ipv4.c
@@ -393,7 +393,9 @@ static inline u64 dccp_v4_init_sequence(const struct sk_buff *skb)
393struct sock *dccp_v4_request_recv_sock(const struct sock *sk, 393struct sock *dccp_v4_request_recv_sock(const struct sock *sk,
394 struct sk_buff *skb, 394 struct sk_buff *skb,
395 struct request_sock *req, 395 struct request_sock *req,
396 struct dst_entry *dst) 396 struct dst_entry *dst,
397 struct request_sock *req_unhash,
398 bool *own_req)
397{ 399{
398 struct inet_request_sock *ireq; 400 struct inet_request_sock *ireq;
399 struct inet_sock *newinet; 401 struct inet_sock *newinet;
@@ -426,7 +428,7 @@ struct sock *dccp_v4_request_recv_sock(const struct sock *sk,
426 428
427 if (__inet_inherit_port(sk, newsk) < 0) 429 if (__inet_inherit_port(sk, newsk) < 0)
428 goto put_and_exit; 430 goto put_and_exit;
429 __inet_hash_nolisten(newsk, NULL); 431 *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash));
430 432
431 return newsk; 433 return newsk;
432 434
diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c
index d9cc731f2619..ef4e48ce9143 100644
--- a/net/dccp/ipv6.c
+++ b/net/dccp/ipv6.c
@@ -380,7 +380,9 @@ drop:
380static struct sock *dccp_v6_request_recv_sock(const struct sock *sk, 380static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
381 struct sk_buff *skb, 381 struct sk_buff *skb,
382 struct request_sock *req, 382 struct request_sock *req,
383 struct dst_entry *dst) 383 struct dst_entry *dst,
384 struct request_sock *req_unhash,
385 bool *own_req)
384{ 386{
385 struct inet_request_sock *ireq = inet_rsk(req); 387 struct inet_request_sock *ireq = inet_rsk(req);
386 struct ipv6_pinfo *newnp; 388 struct ipv6_pinfo *newnp;
@@ -393,7 +395,8 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
393 /* 395 /*
394 * v6 mapped 396 * v6 mapped
395 */ 397 */
396 newsk = dccp_v4_request_recv_sock(sk, skb, req, dst); 398 newsk = dccp_v4_request_recv_sock(sk, skb, req, dst,
399 req_unhash, own_req);
397 if (newsk == NULL) 400 if (newsk == NULL)
398 return NULL; 401 return NULL;
399 402
@@ -511,7 +514,7 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
511 dccp_done(newsk); 514 dccp_done(newsk);
512 goto out; 515 goto out;
513 } 516 }
514 __inet_hash(newsk, NULL); 517 *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash));
515 518
516 return newsk; 519 return newsk;
517 520
diff --git a/net/dccp/minisocks.c b/net/dccp/minisocks.c
index d10aace43672..1994f8af646b 100644
--- a/net/dccp/minisocks.c
+++ b/net/dccp/minisocks.c
@@ -143,6 +143,7 @@ struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb,
143{ 143{
144 struct sock *child = NULL; 144 struct sock *child = NULL;
145 struct dccp_request_sock *dreq = dccp_rsk(req); 145 struct dccp_request_sock *dreq = dccp_rsk(req);
146 bool own_req;
146 147
147 /* Check for retransmitted REQUEST */ 148 /* Check for retransmitted REQUEST */
148 if (dccp_hdr(skb)->dccph_type == DCCP_PKT_REQUEST) { 149 if (dccp_hdr(skb)->dccph_type == DCCP_PKT_REQUEST) {
@@ -182,14 +183,13 @@ struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb,
182 if (dccp_parse_options(sk, dreq, skb)) 183 if (dccp_parse_options(sk, dreq, skb))
183 goto drop; 184 goto drop;
184 185
185 child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL); 186 child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
186 if (child == NULL) 187 req, &own_req);
188 if (!child)
187 goto listen_overflow; 189 goto listen_overflow;
188 190
189 inet_csk_reqsk_queue_drop(sk, req); 191 return inet_csk_complete_hashdance(sk, child, req, own_req);
190 inet_csk_reqsk_queue_add(sk, req, child); 192
191out:
192 return child;
193listen_overflow: 193listen_overflow:
194 dccp_pr_debug("listen_overflow!\n"); 194 dccp_pr_debug("listen_overflow!\n");
195 DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_TOO_BUSY; 195 DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_TOO_BUSY;
@@ -198,7 +198,7 @@ drop:
198 req->rsk_ops->send_reset(sk, skb); 198 req->rsk_ops->send_reset(sk, skb);
199 199
200 inet_csk_reqsk_queue_drop(sk, req); 200 inet_csk_reqsk_queue_drop(sk, req);
201 goto out; 201 return NULL;
202} 202}
203 203
204EXPORT_SYMBOL_GPL(dccp_check_req); 204EXPORT_SYMBOL_GPL(dccp_check_req);