From: Antonio Quartulli <antonio@mandelbit.com>
To: Willem de Bruijn <willemdebruijn.kernel@gmail.com>, jasowangio@gmail.com
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@kernel.org,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
mst@redhat.com, eperezma@redhat.com, shuah@kernel.org,
ast@kernel.org, daniel@iogearbox.net, hawk@kernel.org,
john.fastabend@gmail.com, sdf@fomichev.me, maheshb@google.com,
peterpenkov96@gmail.com, netdev@vger.kernel.org,
virtualization@lists.linux.dev, linux-kselftest@vger.kernel.org,
bpf@vger.kernel.org
Subject: Re: [PATCH net v5 2/2] selftests: net: add a vhost-net test for the tun NAPI queue bound
Date: Fri, 13 Nov 2026 01:33:54 +0100 [thread overview]
Message-ID: <73aa2f53-e2ba-42b7-9712-06e223459144@mandelbit.com> (raw)
In-Reply-To: <willemdebruijn.kernel.1220f3b31f685@gmail.com>
On 13/11/2026 00:34, Willem de Bruijn wrote:
> Antonio Quartulli wrote:
>> Send 16k packets from vhost-net into a tap device with IFF_NAPI and
>> netdev_max_backlog set to 8, in the three ways vhost-net passes packets
>> to tun: batches of XDP buffers, one packet per sendmsg() with MSG_MORE,
>> and zerocopy buffers. Check that excess packets are dropped and counted,
>> that the queue keeps draining while vhost-net keeps sending, that every
>> packet is either delivered or dropped, and that every buffer comes back
>> to the ring. If a dropped zerocopy buffer is never completed, the test
>> fails, and closing vhost-net then blocks until the buffer is released.
>>
>> The zerocopy variant needs vhost_net loaded with experimental_zcopytx
>> set and is skipped otherwise; with it, vhost-net does not batch and the
>> batch variant is skipped instead.
>
> That means it will be skipped as part of kselftests.
>
> And as a misc device all tests may be skipped in minimal environments
> if vhost-net is not autoloaded.
>
> Maybe a .sh wrapper could resolve both.
Right. With a wrapper script we should be able to deal with these.
>
>> The test drives a minimal split virtqueue itself, in little-endian as
>> VIRTIO_F_VERSION_1 requires. It needs root and CONFIG_VHOST_NET.
>> netdev_max_backlog is global: the test lowers it only once setup has
>> succeeded and restores it first in teardown, and should not run in
>> parallel with other tests.
>>
>> Written and tested with the help of an AI coding assistant (Claude
>> Code).
>>
>> Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
>> ---
>> tools/testing/selftests/net/.gitignore | 1 +
>> tools/testing/selftests/net/Makefile | 1 +
>> tools/testing/selftests/net/config | 1 +
>> tools/testing/selftests/net/tun_vhost.c | 409 ++++++++++++++++++++++++
>> 4 files changed, 412 insertions(+)
>> create mode 100644 tools/testing/selftests/net/tun_vhost.c
>
> That's a lot of code for a fairly narrow test.
>
> Not that long ago I would have applauded all test coverage we can get.
> But now that LLMs generate them instantly, not sure where to draw the
> line.
Well, the test has already done its job because it immediately helped me
fix some small itches in patch 1.
This said, I can definitely shrink this test a bit, maybe around 50% of
the current code. But it'll still be quite some code for a small fix.
Should I just drop this patch?
Alternatively, I drop it from this series and I send the shrunk-down
version with the wrapper script to net-next later on.
Opinions?
Thanks a lot!
Regards,
>
>
>>
>> diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
>> index 21412510ac26..aafd07910058 100644
>> --- a/tools/testing/selftests/net/.gitignore
>> +++ b/tools/testing/selftests/net/.gitignore
>> @@ -54,6 +54,7 @@ timestamping
>> tls
>> tools
>> tun
>> +tun_vhost
>> txring_overwrite
>> txtimestamp
>> udp_splice_checksum
>> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
>> index 9180b9bec515..babb684d6f77 100644
>> --- a/tools/testing/selftests/net/Makefile
>> +++ b/tools/testing/selftests/net/Makefile
>> @@ -202,6 +202,7 @@ TEST_GEN_PROGS := \
>> tap \
>> tcp_port_share \
>> tls \
>> + tun_vhost \
>> udp_splice_checksum \
>> # end of TEST_GEN_PROGS
>>
>> diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
>> index d4416f8bdcfd..9afa82dea11d 100644
>> --- a/tools/testing/selftests/net/config
>> +++ b/tools/testing/selftests/net/config
>> @@ -135,6 +135,7 @@ CONFIG_TRACEPOINTS=y
>> CONFIG_TUN=y
>> CONFIG_USER_NS=y
>> CONFIG_VETH=y
>> +CONFIG_VHOST_NET=m
>> CONFIG_VLAN_8021Q=y
>> CONFIG_VXLAN=m
>> CONFIG_XFRM_INTERFACE=m
>> diff --git a/tools/testing/selftests/net/tun_vhost.c b/tools/testing/selftests/net/tun_vhost.c
>> new file mode 100644
>> index 000000000000..ed35435df90e
>> --- /dev/null
>> +++ b/tools/testing/selftests/net/tun_vhost.c
>> @@ -0,0 +1,409 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Send packets from vhost-net into a tap device with IFF_NAPI and check
>> + * that the NAPI queue is bounded by netdev_max_backlog: excess packets
>> + * are dropped and counted, and every buffer is returned to the driver,
>> + * zerocopy ones included.
>> + *
>> + * vhost-net passes packets to tun in three ways:
>> + * - batch: batches of XDP buffers (tun_xdp_one()), the default when the
>> + * socket send buffer is unlimited and zerocopy is disabled
>> + * - copy: one packet per sendmsg(), when the send buffer is limited
>> + * - zerocopy: one packet per sendmsg() with a ubuf_info to complete,
>> + * only with vhost_net experimental_zcopytx set (else skipped)
>> + *
>> + * Tun schedules NAPI after a batch, after the last of a series of MSG_MORE
>> + * packets, once more than NAPI_POLL_WEIGHT (64) packets are queued, and
>> + * when it drops a packet because the queue is full. With netdev_max_backlog
>> + * below 64, the queue always overflows and only the last of these keeps it
>> + * draining while the sender keeps sending.
>> + *
>> + * netdev_max_backlog is global: the test lowers it for the whole system
>> + * while it runs, so it should not run in parallel with other tests.
>> + *
>> + * Author: Antonio Quartulli <antonio@mandelbit.com>
>> + */
>> +
>> +#define _GNU_SOURCE
>> +#include <endian.h>
>> +#include <errno.h>
>> +#include <fcntl.h>
>> +#include <poll.h>
>> +#include <sched.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <unistd.h>
>> +#include <linux/if.h>
>> +#include <linux/if_arp.h>
>> +#include <linux/if_ether.h>
>> +#include <linux/if_tun.h>
>> +#include <linux/vhost.h>
>> +#include <linux/virtio_config.h>
>> +#include <linux/virtio_net.h>
>> +#include <linux/virtio_ring.h>
>> +#include <sys/eventfd.h>
>> +#include <sys/ioctl.h>
>> +#include <sys/socket.h>
>> +
>> +#include "kselftest_harness.h"
>> +
>> +#define TV_IFNAME "tvh0"
>> +#define TV_RING_NUM 256
>> +#define TV_BUF_SIZE 2048
>> +#define TV_HDR_LEN sizeof(struct virtio_net_hdr_v1)
>> +#define TV_PKTS (64 * TV_RING_NUM)
>> +#define TV_BACKLOG "8"
>> +#define TV_TX_VQ 1
>> +
>> +static const unsigned char tv_mac_tap[ETH_ALEN] = { 2, 0, 0, 0, 0, 1 };
>> +static const unsigned char tv_mac_dst[ETH_ALEN] = { 2, 0, 0, 0, 0, 2 };
>> +static const unsigned char tv_mac_src[ETH_ALEN] = { 2, 0, 0, 0, 0, 3 };
>> +
>> +/* TX virtqueue, little-endian as negotiated with VIRTIO_F_VERSION_1 */
>> +struct tv_ring {
>> + void *mem;
>> + struct vring vr;
>> + char *bufs;
>> + int kick;
>> + int call;
>> + unsigned short avail_idx;
>> + unsigned short used_idx;
>> + /* descriptors not owned by vhost: buffers may come back in any order */
>> + unsigned short free[TV_RING_NUM];
>> + int nfree;
>> +};
>> +
>> +struct tv_stats {
>> + unsigned long long rx_packets;
>> + unsigned long long rx_dropped;
>> +};
>> +
>> +static int tv_read_stats(const char *ifname, struct tv_stats *st)
>> +{
>> + char line[512], *p;
>> + FILE *f;
>> + int ret = -1;
>> +
>> + f = fopen("/proc/net/dev", "r");
>> + if (!f)
>> + return -1;
>> + while (fgets(line, sizeof(line), f)) {
>> + p = strchr(line, ':');
>> + if (!p)
>> + continue;
>> + *p = '\0';
>> + if (strcmp(line + strspn(line, " "), ifname))
>> + continue;
>> + if (sscanf(p + 1, "%*u %llu %*u %llu", &st->rx_packets,
>> + &st->rx_dropped) == 2)
>> + ret = 0;
>> + break;
>> + }
>> + fclose(f);
>> + return ret;
>> +}
>> +
>> +FIXTURE(tun_vhost)
>> +{
>> + int backlog_fd;
>> + char backlog[32];
>> + int tap;
>> + int vhost;
>> + struct tv_ring ring;
>> +};
>> +
>> +FIXTURE_VARIANT(tun_vhost)
>> +{
>> + bool batch;
>> + bool limit_sndbuf;
>> + bool zerocopy;
>> + int pktlen;
>> +};
>> +
>> +FIXTURE_VARIANT_ADD(tun_vhost, batch)
>> +{
>> + .batch = true,
>> + .pktlen = 64,
>> +};
>> +
>> +FIXTURE_VARIANT_ADD(tun_vhost, copy)
>> +{
>> + .limit_sndbuf = true,
>> + .pktlen = 64,
>> +};
>> +
>> +FIXTURE_VARIANT_ADD(tun_vhost, zerocopy)
>> +{
>> + .limit_sndbuf = true,
>> + .zerocopy = true,
>> + .pktlen = 1000,
>> +};
>> +
>> +/* vhost-net uses zerocopy for any non-zero value */
>> +static bool tv_zerocopy_enabled(void)
>> +{
>> + char buf[16] = {};
>> + int fd;
>> +
>> + fd = open("/sys/module/vhost_net/parameters/experimental_zcopytx",
>> + O_RDONLY);
>> + if (fd < 0)
>> + return false;
>> + if (read(fd, buf, sizeof(buf) - 1) <= 0)
>> + buf[0] = '\0';
>> + close(fd);
>> + return atoi(buf) != 0;
>> +}
>> +
>> +static int tv_vhost_setup(int vhost, int tap, struct tv_ring *r)
>> +{
>> + struct vhost_vring_state state = { .index = TV_TX_VQ };
>> + struct vhost_vring_file file = { .index = TV_TX_VQ };
>> + struct vhost_vring_addr addr = { .index = TV_TX_VQ };
>> + size_t memsz = sizeof(struct vhost_memory) +
>> + sizeof(struct vhost_memory_region);
>> + unsigned long long features;
>> + struct vhost_memory *mem;
>> + int ret;
>> +
>> + if (ioctl(vhost, VHOST_SET_OWNER, NULL) ||
>> + ioctl(vhost, VHOST_GET_FEATURES, &features) ||
>> + !(features & (1ULL << VIRTIO_F_VERSION_1)))
>> + return -1;
>> + features = 1ULL << VIRTIO_F_VERSION_1;
>> + if (ioctl(vhost, VHOST_SET_FEATURES, &features))
>> + return -1;
>> +
>> + /* buffer addresses are passed as is: map them 1:1 */
>> + mem = calloc(1, memsz);
>> + if (!mem)
>> + return -1;
>> + mem->nregions = 1;
>> + mem->regions[0].guest_phys_addr = (unsigned long)r->bufs;
>> + mem->regions[0].userspace_addr = (unsigned long)r->bufs;
>> + mem->regions[0].memory_size = TV_RING_NUM * TV_BUF_SIZE;
>> + ret = ioctl(vhost, VHOST_SET_MEM_TABLE, mem);
>> + free(mem);
>> + if (ret)
>> + return -1;
>> +
>> + state.num = TV_RING_NUM;
>> + if (ioctl(vhost, VHOST_SET_VRING_NUM, &state))
>> + return -1;
>> + state.num = 0;
>> + if (ioctl(vhost, VHOST_SET_VRING_BASE, &state))
>> + return -1;
>> + addr.desc_user_addr = (unsigned long)r->vr.desc;
>> + addr.avail_user_addr = (unsigned long)r->vr.avail;
>> + addr.used_user_addr = (unsigned long)r->vr.used;
>> + if (ioctl(vhost, VHOST_SET_VRING_ADDR, &addr))
>> + return -1;
>> + file.fd = r->kick;
>> + if (ioctl(vhost, VHOST_SET_VRING_KICK, &file))
>> + return -1;
>> + file.fd = r->call;
>> + if (ioctl(vhost, VHOST_SET_VRING_CALL, &file))
>> + return -1;
>> + file.fd = tap;
>> + return ioctl(vhost, VHOST_NET_SET_BACKEND, &file);
>> +}
>> +
>> +static int tv_ring_init(struct tv_ring *r)
>> +{
>> + int i;
>> +
>> + if (posix_memalign((void **)&r->bufs, 4096, TV_RING_NUM * TV_BUF_SIZE))
>> + return -1;
>> + memset(r->bufs, 0, TV_RING_NUM * TV_BUF_SIZE);
>> +
>> + /* every buffer: zeroed vnet header + IPv4 frame for another host,
>> + * which the stack drops without counting it in rx_dropped
>> + */
>> + for (i = 0; i < TV_RING_NUM; i++) {
>> + unsigned char *eth = (unsigned char *)r->bufs +
>> + i * TV_BUF_SIZE + TV_HDR_LEN;
>> +
>> + memcpy(eth, tv_mac_dst, ETH_ALEN);
>> + memcpy(eth + ETH_ALEN, tv_mac_src, ETH_ALEN);
>> + eth[12] = ETH_P_IP >> 8;
>> + eth[13] = ETH_P_IP & 0xff;
>> + eth[14] = 0x45;
>> + r->free[i] = i;
>> + }
>> + r->nfree = TV_RING_NUM;
>> +
>> + if (posix_memalign(&r->mem, 4096, vring_size(TV_RING_NUM, 4096)))
>> + return -1;
>> + memset(r->mem, 0, vring_size(TV_RING_NUM, 4096));
>> + vring_init(&r->vr, TV_RING_NUM, r->mem, 4096);
>> +
>> + r->kick = eventfd(0, EFD_NONBLOCK);
>> + r->call = eventfd(0, EFD_NONBLOCK);
>> + return r->kick < 0 || r->call < 0 ? -1 : 0;
>> +}
>> +
>> +/* save the global netdev_max_backlog, to restore it in teardown */
>> +static int tv_backlog_save(int *fd, char *old, size_t size)
>> +{
>> + ssize_t n;
>> +
>> + /* only visible from init_net: keep it open across unshare() */
>> + *fd = open("/proc/sys/net/core/netdev_max_backlog", O_RDWR);
>> + if (*fd < 0)
>> + return -1;
>> + n = read(*fd, old, size - 1);
>> + if (n <= 0)
>> + return -1;
>> + old[n] = '\0';
>> + return 0;
>> +}
>> +
>> +FIXTURE_SETUP(tun_vhost)
>> +{
>> + struct ifreq ifr = {};
>> + int hdrlen = TV_HDR_LEN;
>> + int sndbuf = 1 << 20;
>> + int fd;
>> +
>> + self->backlog_fd = -1;
>> + self->vhost = open("/dev/vhost-net", O_RDWR);
>> + if (self->vhost < 0)
>> + SKIP(return, "no /dev/vhost-net");
>> + if (variant->zerocopy && !tv_zerocopy_enabled())
>> + SKIP(return, "vhost_net experimental_zcopytx not enabled");
>> + if (variant->batch && tv_zerocopy_enabled())
>> + SKIP(return, "vhost_net does not batch with zerocopy");
>> +
>> + ASSERT_EQ(tv_backlog_save(&self->backlog_fd, self->backlog,
>> + sizeof(self->backlog)), 0);
>> + ASSERT_EQ(unshare(CLONE_NEWNET), 0);
>> +
>> + self->tap = open("/dev/net/tun", O_RDWR);
>> + ASSERT_GE(self->tap, 0);
>> + ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR | IFF_NAPI;
>> + memcpy(ifr.ifr_name, TV_IFNAME, sizeof(TV_IFNAME));
>> + ASSERT_EQ(ioctl(self->tap, TUNSETIFF, &ifr), 0);
>> + ASSERT_EQ(ioctl(self->tap, TUNSETVNETHDRSZ, &hdrlen), 0);
>> + if (variant->limit_sndbuf)
>> + ASSERT_EQ(ioctl(self->tap, TUNSETSNDBUF, &sndbuf), 0);
>> +
>> + fd = socket(AF_INET, SOCK_DGRAM, 0);
>> + ASSERT_GE(fd, 0);
>> + ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
>> + memcpy(ifr.ifr_hwaddr.sa_data, tv_mac_tap, ETH_ALEN);
>> + ASSERT_EQ(ioctl(fd, SIOCSIFHWADDR, &ifr), 0);
>> + ASSERT_EQ(ioctl(fd, SIOCGIFFLAGS, &ifr), 0);
>> + ifr.ifr_flags |= IFF_UP;
>> + ASSERT_EQ(ioctl(fd, SIOCSIFFLAGS, &ifr), 0);
>> + close(fd);
>> +
>> + ASSERT_EQ(tv_ring_init(&self->ring), 0);
>> + ASSERT_EQ(tv_vhost_setup(self->vhost, self->tap, &self->ring), 0);
>> +}
>> +
>> +FIXTURE_TEARDOWN(tun_vhost)
>> +{
>> + /* restore first: closing vhost may block if buffers are not returned */
>> + if (self->backlog_fd >= 0) {
>> + if (pwrite(self->backlog_fd, self->backlog,
>> + strlen(self->backlog), 0) < 0)
>> + TH_LOG("cannot restore netdev_max_backlog to %s",
>> + self->backlog);
>> + close(self->backlog_fd);
>> + }
>> + close(self->vhost);
>> + close(self->tap);
>> +}
>> +
>> +/* queue up to @n buffers, return how many */
>> +static int tv_post(struct tv_ring *r, int pktlen, int n)
>> +{
>> + unsigned long long one = 1;
>> + int i;
>> +
>> + for (i = 0; i < n && r->nfree; i++) {
>> + unsigned short id = r->free[--r->nfree];
>> +
>> + r->vr.desc[id].addr = htole64((unsigned long)r->bufs +
>> + id * TV_BUF_SIZE);
>> + r->vr.desc[id].len = htole32(TV_HDR_LEN + pktlen);
>> + r->vr.desc[id].flags = 0;
>> + r->vr.avail->ring[r->avail_idx % TV_RING_NUM] = htole16(id);
>> + r->avail_idx++;
>> + }
>> + __atomic_store_n(&r->vr.avail->idx, htole16(r->avail_idx),
>> + __ATOMIC_RELEASE);
>> + if (i && write(r->kick, &one, sizeof(one)) != sizeof(one))
>> + return -1;
>> + return i;
>> +}
>> +
>> +/* reap returned buffers, waiting up to @ms for at least one */
>> +static int tv_reap(struct tv_ring *r, int ms)
>> +{
>> + struct pollfd pfd = { .fd = r->call, .events = POLLIN };
>> + unsigned short used;
>> + unsigned long long v;
>> + int n = 0;
>> +
>> + for (;;) {
>> + used = le16toh(__atomic_load_n(&r->vr.used->idx,
>> + __ATOMIC_ACQUIRE));
>> + while (r->used_idx != used) {
>> + struct vring_used_elem *e;
>> +
>> + e = &r->vr.used->ring[r->used_idx % TV_RING_NUM];
>> + r->free[r->nfree++] = le32toh(e->id);
>> + r->used_idx++;
>> + n++;
>> + }
>> + if (n)
>> + return n;
>> + if (ms <= 0 || poll(&pfd, 1, 100) < 0)
>> + return 0;
>> + if (pfd.revents & POLLIN && read(r->call, &v, sizeof(v)) < 0)
>> + return -1;
>> + ms -= 100;
>> + }
>> +}
>> +
>> +TEST_F(tun_vhost, napi_queue_bounded)
>> +{
>> + struct tv_stats before, after;
>> + unsigned long long delivered, dropped;
>> + int posted = 0, reaped = 0, n;
>> +
>> + ASSERT_EQ(tv_read_stats(TV_IFNAME, &before), 0);
>> + /* lowered only now, so that a failed setup leaves it alone */
>> + ASSERT_EQ(pwrite(self->backlog_fd, TV_BACKLOG, strlen(TV_BACKLOG), 0),
>> + strlen(TV_BACKLOG));
>> +
>> + while (reaped < TV_PKTS) {
>> + n = tv_post(&self->ring, variant->pktlen, TV_PKTS - posted);
>> + ASSERT_GE(n, 0);
>> + posted += n;
>> + n = tv_reap(&self->ring, 5000);
>> + ASSERT_GE(n, 0);
>> + if (!n) {
>> + TH_LOG("%d of %d buffers not returned",
>> + posted - reaped, posted);
>> + ASSERT_GT(n, 0);
>> + }
>> + reaped += n;
>> + }
>> +
>> + ASSERT_EQ(tv_read_stats(TV_IFNAME, &after), 0);
>> + delivered = after.rx_packets - before.rx_packets;
>> + dropped = after.rx_dropped - before.rx_dropped;
>> + TH_LOG("sent %d, delivered %llu, dropped %llu", TV_PKTS, delivered,
>> + dropped);
>> + /* bounded, */
>> + EXPECT_GT(dropped, 0);
>> + /* still draining, */
>> + EXPECT_GE(delivered, TV_PKTS / 64);
>> + /* and every packet accounted for */
>> + EXPECT_EQ(delivered + dropped, TV_PKTS);
>> +}
>> +
>> +TEST_HARNESS_MAIN
>> --
>> 2.56.0
>>
>
--
Antonio Quartulli
CEO and Co-Founder
Mandelbit Srl
https://www.mandelbit.com
prev parent reply other threads:[~2026-11-13 0:34 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 1:42 [PATCH net v5 0/2] tun: bound the NAPI receive queue Antonio Quartulli
2026-11-11 1:42 ` [PATCH net v5 1/2] " Antonio Quartulli
2026-11-12 23:28 ` Willem de Bruijn
2026-11-13 0:29 ` Antonio Quartulli
2026-11-11 1:42 ` [PATCH net v5 2/2] selftests: net: add a vhost-net test for the tun NAPI queue bound Antonio Quartulli
2026-11-12 23:34 ` Willem de Bruijn
2026-11-13 0:33 ` Antonio Quartulli [this message]
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=73aa2f53-e2ba-42b7-9712-06e223459144@mandelbit.com \
--to=antonio@mandelbit.com \
--cc=andrew+netdev@lunn.ch \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=eperezma@redhat.com \
--cc=hawk@kernel.org \
--cc=horms@kernel.org \
--cc=jasowangio@gmail.com \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=maheshb@google.com \
--cc=mst@redhat.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=peterpenkov96@gmail.com \
--cc=sdf@fomichev.me \
--cc=shuah@kernel.org \
--cc=virtualization@lists.linux.dev \
--cc=willemdebruijn.kernel@gmail.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