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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox