Commit 7c3ceb4fb9667f34f1599a062efecf4cdc4a4ce5
Committed by
David S. Miller
1 parent
91ef5d2d6e
Exists in
master
and in
7 other branches
[SCTP]: Allow spillover of receive buffer to avoid deadlock.
This patch fixes a deadlock situation in the receive path by allowing temporary spillover of the receive buffer. - If the chunk we receive has a tsn that immediately follows the ctsn, accept it even if we run out of receive buffer space and renege data with higher TSNs. - Once we accept one chunk in a packet, accept all the remaining chunks even if we run out of receive buffer space. Signed-off-by: Neil Horman <nhorman@tuxdriver.com> Acked-by: Mark Butler <butlerm@middle.net> Acked-by: Vlad Yasevich <vladislav.yasevich@hp.com> Signed-off-by: Sridhar Samudrala <sri@us.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Showing 3 changed files with 38 additions and 10 deletions Side-by-side Diff
include/net/sctp/structs.h
... | ... | @@ -712,6 +712,7 @@ |
712 | 712 | __u8 tsn_gap_acked; /* Is this chunk acked by a GAP ACK? */ |
713 | 713 | __s8 fast_retransmit; /* Is this chunk fast retransmitted? */ |
714 | 714 | __u8 tsn_missing_report; /* Data chunk missing counter. */ |
715 | + __u8 data_accepted; /* At least 1 chunk in this packet accepted */ | |
715 | 716 | }; |
716 | 717 | |
717 | 718 | void sctp_chunk_hold(struct sctp_chunk *); |
net/sctp/inqueue.c
net/sctp/sm_statefuns.c
... | ... | @@ -5151,7 +5151,9 @@ |
5151 | 5151 | int tmp; |
5152 | 5152 | __u32 tsn; |
5153 | 5153 | int account_value; |
5154 | + struct sctp_tsnmap *map = (struct sctp_tsnmap *)&asoc->peer.tsn_map; | |
5154 | 5155 | struct sock *sk = asoc->base.sk; |
5156 | + int rcvbuf_over = 0; | |
5155 | 5157 | |
5156 | 5158 | data_hdr = chunk->subh.data_hdr = (sctp_datahdr_t *)chunk->skb->data; |
5157 | 5159 | skb_pull(chunk->skb, sizeof(sctp_datahdr_t)); |
5158 | 5160 | |
... | ... | @@ -5162,10 +5164,16 @@ |
5162 | 5164 | /* ASSERT: Now skb->data is really the user data. */ |
5163 | 5165 | |
5164 | 5166 | /* |
5165 | - * if we are established, and we have used up our receive | |
5166 | - * buffer memory, drop the frame | |
5167 | + * If we are established, and we have used up our receive buffer | |
5168 | + * memory, think about droping the frame. | |
5169 | + * Note that we have an opportunity to improve performance here. | |
5170 | + * If we accept one chunk from an skbuff, we have to keep all the | |
5171 | + * memory of that skbuff around until the chunk is read into user | |
5172 | + * space. Therefore, once we accept 1 chunk we may as well accept all | |
5173 | + * remaining chunks in the skbuff. The data_accepted flag helps us do | |
5174 | + * that. | |
5167 | 5175 | */ |
5168 | - if (asoc->state == SCTP_STATE_ESTABLISHED) { | |
5176 | + if ((asoc->state == SCTP_STATE_ESTABLISHED) && (!chunk->data_accepted)) { | |
5169 | 5177 | /* |
5170 | 5178 | * If the receive buffer policy is 1, then each |
5171 | 5179 | * association can allocate up to sk_rcvbuf bytes |
5172 | 5180 | |
... | ... | @@ -5176,9 +5184,25 @@ |
5176 | 5184 | account_value = atomic_read(&asoc->rmem_alloc); |
5177 | 5185 | else |
5178 | 5186 | account_value = atomic_read(&sk->sk_rmem_alloc); |
5187 | + if (account_value > sk->sk_rcvbuf) { | |
5188 | + /* | |
5189 | + * We need to make forward progress, even when we are | |
5190 | + * under memory pressure, so we always allow the | |
5191 | + * next tsn after the ctsn ack point to be accepted. | |
5192 | + * This lets us avoid deadlocks in which we have to | |
5193 | + * drop frames that would otherwise let us drain the | |
5194 | + * receive queue. | |
5195 | + */ | |
5196 | + if ((sctp_tsnmap_get_ctsn(map) + 1) != tsn) | |
5197 | + return SCTP_IERROR_IGNORE_TSN; | |
5179 | 5198 | |
5180 | - if (account_value > sk->sk_rcvbuf) | |
5181 | - return SCTP_IERROR_IGNORE_TSN; | |
5199 | + /* | |
5200 | + * We're going to accept the frame but we should renege | |
5201 | + * to make space for it. This will send us down that | |
5202 | + * path later in this function. | |
5203 | + */ | |
5204 | + rcvbuf_over = 1; | |
5205 | + } | |
5182 | 5206 | } |
5183 | 5207 | |
5184 | 5208 | /* Process ECN based congestion. |
... | ... | @@ -5226,6 +5250,7 @@ |
5226 | 5250 | datalen -= sizeof(sctp_data_chunk_t); |
5227 | 5251 | |
5228 | 5252 | deliver = SCTP_CMD_CHUNK_ULP; |
5253 | + chunk->data_accepted = 1; | |
5229 | 5254 | |
5230 | 5255 | /* Think about partial delivery. */ |
5231 | 5256 | if ((datalen >= asoc->rwnd) && (!asoc->ulpq.pd_mode)) { |
... | ... | @@ -5242,7 +5267,8 @@ |
5242 | 5267 | * large spill over. |
5243 | 5268 | */ |
5244 | 5269 | if (!asoc->rwnd || asoc->rwnd_over || |
5245 | - (datalen > asoc->rwnd + asoc->frag_point)) { | |
5270 | + (datalen > asoc->rwnd + asoc->frag_point) || | |
5271 | + rcvbuf_over) { | |
5246 | 5272 | |
5247 | 5273 | /* If this is the next TSN, consider reneging to make |
5248 | 5274 | * room. Note: Playing nice with a confused sender. A |
... | ... | @@ -5250,8 +5276,8 @@ |
5250 | 5276 | * space and in the future we may want to detect and |
5251 | 5277 | * do more drastic reneging. |
5252 | 5278 | */ |
5253 | - if (sctp_tsnmap_has_gap(&asoc->peer.tsn_map) && | |
5254 | - (sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map) + 1) == tsn) { | |
5279 | + if (sctp_tsnmap_has_gap(map) && | |
5280 | + (sctp_tsnmap_get_ctsn(map) + 1) == tsn) { | |
5255 | 5281 | SCTP_DEBUG_PRINTK("Reneging for tsn:%u\n", tsn); |
5256 | 5282 | deliver = SCTP_CMD_RENEGE; |
5257 | 5283 | } else { |