Netdev List
 help / color / mirror / Atom feed
From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
To: Antonio Quartulli <antonio@mandelbit.com>,
	 willemdebruijn.kernel@gmail.com,  jasowangio@gmail.com
Cc: Antonio Quartulli <antonio@mandelbit.com>,
	 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: Thu, 12 Nov 2026 18:34:33 -0500	[thread overview]
Message-ID: <willemdebruijn.kernel.1220f3b31f685@gmail.com> (raw)
In-Reply-To: <20261111014208.1795777-3-antonio@mandelbit.com>

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.
 
> 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.


> 
> 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
> 



      reply	other threads:[~2026-11-12 23:34 UTC|newest]

Thread overview: 5+ 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-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 [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=willemdebruijn.kernel.1220f3b31f685@gmail.com \
    --to=willemdebruijn.kernel@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=antonio@mandelbit.com \
    --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 \
    /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