On Wed, Aug 12, 2026 at 09:17:30PM +0300, Ammar Yasser wrote: > On Mon Aug 10, 2026 at 10:34 AM EEST, David Gibson wrote: > > On Sun, Aug 02, 2026 at 01:21:53PM +0000, Ammar Yasser wrote: > >> Add a new function called tap_vhost_input that will be used to indicate > >> that the guest is trying to send data to us through the rx queue and the > >> kernel is informing us to handle this data. > >> > >> We will however still recieve epoll events on the fd normally. We should > > > > "the fd" is not very clear here, there are a bunch of fds. I'm > > guessing you mean the character device fd? > > Yeah. And tying it back to your comment about the fact that we > shouldn't call epoll add with the device fd if the user requested vhost > means that this comment will be redundant anyways, will delete it. > > For the record, while debugging performance, the fact that we received a > double kick on the device fd and the queue fd was a major slow down > reason. I did remove epoll registration for the device in this > experimental branch. Will include it in my next revision. Oh, excellent :). > > >> @@ -1359,7 +1362,8 @@ void tap_handler_pasta(struct ctx *c, uint32_t events, > >> if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) > >> die("Disconnect event on /dev/net/tun device, exiting"); > >> > >> - if (events & EPOLLIN) > >> + /* don't proceed with the normal tap processing in case vhost acceleration was required */ > >> + if (events & EPOLLIN && !c->vhost) > > > > Seems like it would be more elegant to remove the character device fd > > from the epoll set (or at least remove EPOLLIN) when vhost is > > activated, rather than take it's events then ignore the. > > Yeah, agreed as per my comment above. > > > > >> tap_pasta_input(c, now); > >> } > >> > >> @@ -1514,6 +1518,90 @@ void tap_listen_handler(struct ctx *c, uint32_t events) > >> tap_start_connection(c); > >> } > >> > >> +/** > >> + * consume_one_rx_descriptor() - Consume one used RX descriptor from the kernel > > > > I'd suggest avoiding "Rx" or "Tx" terminology throughout, using > > "toguest" or "fromguest" instead. Note that for extra confusion Rx > > here is meaning "fromguest", but for vhost-user it means "toguest". > > Noted > > > > >> + * @len: Set to the length of data written by the kernel > >> + * > >> + * Pops a single entry from the used ring. Advances vqs[0].last_used_idx > >> + * (the number of entries we have consumed) and vqs[0].num_free (the count > >> + * of descriptors awaiting refill announcement). > >> + * > >> + * NOTE: This function assumes the kernel is going to post single descriptors > >> + * always, No chains. If that changes, we would need to increment num_free > >> + * as we advertise back to the kernel the free descriptors by the length of the chain. > >> + * > >> + * Return: pointer to the packet buffer, or NULL if no data is available > >> + */ > >> +static void *consume_one_rx_descriptor(unsigned *len) > > > > For a length of data, we'd typically use a size_t rather than a bare > > unsigned. > > Noted > > >> + /* read the last index we consumed */ > >> + last_used = vqs[0].last_used_idx % VHOST_NDESCS; > >> + /* read the index of what the */ > >> + i = le32toh(used->ring[last_used].id); > >> + *len = le32toh(used->ring[last_used].len); > >> + > >> + if (i != last_used) { > >> + die("vhost: id %u at used position %u != %u", i, last_used, i); > >> + } > > > > Braces not necessary for single line if blocks. > > Ok > > > > >> + > >> + /* the kernel has queued for us something we cannot receive */ > >> + if (*len > PKT_BUF_BYTES/VHOST_NDESCS) { > > > > This seems oddly circular: deriving the maximum frame size from the > > buffer and number of descriptors, when the number of descriptors was > > derived from the buffer size and maximum frame size. Better to use > > (or add) an L2_MAX_LEN_XX constant. > > Will do > > > > >> + die("vhost: id %d len %u > %zu", i, *len, PKT_BUF_BYTES/VHOST_NDESCS); > >> + } > >> + > >> + vqs[0].last_used_idx++; > >> + vqs[0].num_free++; > >> + return pkt_buf + i * (PKT_BUF_BYTES/VHOST_NDESCS); > >> +} > >> + > >> + > >> +/** > >> + * tap_vhost_input() - Handler for new data on the tun socket to hypervisor vq > >> + * @c: Execution context > >> + * @ref: epoll reference > >> + * @now: Current timestamp > >> + */ > >> +void tap_vhost_input(struct ctx *c, union epoll_ref ref, const struct timespec *now) > >> +{ > >> + eventfd_read(ref.fd, (eventfd_t[]){ 0 }); > >> + > >> + tap_flush_pools(); > >> + > >> + struct virtio_net_hdr_mrg_rxbuf *hdr; > >> + struct iov_tail data; > >> + unsigned len; > > > > My convention, we don't use inline declarations, even though we could > > in C11 - move these up to the top. > > > >> + > >> + while ((hdr = consume_one_rx_descriptor(&len))) { > >> + if (len < sizeof(*hdr)) { > > > > I think you want to check that at least an ethernet (L2) header is > > there as well as the virtio_net_hdr_mrg_rxbuf: both tap_pasta_input() > > and tap_passt_input() appear to check for this. > > Sure, i can add that. But just for my knowledge is there any case where > there can be a virtio_net header without an ethernet header ? Maybe. It might be possible to pass in tap device in with --fd, but have it misconfigured as a "tun" device (raw IP) instead of "tap" (virtual ethernet). Not sure if that's currently possible to combine with vhost-kernel, but it's close enough to possible that it makes sense to be cautious. I'm also not sure what would happen if the guest used an AF_PACKET socket to send malformed Ethernet frames. There's a good chance it would get filtered before reaching us, but still, better to check. > > > > >> + warn("vhost: invalid len %u", len); > >> + continue; > >> + } > >> + > >> + /* skip over the vnet header, we wanna add the packet without it*/ > >> + data = IOV_TAIL_FROM_BUF((void *)(hdr+1), len - sizeof(*hdr), 0); > >> + tap_add_packet(c, &data, now); > >> + } > >> + > >> + tap_handler(c, now); > >> + rx_descriptor_handoff(c); > >> +} > >> + > >> /** > >> * tap_ns_tun() - Get tuntap fd in namespace > >> * @c: Execution context > >> @@ -1524,16 +1612,15 @@ void tap_listen_handler(struct ctx *c, uint32_t events) > >> */ > >> static int tap_ns_tun(void *arg) > >> { > >> - struct ifreq ifr = { .ifr_flags = IFF_TAP | IFF_NO_PI }; > >> - int flags = O_RDWR | O_NONBLOCK | O_CLOEXEC; > >> struct ctx *c = (struct ctx *)arg; > >> + struct ifreq ifr = { .ifr_flags = IFF_TAP | IFF_NO_PI }; > >> int fd, rc; > >> > >> c->fd_tap = -1; > >> memcpy(ifr.ifr_name, c->pasta_ifn, IFNAMSIZ); > >> ns_enter(c); > >> > >> - fd = open("/dev/net/tun", flags); > >> + fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); > > > > This change seems unrelated to the actual aim of this patch. > > Woops, removing > > > > >> if (fd < 0) > >> die_perror("Failed to open() /dev/net/tun"); > >> > >> @@ -1561,6 +1648,20 @@ static void tap_sock_tun_init(struct ctx *c) > >> die("Failed to set up tap device in namespace"); > >> } > >> > >> + /* initialize the vhost-net dev file descriptor */ > >> + if (c->vhost) { > >> + setup_vhost_net(c); > >> + > >> + for (int i = 0; i < ARRAY_SIZE(c->vq); i++) > >> + setup_eventfds(c, i); > >> + > >> + if (setup_memory_table(c) < 0) > >> + die_perror("VHOST_SET_MEM_TABLE ioctl on /dev/vhost-net failed"); > >> + > >> + for (int i = 0; i < ARRAY_SIZE(c->vq); i++) > >> + set_vring_for_queue(c, i, c->fd_tap); > >> + } > > > > It's not clear to me if this code block needs to execute within the > > guest namespace. If not, it should probably be in tap_sock_tun_init() > > rather than tap_ns_tun(). If it _does_ have to be in tap_ns_tun(), > > then tap_ns_tun() probably needs a rename, since it's now doing rather > > more in the vhost case that just getting a device fd. > > Not sure i follow. its already in tap_sock_tun_init not tap_ns_tun. > But generally speaking it needs to be after tap_ns_tun. because it > relies on the device fd that tap_ns_tun has created in the namespace Oh, sorry, I misread the diff. > >> + > >> pasta_ns_conf(c); > >> > >> if (!c->splice_only) > >> diff --git a/tap.h b/tap.h > >> index 1625975..eb02da8 100644 > >> --- a/tap.h > >> +++ b/tap.h > >> @@ -66,6 +66,7 @@ static inline void tap_hdr_update(struct tap_hdr *thdr, size_t l2len) > >> thdr->vnet_len = htonl(l2len); > >> } > >> > >> +void tap_vhost_input(struct ctx *c, union epoll_ref ref, const struct timespec *now); > >> unsigned long tap_l2_max_len(const struct ctx *c); > >> void *tap_push_l2h(const struct ctx *c, void *buf, > >> const void *src_mac, uint16_t proto); > >> -- > >> 2.34.1 > >> > -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson