Received: by 10.192.165.148 with SMTP id m20csp280845imm; Tue, 24 Apr 2018 22:29:21 -0700 (PDT) X-Google-Smtp-Source: AIpwx49XXWzeSlShxQ+reJQBX/RSKXXtkrGPFiVN5p4v1wQvhVvX6SGJEJKR5thlo40r+piEWClE X-Received: by 2002:a17:902:7841:: with SMTP id e1-v6mr28124577pln.197.1524634161325; Tue, 24 Apr 2018 22:29:21 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1524634161; cv=none; d=google.com; s=arc-20160816; b=wBwvhdjCoSjdJo8ds6p87n/Kal05kfMUkbikTt1ibAT2X6s3D1mj3B9aeAuFmXiBuX A3ZyeZzgnTK7xsZE2q2hxaBfRQiLBShxhrCr3ZyTacoEde0s3w9oK5KPTKKBNMFwJQsZ 8AedOYTxMKZ1KcJD3n+VmavnvWfW5doUksqJ1p4afmsEjvvV9JpiQe6IIciuk/47i6AO 1EFQQB53smIyvqregsBXk5OzYib5ekvXsf9zpxgolPNYi8VtQg84JfQStdtAcJ0mOkJF nJJGDwtssPUj3E5bB9eI3BD4xiiUI3a5PBYVC50X615HkEhRoyILox99uktY9a0iq7BW CzqQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=v8MXnrZe5Fb6ySF7LE4cl9Gm3f7lCqx5nXIO75FxTfI=; b=DZOQPyV9sgwKCZd5amhS1xLA1pYeGvxDLLiwTjvlDchKh77tX+9E0c8JIH9drOJY1y o7/0Iq21uKM/quLMp9dqvQOoMSNN9o3a0XI3TVi4F5qasl9bDAv67i72Stu38iNOqsIF I9Q5y7fpLjEsS0HozoSx4OJdZXdlwkJBTQzvuKCQxCM/09Tz9nyaloD2dTxXH1A9bjHH 3C20CweCO51uYXRcFO3Yc8CQEgLc442S8EcRa6aEBrlTs5IDnBNiDgk/YojYuupawTRk 2zEqbNFmIeA7q6jQtvgqLOX1qN9W8gsjG7hiW3fjrNC7I8UugD1tMZUSo9Gb+9Ai/9dM pUhA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=UPuvXsbk; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id g5si7567968pgc.283.2018.04.24.22.29.07; Tue, 24 Apr 2018 22:29:21 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=UPuvXsbk; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751302AbeDYF1l (ORCPT + 99 others); Wed, 25 Apr 2018 01:27:41 -0400 Received: from mail-wr0-f169.google.com ([209.85.128.169]:42382 "EHLO mail-wr0-f169.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750888AbeDYF1d (ORCPT ); Wed, 25 Apr 2018 01:27:33 -0400 Received: by mail-wr0-f169.google.com with SMTP id v5-v6so1258154wrf.9 for ; Tue, 24 Apr 2018 22:27:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=v8MXnrZe5Fb6ySF7LE4cl9Gm3f7lCqx5nXIO75FxTfI=; b=UPuvXsbklR+eH7YpUNE/RAiSLhNt0WsDqipqY60VCNINaXecIDB9tMfJ9qexEA7Y6o jt+qo7JDbaBmFDk2c6SLjnN3RTAuUCFt/6sMejnpuE5CFeZzlGVDI2N7j/hezlE/PTYq Nm0wkJYsdq9RrLnlue4LCSGUaF2ueuNiA+szU5L/Wm0gsFa57F11WhDlfHzBFuGpDqqV ZEqb4p4IYEAr9kaGonsLAptYOHpRs99pccTBG5IZ6Cn03WbCsAtyOGukYWzdCLtKkhcJ 7XJIuw/2ZvbKSdjaLpAhQ89v1AucCONyGueM4pk3yTNm+yfemFD26RPJmX0F1EPjnBc5 weLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=v8MXnrZe5Fb6ySF7LE4cl9Gm3f7lCqx5nXIO75FxTfI=; b=ukp2q8IykZZaWAPlSOqoppe/VppBEx0AFwSEgeGH2plS0+vCkSs3Gu+fSjc/CpEuoy zFFUYEhNWzazGoMITk+4q6s0XtYledplmLY+JsLMUbqAC7OaZPSS5CgYhXaZpM4n+Qja X2LL8y0eBedW5IFbcOU5avvtuiteDYF5spWMieicN2ZYewrSwz6w5Ro7dsGxXt2o2Wse H9IRGqCNO3EPKA32kmg2b2PHJVjvAoqp057VVoLq4cIg8Jux1tl+HdaOYyGZZZk2c4r2 5mP/2pmP53mdGr3sqiXOCOZU6236VfX7k+J7KIpzwZF18/0sNRE8nSpQLhOWzOIEXpMf 9uFw== X-Gm-Message-State: ALQs6tBHRcmu/GAjzyCG1CrxZc334/bVcHQJcAqq2hx7Z8Z0JyZfB7YN 5SEuAayeP1Wg1IZVCyjoYVVMbQ== X-Received: by 2002:adf:86ac:: with SMTP id 41-v6mr21867751wrx.260.1524634051730; Tue, 24 Apr 2018 22:27:31 -0700 (PDT) Received: from localhost ([2620:15c:2c4:201:f5a:7eca:440a:3ead]) by smtp.gmail.com with ESMTPSA id p35-v6sm22793256wrb.12.2018.04.24.22.27.30 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 24 Apr 2018 22:27:30 -0700 (PDT) From: Eric Dumazet To: "David S . Miller" Cc: netdev , Andy Lutomirski , linux-kernel , linux-mm , Soheil Hassas Yeganeh , Eric Dumazet , Eric Dumazet Subject: [PATCH net-next 1/2] tcp: add TCP_ZEROCOPY_RECEIVE support for zerocopy receive Date: Tue, 24 Apr 2018 22:27:21 -0700 Message-Id: <20180425052722.73022-2-edumazet@google.com> X-Mailer: git-send-email 2.17.0.484.g0c8726318c-goog In-Reply-To: <20180425052722.73022-1-edumazet@google.com> References: <20180425052722.73022-1-edumazet@google.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org When adding tcp mmap() implementation, I forgot that socket lock had to be taken before current->mm->mmap_sem. syzbot eventually caught the bug. Since we can not lock the socket in tcp mmap() handler we have to split the operation in two phases. 1) mmap() on a tcp socket simply reserves VMA space, and nothing else. This operation does not involve any TCP locking. 2) setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) implements the transfert of pages from skbs to one VMA. This operation only uses down_read(¤t->mm->mmap_sem) after holding TCP lock, thus solving the lockdep issue. This new implementation was suggested by Andy Lutomirski with great details. Benefits are : - Better scalability, in case multiple threads reuse VMAS (without mmap()/munmap() calls) since mmap_sem wont be write locked. - Better error recovery. The previous mmap() model had to provide the expected size of the mapping. If for some reason one part could not be mapped (partial MSS), the whole operation had to be aborted. With the tcp_zerocopy_receive struct, kernel can report how many bytes were successfuly mapped, and how many bytes should be read to skip the problematic sequence. - No more memory allocation to hold an array of page pointers. 16 MB mappings needed 32 KB for this array, potentially using vmalloc() :/ - skbs are freed while mmap_sem has been released Following patch makes the change in tcp_mmap tool to demonstrate one possible use of mmap() and setsockopt(... TCP_ZEROCOPY_RECEIVE ...) Note that memcg might require additional changes. Fixes: 93ab6cc69162 ("tcp: implement mmap() for zero copy receive") Signed-off-by: Eric Dumazet Reported-by: syzbot Suggested-by: Andy Lutomirski Cc: linux-mm@kvack.org Cc: Soheil Hassas Yeganeh --- include/uapi/linux/tcp.h | 8 ++ net/ipv4/tcp.c | 186 ++++++++++++++++++++------------------- 2 files changed, 103 insertions(+), 91 deletions(-) diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index 379b08700a542d49bbce9b4b49b17879d00b69bb..e9e8373b34b9ddc735329341b91f455bf5c0b17c 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -122,6 +122,7 @@ enum { #define TCP_MD5SIG_EXT 32 /* TCP MD5 Signature with extensions */ #define TCP_FASTOPEN_KEY 33 /* Set the key for Fast Open (cookie) */ #define TCP_FASTOPEN_NO_COOKIE 34 /* Enable TFO without a TFO cookie */ +#define TCP_ZEROCOPY_RECEIVE 35 struct tcp_repair_opt { __u32 opt_code; @@ -276,4 +277,11 @@ struct tcp_diag_md5sig { __u8 tcpm_key[TCP_MD5SIG_MAXKEYLEN]; }; +/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */ + +struct tcp_zerocopy_receive { + __u64 address; /* in: address of mapping */ + __u32 length; /* in/out: number of bytes to map/mapped */ + __u32 recv_skip_hint; /* out: amount of bytes to skip */ +}; #endif /* _UAPI_LINUX_TCP_H */ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index dfd090ea54ad47112fc23c61180b5bf8edd2c736..a28eca97df9465a0aa522a1833a171b53c237b1f 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1726,118 +1726,108 @@ int tcp_set_rcvlowat(struct sock *sk, int val) } EXPORT_SYMBOL(tcp_set_rcvlowat); -/* When user wants to mmap X pages, we first need to perform the mapping - * before freeing any skbs in receive queue, otherwise user would be unable - * to fallback to standard recvmsg(). This happens if some data in the - * requested block is not exactly fitting in a page. - * - * We only support order-0 pages for the moment. - * mmap() on TCP is very strict, there is no point - * trying to accommodate with pathological layouts. - */ +static const struct vm_operations_struct tcp_vm_ops = { +}; + int tcp_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma) { - unsigned long size = vma->vm_end - vma->vm_start; - unsigned int nr_pages = size >> PAGE_SHIFT; - struct page **pages_array = NULL; - u32 seq, len, offset, nr = 0; - struct sock *sk = sock->sk; - const skb_frag_t *frags; + if (vma->vm_flags & (VM_WRITE | VM_EXEC)) + return -EPERM; + vma->vm_flags &= ~(VM_MAYWRITE | VM_MAYEXEC); + + /* Instruct vm_insert_page() to not down_read(mmap_sem) */ + vma->vm_flags |= VM_MIXEDMAP; + + vma->vm_ops = &tcp_vm_ops; + return 0; +} +EXPORT_SYMBOL(tcp_mmap); + +static int tcp_zerocopy_receive(struct sock *sk, + struct tcp_zerocopy_receive *zc) +{ + unsigned long address = (unsigned long)zc->address; + const skb_frag_t *frags = NULL; + u32 length = 0, seq, offset; + struct vm_area_struct *vma; + struct sk_buff *skb = NULL; struct tcp_sock *tp; - struct sk_buff *skb; int ret; - if (vma->vm_pgoff || !nr_pages) + if (address & (PAGE_SIZE - 1) || address != zc->address) return -EINVAL; - if (vma->vm_flags & VM_WRITE) - return -EPERM; - /* TODO: Maybe the following is not needed if pages are COW */ - vma->vm_flags &= ~VM_MAYWRITE; - - lock_sock(sk); - - ret = -ENOTCONN; if (sk->sk_state == TCP_LISTEN) - goto out; + return -ENOTCONN; sock_rps_record_flow(sk); - if (tcp_inq(sk) < size) { - ret = sock_flag(sk, SOCK_DONE) ? -EIO : -EAGAIN; + down_read(¤t->mm->mmap_sem); + + ret = -EINVAL; + vma = find_vma(current->mm, address); + if (!vma || vma->vm_start > address || vma->vm_ops != &tcp_vm_ops) goto out; - } + zc->length = min_t(unsigned long, zc->length, vma->vm_end - address); + tp = tcp_sk(sk); seq = tp->copied_seq; - /* Abort if urgent data is in the area */ - if (unlikely(tp->urg_data)) { - u32 urg_offset = tp->urg_seq - seq; + zc->length = min_t(u32, zc->length, tcp_inq(sk)); - ret = -EINVAL; - if (urg_offset < size) - goto out; - } - ret = -ENOMEM; - pages_array = kvmalloc_array(nr_pages, sizeof(struct page *), - GFP_KERNEL); - if (!pages_array) - goto out; - skb = tcp_recv_skb(sk, seq, &offset); - ret = -EINVAL; -skb_start: - /* We do not support anything not in page frags */ - offset -= skb_headlen(skb); - if ((int)offset < 0) - goto out; - if (skb_has_frag_list(skb)) - goto out; - len = skb->data_len - offset; - frags = skb_shinfo(skb)->frags; - while (offset) { - if (frags->size > offset) - goto out; - offset -= frags->size; - frags++; - } - while (nr < nr_pages) { - if (len) { - if (len < PAGE_SIZE) - goto out; - if (frags->size != PAGE_SIZE || frags->page_offset) - goto out; - pages_array[nr++] = skb_frag_page(frags); - frags++; - len -= PAGE_SIZE; - seq += PAGE_SIZE; - continue; - } - skb = skb->next; - offset = seq - TCP_SKB_CB(skb)->seq; - goto skb_start; - } - /* OK, we have a full set of pages ready to be inserted into vma */ - for (nr = 0; nr < nr_pages; nr++) { - ret = vm_insert_page(vma, vma->vm_start + (nr << PAGE_SHIFT), - pages_array[nr]); - if (ret) - goto out; - } - /* operation is complete, we can 'consume' all skbs */ - tp->copied_seq = seq; - tcp_rcv_space_adjust(sk); - - /* Clean up data we have read: This will do ACK frames. */ - tcp_recv_skb(sk, seq, &offset); - tcp_cleanup_rbuf(sk, size); + zap_page_range(vma, address, zc->length); + zc->recv_skip_hint = 0; ret = 0; + while (length + PAGE_SIZE <= zc->length) { + if (zc->recv_skip_hint < PAGE_SIZE) { + if (skb) { + skb = skb->next; + offset = seq - TCP_SKB_CB(skb)->seq; + } else { + skb = tcp_recv_skb(sk, seq, &offset); + } + + zc->recv_skip_hint = skb->len - offset; + offset -= skb_headlen(skb); + if ((int)offset < 0 || skb_has_frag_list(skb)) + break; + frags = skb_shinfo(skb)->frags; + while (offset) { + if (frags->size > offset) + goto out; + offset -= frags->size; + frags++; + } + } + if (frags->size != PAGE_SIZE || frags->page_offset) + break; + ret = vm_insert_page(vma, address + length, + skb_frag_page(frags)); + if (ret) + break; + length += PAGE_SIZE; + seq += PAGE_SIZE; + zc->recv_skip_hint -= PAGE_SIZE; + frags++; + } out: - release_sock(sk); - kvfree(pages_array); + up_read(¤t->mm->mmap_sem); + if (length) { + tp->copied_seq = seq; + tcp_rcv_space_adjust(sk); + + /* Clean up data we have read: This will do ACK frames. */ + tcp_recv_skb(sk, seq, &offset); + tcp_cleanup_rbuf(sk, length); + ret = 0; + } else { + if (!zc->recv_skip_hint && sock_flag(sk, SOCK_DONE)) + ret = -EIO; + } + zc->length = length; return ret; } -EXPORT_SYMBOL(tcp_mmap); static void tcp_update_recv_tstamps(struct sk_buff *skb, struct scm_timestamping *tss) @@ -2738,6 +2728,20 @@ static int do_tcp_setsockopt(struct sock *sk, int level, return tcp_fastopen_reset_cipher(net, sk, key, sizeof(key)); } + case TCP_ZEROCOPY_RECEIVE: { + struct tcp_zerocopy_receive zc; + + if (optlen != sizeof(zc)) + return -EINVAL; + if (copy_from_user(&zc, optval, optlen)) + return -EFAULT; + lock_sock(sk); + err = tcp_zerocopy_receive(sk, &zc); + release_sock(sk); + if (!err && copy_to_user(optval, &zc, optlen)) + err = -EFAULT; + return err; + } default: /* fallthru */ break; -- 2.17.0.484.g0c8726318c-goog