From: Eric Dumazet <edumazet@google.com>
To: "David S . Miller" <davem@davemloft.net>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>
Cc: Wei Wang <weiwan@google.com>, Simon Horman <horms@kernel.org>,
Cai Xinchen <caixinchen1@huawei.com>,
netdev@vger.kernel.org, edumazet@kernel.org,
Eric Dumazet <edumazet@google.com>
Subject: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
Date: Fri, 25 Sep 2026 13:52:44 +0000 [thread overview]
Message-ID: <20260925135244.3715196-3-edumazet@google.com> (raw)
In-Reply-To: <20260925135244.3715196-1-edumazet@google.com>
Add a kselftest suite covering SO_RESERVE_MEM behavior:
- Reject unsupported socket types (UDP/SOCK_DGRAM, UNIX/SOCK_STREAM,
SOCK_RAW, MPTCP) with EOPNOTSUPP.
- Validate bounds (negative values and values > SZ_1G return EINVAL).
- Verify reserve adjustments (increase, decrease, page rounding, and
reset to 0) and corresponding sk_meminfo[SK_MEMINFO_FWD_ALLOC] changes
via getsockopt(SO_MEMINFO).
- Exercise cgroup v2 memory.max enforcement returning ENOMEM when the
requested reservation exceeds the cgroup limit.
- Verify that a child socket returned by accept() starts with a 0
reserve even when its parent listener configured a non-zero
SO_RESERVE_MEM.
- Verify reserve retention across TCP send/receive/ACK cycles.
Also enable CONFIG_CGROUPS=y and CONFIG_MEMCG=y in
tools/testing/selftests/net/config.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Assisted-by: LLM
---
tools/testing/selftests/net/.gitignore | 1 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 2 +
tools/testing/selftests/net/so_reserve_mem.c | 404 +++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 tools/testing/selftests/net/so_reserve_mem.c
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index c9f46031ac73b20850d068125bb804420d236ad8..a15d7cc4fdcb22b36fdb35b9eaf47e511caa8673 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -41,6 +41,7 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
+so_reserve_mem
so_rcv_listener
stress_reuseport_listen
tap
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 3ee3378f8b26eff9d99a834491ad5ef594dcd782..300207a840c1bcb73a41151c8f94e8f78d01a29b 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -196,6 +196,7 @@ TEST_GEN_PROGS := \
sk_connect_zero_addr \
sk_so_peek_off \
so_incoming_cpu \
+ so_reserve_mem \
tap \
tcp_port_share \
tls \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 737e7e6327b3793984718eb518d3fb79b7870e4d..d355cf98059770bbcdcda1a33d8e84bc390eb26a 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -7,6 +7,7 @@ CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_CAN=m
CONFIG_CAN_DEV=m
CONFIG_CAN_VXCAN=m
+CONFIG_CGROUPS=y
CONFIG_CRYPTO_ARIA=y
CONFIG_CRYPTO_CHACHA20POLY1305=m
CONFIG_CRYPTO_SHA1=y
@@ -61,6 +62,7 @@ CONFIG_L2TP_V3=y
CONFIG_MACSEC=m
CONFIG_MACVLAN=y
CONFIG_MACVTAP=y
+CONFIG_MEMCG=y
CONFIG_MPLS=y
CONFIG_MPLS_IPTUNNEL=m
CONFIG_MPLS_ROUTING=m
diff --git a/tools/testing/selftests/net/so_reserve_mem.c b/tools/testing/selftests/net/so_reserve_mem.c
new file mode 100644
index 0000000000000000000000000000000000000000..920aeccc0ddf780e1b7ffd2172cf8bc2a01320f1
--- /dev/null
+++ b/tools/testing/selftests/net/so_reserve_mem.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/sock_diag.h>
+#include <netinet/in.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+#ifndef IPPROTO_MPTCP
+#define IPPROTO_MPTCP 262
+#endif
+
+#define SO_RESERVE_MEM_MAX (1 << 30)
+
+static int get_reserve_mem(struct __test_metadata *_metadata, int fd)
+{
+ int val = -1;
+ socklen_t len = sizeof(val);
+
+ EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, &len), 0);
+ return val;
+}
+
+static __u32 get_fwd_alloc(struct __test_metadata *_metadata, int fd)
+{
+ __u32 meminfo[SK_MEMINFO_VARS] = {};
+ socklen_t len = sizeof(meminfo);
+
+ EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
+ return meminfo[SK_MEMINFO_FWD_ALLOC];
+}
+
+static void wait_wmem_drained(struct __test_metadata *_metadata, int fd)
+{
+ __u32 meminfo[SK_MEMINFO_VARS] = {};
+ socklen_t len;
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ len = sizeof(meminfo);
+ ASSERT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
+ if (meminfo[SK_MEMINFO_WMEM_QUEUED] == 0)
+ return;
+ usleep(1000);
+ }
+ EXPECT_EQ(meminfo[SK_MEMINFO_WMEM_QUEUED], 0U);
+}
+
+FIXTURE(so_reserve_mem)
+{
+ char cg_root[64];
+ char cg_child[128];
+ long page_size;
+ bool cg_mounted;
+ bool restore_subtree_ctrl;
+};
+
+FIXTURE_TEARDOWN(so_reserve_mem)
+{
+ char path[160];
+ int fd;
+
+ if (!self->cg_mounted)
+ return;
+
+ snprintf(path, sizeof(path), "%s/cgroup.procs", self->cg_root);
+ fd = open(path, O_WRONLY);
+ if (fd >= 0) {
+ dprintf(fd, "%d\n", getpid());
+ close(fd);
+ }
+ if (self->cg_child[0]) {
+ rmdir(self->cg_child);
+ self->cg_child[0] = '\0';
+ }
+ if (self->restore_subtree_ctrl) {
+ snprintf(path, sizeof(path), "%s/cgroup.subtree_control",
+ self->cg_root);
+ fd = open(path, O_WRONLY);
+ if (fd >= 0) {
+ if (write(fd, "-memory", 7) < 0)
+ ;
+ close(fd);
+ }
+ self->restore_subtree_ctrl = false;
+ }
+ umount2(self->cg_root, MNT_DETACH);
+ rmdir(self->cg_root);
+ self->cg_mounted = false;
+}
+
+FIXTURE_SETUP(so_reserve_mem)
+{
+ char procs_path[160], ctrl_path[160], ctrl_buf[256] = {};
+ int fd, ret, val = 0;
+
+ self->page_size = sysconf(_SC_PAGESIZE);
+ ASSERT_GT(self->page_size, 0);
+
+ if (unshare(CLONE_NEWNS))
+ SKIP(return, "Failed to unshare mount namespace (need root)");
+
+ mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+
+ snprintf(self->cg_root, sizeof(self->cg_root),
+ "/tmp/ksft_so_reserve_XXXXXX");
+ ASSERT_NE(mkdtemp(self->cg_root), NULL);
+
+ if (mount("none", self->cg_root, "cgroup2", 0, NULL)) {
+ rmdir(self->cg_root);
+ SKIP(return, "Failed to mount cgroup2 (need root)");
+ }
+ self->cg_mounted = true;
+
+ snprintf(ctrl_path, sizeof(ctrl_path), "%s/cgroup.subtree_control",
+ self->cg_root);
+ fd = open(ctrl_path, O_RDWR);
+ if (fd < 0) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "Failed to open cgroup.subtree_control");
+ }
+ if (read(fd, ctrl_buf, sizeof(ctrl_buf) - 1) < 0) {
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "Failed to read cgroup.subtree_control");
+ }
+ if (!strstr(ctrl_buf, "memory")) {
+ if (write(fd, "+memory", 7) != 7) {
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "cgroup2 memory controller not available");
+ }
+ self->restore_subtree_ctrl = true;
+ }
+ close(fd);
+
+ snprintf(self->cg_child, sizeof(self->cg_child), "%s/test_%d",
+ self->cg_root, getpid());
+ if (mkdir(self->cg_child, 0755)) {
+ self->cg_child[0] = '\0';
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_TRUE(false);
+ }
+
+ snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
+ self->cg_child);
+ fd = open(procs_path, O_WRONLY);
+ if (fd < 0 || dprintf(fd, "%d\n", getpid()) <= 0) {
+ if (fd >= 0)
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_TRUE(false);
+ }
+ close(fd);
+
+ /* Verify memcg socket accounting is enabled on this kernel */
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_GE(fd, 0);
+ }
+ ret = setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val));
+ close(fd);
+ if (ret && errno == EOPNOTSUPP) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "memcg socket accounting not enabled");
+ }
+ if (ret) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_EQ(ret, 0);
+ }
+}
+
+static void check_non_tcp_rejected(struct __test_metadata *_metadata,
+ int domain, int type, int protocol,
+ int val)
+{
+ int fd = socket(domain, type, protocol);
+
+ if (fd < 0) {
+ EXPECT_TRUE(errno == EAFNOSUPPORT ||
+ errno == EPROTONOSUPPORT ||
+ errno == ENOPROTOOPT);
+ return;
+ }
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EOPNOTSUPP);
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, non_tcp_rejected)
+{
+ int val = self->page_size * 4;
+
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_DGRAM, 0, val);
+ check_non_tcp_rejected(_metadata, AF_UNIX, SOCK_STREAM, 0, val);
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_RAW, IPPROTO_ICMP, val);
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_STREAM, IPPROTO_MPTCP, val);
+}
+
+TEST_F(so_reserve_mem, grow_shrink_and_rounding)
+{
+ int ps = self->page_size;
+ int fd, val;
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(fd, 0);
+
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
+
+ /* Negative or > SO_RESERVE_MEM_MAX value -> EINVAL */
+ val = -1;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ val = SO_RESERVE_MEM_MAX + 1;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ val = INT_MAX;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* 1 byte rounds up to 1 page */
+ val = 1;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)ps);
+
+ /* Grow to 16 pages */
+ val = 16 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
+
+ /* Shrink by 1 byte (rounds delta down to 0 -> stays 16 pages) */
+ val = 16 * ps - 1;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
+
+ /* Shrink to 4 pages */
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(4 * ps));
+
+ /* Release all */
+ val = 0;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
+
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, cgroup_memory_max)
+{
+ char max_path[160], procs_path[160];
+ int fd, max_fd, procs_fd, val;
+ int ps = self->page_size;
+
+ snprintf(max_path, sizeof(max_path), "%s/memory.max", self->cg_child);
+ max_fd = open(max_path, O_WRONLY | O_NONBLOCK);
+ if (max_fd < 0)
+ SKIP(return, "cgroup memory controller not delegated");
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(fd, 0);
+
+ /* Move test process back to root cgroup before lowering cg_child's
+ * memory.max so the limit only governs the socket's memcg charges
+ * and cannot trigger OOM on the test process itself.
+ */
+ snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
+ self->cg_root);
+ procs_fd = open(procs_path, O_WRONLY);
+ ASSERT_GE(procs_fd, 0);
+ ASSERT_GT(dprintf(procs_fd, "%d\n", getpid()), 0);
+ close(procs_fd);
+
+ /* Limit cg_child memory to 8 pages and try to reserve 128 pages */
+ ASSERT_GT(dprintf(max_fd, "%ld\n", 8L * ps), 0);
+
+ val = 128 * ps;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, ENOMEM);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+
+ /* Restore unlimited memory.max */
+ ASSERT_GT(dprintf(max_fd, "max\n"), 0);
+ close(max_fd);
+
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
+
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, accept_child_zero_reserve)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ socklen_t alen = sizeof(addr);
+ int ps = self->page_size;
+ int lfd, cfd, sfd, val;
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(lfd, 0);
+
+ /* Set SO_RESERVE_MEM on listener before listen() */
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+ ASSERT_EQ(listen(lfd, 2), 0);
+ ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
+
+ /* Grow SO_RESERVE_MEM on listener after listen() */
+ val = 8 * ps;
+ ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
+
+ cfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(cfd, 0);
+ ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+
+ sfd = accept(lfd, NULL, NULL);
+ ASSERT_GE(sfd, 0);
+
+ /* Child after accept() must have 0 reserve while listener keeps 8 pages */
+ EXPECT_EQ(get_reserve_mem(_metadata, sfd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
+ EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
+
+ /* Child can still independently set its own SO_RESERVE_MEM */
+ val = 6 * ps;
+ ASSERT_EQ(setsockopt(sfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, sfd), 6 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), (__u32)(6 * ps));
+
+ close(sfd);
+ close(cfd);
+ close(lfd);
+}
+
+TEST_F(so_reserve_mem, preserved_after_traffic)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ socklen_t alen = sizeof(addr);
+ int ps = self->page_size;
+ int lfd, cfd, sfd, val;
+ char buf[8192] = {};
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(lfd, 0);
+ ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+ ASSERT_EQ(listen(lfd, 1), 0);
+ ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
+
+ cfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(cfd, 0);
+ val = 16 * ps;
+ ASSERT_EQ(setsockopt(cfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+
+ sfd = accept(lfd, NULL, NULL);
+ ASSERT_GE(sfd, 0);
+
+ /* Send & drain traffic; cfd must retain its 16-page forward alloc,
+ * while sfd (0 reserve) reclaims its forward alloc back to 0.
+ */
+ ASSERT_EQ(send(cfd, buf, sizeof(buf), 0), (ssize_t)sizeof(buf));
+ ASSERT_EQ(recv(sfd, buf, sizeof(buf), MSG_WAITALL), (ssize_t)sizeof(buf));
+ wait_wmem_drained(_metadata, cfd);
+
+ EXPECT_GE(get_fwd_alloc(_metadata, cfd), (__u32)(16 * ps));
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
+
+ close(sfd);
+ close(cfd);
+ close(lfd);
+}
+
+TEST_HARNESS_MAIN
--
2.56.0.rc1.315.gc6ed9934b7-goog
next prev parent reply other threads:[~2026-09-25 13:52 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 13:52 [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
2026-09-28 17:23 ` Wei Wang
2026-09-25 13:52 ` Eric Dumazet [this message]
2026-09-28 17:22 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Wei Wang
2026-09-28 18:27 ` Eric Dumazet
2026-09-28 19:54 ` netdev-bot+sashiko
2026-09-28 20:03 ` Eric Dumazet
2026-09-28 20:18 ` Wei Wang
2026-09-29 2:40 ` [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260925135244.3715196-3-edumazet@google.com \
--to=edumazet@google.com \
--cc=caixinchen1@huawei.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=weiwan@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.