Add the AF_PACKET plumbing that lets filter-redirector bypass vhost and
talk to the TAP device directly.

Resolve the TAP ifname from the backend fd, create a nonblocking raw
socket, bind it to the interface, and store it as either the capture or
inject endpoint depending on the redirector role.

Also add the capture-side fd handler, which drains PACKET_OUTGOING
frames and forwards them into the filter chain.

Signed-off-by: Cindy Lu <[email protected]>
---
 net/filter-mirror.c | 179 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 170 insertions(+), 9 deletions(-)

diff --git a/net/filter-mirror.c b/net/filter-mirror.c
index 376b7da025..915f2f8b35 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -27,6 +27,13 @@
 #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,
@@ -41,6 +48,10 @@ struct MirrorState {
     NetFilterState parent_obj;
     char *indev;
     char *outdev;
+    NetClientState *out_net;
+    int in_netfd;
+    uint8_t *in_netbuf;
+    int out_netfd;
     CharFrontend chr_in;
     CharFrontend chr_out;
     SocketReadState rs;
@@ -189,6 +200,17 @@ static int redirector_chr_can_read(void *opaque)
     return REDIRECTOR_MAX_LEN;
 }
 
+static bool filter_redirector_input_active(NetFilterState *nf, bool enable)
+{
+    MirrorState *s = FILTER_REDIRECTOR(nf);
+
+    if (!enable) {
+        return false;
+    }
+
+    return runstate_is_running() || s->enable_when_stopped;
+}
+
 static void redirector_chr_read(void *opaque, const uint8_t *buf, int size)
 {
     NetFilterState *nf = opaque;
@@ -225,6 +247,40 @@ static void redirector_chr_event(void *opaque, 
QEMUChrEvent event)
     }
 }
 
+static void filter_redirector_netdev_read(void *opaque)
+{
+    NetFilterState *nf = opaque;
+    MirrorState *s = FILTER_REDIRECTOR(nf);
+    struct sockaddr_ll sll;
+    socklen_t sll_len;
+    ssize_t len;
+
+    if (!s->in_netbuf || s->in_netfd < 0) {
+        return;
+    }
+
+    for (;;) {
+        sll_len = sizeof(sll);
+        len = recvfrom(s->in_netfd, s->in_netbuf, REDIRECTOR_MAX_LEN, 0,
+                       (struct sockaddr *)&sll, &sll_len);
+        if (len <= 0) {
+            break;
+        }
+
+        if (sll.sll_pkttype != PACKET_OUTGOING) {
+            continue;
+        }
+
+        redirector_to_filter(nf, s->in_netbuf, len);
+    }
+
+    if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK &&
+        errno != EINTR) {
+        error_report("filter redirector read netdev failed(%s)",
+                     strerror(errno));
+    }
+}
+
 static ssize_t filter_mirror_receive_iov(NetFilterState *nf,
                                          NetClientState *sender,
                                          unsigned flags,
@@ -285,7 +341,19 @@ 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->vmsentry) {
+        qemu_del_vm_change_state_handler(s->vmsentry);
+        s->vmsentry = NULL;
+    }
+    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;
@@ -352,6 +420,14 @@ static void filter_redirector_vm_state_change(void 
*opaque, bool running,
     NetFilterState *nf = opaque;
     MirrorState *s = FILTER_REDIRECTOR(nf);
     NetClientState *nc = nf->netdev;
+    bool active = filter_redirector_input_active(nf, nf->on);
+
+    if (s->in_netfd >= 0) {
+        qemu_set_fd_handler(s->in_netfd,
+                            active ? filter_redirector_netdev_read : NULL,
+                            NULL,
+                            active ? nf : NULL);
+    }
 
     if (!running && nc && s->enable_when_stopped && nc->info->read_poll) {
         nc->info->read_poll(nc, true);
@@ -379,21 +455,83 @@ 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 };
+    int ifindex;
+    int fd;
+    NetClientState *nc = nf->netdev;
+    int tapfd;
+    bool capture = filter_redirector_use_capture_netdev(nf);
+    bool inject = filter_redirector_use_inject_netdev(nf);
+
+    if (!capture && !inject) {
+        return true;
+    }
+
+    if (!nc || nc->info->type != NET_CLIENT_DRIVER_TAP) {
+        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) {
+        s->in_netfd = fd;
+        g_free(s->in_netbuf);
+        s->in_netbuf = g_malloc(REDIRECTOR_MAX_LEN);
+    } else if (inject) {
+        s->out_netfd = fd;
+        s->out_net = nc;
+    }
+    return true;
+}
+
 static void filter_redirector_setup(NetFilterState *nf, Error **errp)
 {
     MirrorState *s = FILTER_REDIRECTOR(nf);
     Chardev *chr;
 
     if (!s->indev && !s->outdev) {
-        error_setg(errp, "filter redirector needs 'indev' or "
-                   "'outdev' at least one property set");
+        error_setg(errp, "filter redirector needs at least one of "
+                   "'indev' or 'outdev'");
+        return;
+    }
+
+    if (s->indev && s->outdev && !strcmp(s->indev, s->outdev)) {
+        error_setg(errp, "'indev' and 'outdev' could not be same "
+                   "for filter redirector");
         return;
-    } else if (s->indev && s->outdev) {
-        if (!strcmp(s->indev, s->outdev)) {
-            error_setg(errp, "'indev' and 'outdev' could not be same "
-                       "for filter redirector");
-            return;
-        }
     }
 
     net_socket_rs_init(&s->rs, redirector_rs_finalize, s->vnet_hdr);
@@ -429,9 +567,21 @@ 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);
 
+    if (s->in_netfd >= 0) {
+        bool active = filter_redirector_input_active(nf, nf->on);
+
+        qemu_set_fd_handler(s->in_netfd,
+                            active ? filter_redirector_netdev_read : NULL,
+                            NULL,
+                            active ? nf : NULL);
+    }
     filter_redirector_maybe_enable_read_poll(nf);
 
     filter_redirector_refresh_allow_send_when_stopped(nf);
@@ -440,6 +590,7 @@ static void filter_redirector_setup(NetFilterState *nf, 
Error **errp)
 static void filter_redirector_status_changed(NetFilterState *nf, Error **errp)
 {
     MirrorState *s = FILTER_REDIRECTOR(nf);
+    bool active = filter_redirector_input_active(nf, nf->on);
 
     if (s->indev) {
         if (nf->on) {
@@ -452,6 +603,13 @@ static void 
filter_redirector_status_changed(NetFilterState *nf, Error **errp)
         }
     }
 
+    if (s->in_netfd >= 0) {
+        qemu_set_fd_handler(s->in_netfd,
+                            active ? filter_redirector_netdev_read : NULL,
+                            NULL,
+                            active ? nf : NULL);
+    }
+
     if (nf->on) {
         filter_redirector_maybe_enable_read_poll(nf);
     }
@@ -642,6 +800,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)
@@ -657,6 +817,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


Reply via email to