Prepare filter-redirector for direct TAP access through AF_PACKET sockets.
Add the setup-side plumbing needed by later capture/inject changes:
- extend MirrorState with dedicated AF_PACKET fds and a capture buffer
- resolve the TAP ifname from the backend fd
- create and bind a nonblocking AF_PACKET socket on that interface
- store the socket as the capture or inject endpoint based on the
redirector role
- clean up the AF_PACKET resources from cleanup/finalize paths
This commit only prepares the sockets and object state. The actual
capture and inject datapaths are added by follow-up commits.
Signed-off-by: Cindy Lu <lulu@redhat.com>
---
net/filter-mirror.c | 91 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/net/filter-mirror.c b/net/filter-mirror.c
index ab711e8835..d9f6a11d6b 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -22,10 +22,18 @@
#include "qemu/error-report.h"
#include "trace.h"
#include "chardev/char-fe.h"
+#include "net/vhost_net.h"
#include "qemu/iov.h"
#include "qemu/sockets.h"
#include "block/aio-wait.h"
#include "system/runstate.h"
+#include "net/tap.h"
+#include "net/tap_int.h"
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <linux/if_packet.h>
+#include <netinet/if_ether.h>
typedef struct MirrorState MirrorState;
DECLARE_INSTANCE_CHECKER(MirrorState, FILTER_MIRROR,
@@ -40,6 +48,9 @@ struct MirrorState {
NetFilterState parent_obj;
char *indev;
char *outdev;
+ int in_netfd;
+ uint8_t *in_netbuf;
+ int out_netfd;
CharFrontend chr_in;
CharFrontend chr_out;
SocketReadState rs;
@@ -267,6 +278,15 @@ static void filter_redirector_cleanup(NetFilterState *nf)
qemu_chr_fe_deinit(&s->chr_in, false);
qemu_chr_fe_deinit(&s->chr_out, false);
qemu_del_vm_change_state_handler(s->vmsentry);
+ if (s->in_netfd >= 0) {
+ qemu_set_fd_handler(s->in_netfd, NULL, NULL, NULL);
+ close(s->in_netfd);
+ s->in_netfd = -1;
+ }
+ if (s->out_netfd >= 0) {
+ close(s->out_netfd);
+ s->out_netfd = -1;
+ }
if (nf->netdev) {
nf->netdev->allow_send_when_stopped = 0;
@@ -360,6 +380,70 @@ static void filter_redirector_maybe_enable_read_poll(NetFilterState *nf)
}
}
+static bool filter_redirector_netdev_setup(NetFilterState *nf, Error **errp)
+{
+ MirrorState *s = FILTER_REDIRECTOR(nf);
+ struct sockaddr_ll sll = { 0 };
+ char ifname[IFNAMSIZ] = { 0 };
+ bool capture_role;
+ bool inject_role;
+ int ifindex;
+ int fd;
+ NetClientState *nc = nf->netdev;
+ int tapfd;
+
+ if (!nc || nc->info->type != NET_CLIENT_DRIVER_TAP) {
+ return true;
+ }
+
+ capture_role = s->outdev && !s->indev && get_vhost_net(nc);
+ inject_role = s->indev && !s->outdev && get_vhost_net(nc);
+ if (!capture_role && !inject_role) {
+ return true;
+ }
+
+ tapfd = tap_get_fd(nc);
+ if (tapfd < 0 || tap_fd_get_ifname(tapfd, ifname) != 0) {
+ error_setg(errp, "failed to resolve TAP ifname for netdev '%s'",
+ nf->netdev_id);
+ return false;
+ }
+
+ ifindex = if_nametoindex(ifname);
+ if (!ifindex) {
+ error_setg_errno(errp, errno,
+ "failed to resolve ifindex for '%s'", ifname);
+ return false;
+ }
+
+ fd = qemu_socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL));
+ if (fd < 0) {
+ error_setg_errno(errp, errno, "failed to create AF_PACKET socket");
+ return false;
+ }
+
+ sll.sll_family = AF_PACKET;
+ sll.sll_ifindex = ifindex;
+ sll.sll_protocol = htons(ETH_P_ALL);
+ if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
+ error_setg_errno(errp, errno,
+ "failed to bind AF_PACKET socket for ifname '%s'",
+ ifname);
+ close(fd);
+ return false;
+ }
+
+ if (capture_role) {
+ s->in_netfd = fd;
+ g_free(s->in_netbuf);
+ s->in_netbuf = g_malloc(REDIRECTOR_MAX_LEN);
+ } else {
+ s->out_netfd = fd;
+ }
+
+ return true;
+}
+
static void filter_redirector_setup(NetFilterState *nf, Error **errp)
{
MirrorState *s = FILTER_REDIRECTOR(nf);
@@ -410,6 +494,10 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp)
}
}
+ if (!filter_redirector_netdev_setup(nf, errp)) {
+ return;
+ }
+
s->vmsentry = qemu_add_vm_change_state_handler(
filter_redirector_vm_state_change, nf);
@@ -623,6 +711,8 @@ static void filter_redirector_init(Object *obj)
MirrorState *s = FILTER_REDIRECTOR(obj);
s->vnet_hdr = false;
+ s->in_netfd = -1;
+ s->out_netfd = -1;
}
static void filter_mirror_fini(Object *obj)
@@ -638,6 +728,7 @@ static void filter_redirector_fini(Object *obj)
g_free(s->indev);
g_free(s->outdev);
+ g_free(s->in_netbuf);
}
static const TypeInfo filter_redirector_info = {
--
2.52.0
On Mon, Mar 30, 2026 at 5:10 PM Cindy Lu <lulu@redhat.com> wrote: > > Prepare filter-redirector for direct TAP access through AF_PACKET sockets. > > Add the setup-side plumbing needed by later capture/inject changes: > - extend MirrorState with dedicated AF_PACKET fds and a capture buffer > - resolve the TAP ifname from the backend fd > - create and bind a nonblocking AF_PACKET socket on that interface > - store the socket as the capture or inject endpoint based on the > redirector role > - clean up the AF_PACKET resources from cleanup/finalize paths > > This commit only prepares the sockets and object state. The actual > capture and inject datapaths are added by follow-up commits. > > Signed-off-by: Cindy Lu <lulu@redhat.com> I think I have the same question as version 1, could we simply reuse 1) netdev socket or 2) chardev for packet socket instead of having this? Thanks
© 2016 - 2026 Red Hat, Inc.