diff options
Diffstat (limited to 'net/ipv4/tcp_input.c')
| -rw-r--r-- | net/ipv4/tcp_input.c | 23 | 
1 files changed, 20 insertions, 3 deletions
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index fdd88c3803a6..2d656eef7f8e 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4481,19 +4481,34 @@ static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int  int tcp_send_rcvq(struct sock *sk, struct msghdr *msg, size_t size)  {  	struct sk_buff *skb; +	int err = -ENOMEM; +	int data_len = 0;  	bool fragstolen;  	if (size == 0)  		return 0; -	skb = alloc_skb(size, sk->sk_allocation); +	if (size > PAGE_SIZE) { +		int npages = min_t(size_t, size >> PAGE_SHIFT, MAX_SKB_FRAGS); + +		data_len = npages << PAGE_SHIFT; +		size = data_len + (size & ~PAGE_MASK); +	} +	skb = alloc_skb_with_frags(size - data_len, data_len, +				   PAGE_ALLOC_COSTLY_ORDER, +				   &err, sk->sk_allocation);  	if (!skb)  		goto err; +	skb_put(skb, size - data_len); +	skb->data_len = data_len; +	skb->len = size; +  	if (tcp_try_rmem_schedule(sk, skb, skb->truesize))  		goto err_free; -	if (memcpy_from_msg(skb_put(skb, size), msg, size)) +	err = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, size); +	if (err)  		goto err_free;  	TCP_SKB_CB(skb)->seq = tcp_sk(sk)->rcv_nxt; @@ -4509,7 +4524,8 @@ int tcp_send_rcvq(struct sock *sk, struct msghdr *msg, size_t size)  err_free:  	kfree_skb(skb);  err: -	return -ENOMEM; +	return err; +  }  static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) @@ -5667,6 +5683,7 @@ discard:  		}  		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; +		tp->copied_seq = tp->rcv_nxt;  		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;  		/* RFC1323: The window in SYN & SYN/ACK segments is  |