Complete the AF_PACKET based packet forwarding implementation for
filter-redirector:
1. filter_redirector_send_netdev_packet(): Send packets via AF_PACKET
socket to out_netdev. Updates netdev_tx statistics.
2. filter_redirector_recv_from_chardev(): Handle packets received from
chardev indev. Can forward to either chardev outdev, AF_PACKET
out_netdev, or inject into the netfilter chain.
3. filter_redirector_recv_from_netdev(): Handle packets received from
AF_PACKET in_netdev. Can forward to chardev outdev or inject into
the netfilter chain.
4. Updated filter_redirector_receive_iov() to support out_netdev as
an output endpoint. Added logic to skip netdev path consumption
when redirector has an input endpoint (indev/in_netdev) to prevent
packet loops.
5. Added netdev_rx and netdev_tx counters to query-netfilter-stats
output for monitoring AF_PACKET datapath activity.
Signed-off-by: Cindy Lu <lulu@redhat.com>
---
net/filter-mirror.c | 177 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 171 insertions(+), 6 deletions(-)
diff --git a/net/filter-mirror.c b/net/filter-mirror.c
index f8001612ec..d9e8fcba59 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -65,6 +65,11 @@ struct MirrorState {
uint64_t indev_bytes;
uint64_t outdev_packets;
uint64_t outdev_bytes;
+ /* netdev replay/capture statistics for filter-redirector */
+ uint64_t netdev_rx_packets;
+ uint64_t netdev_rx_bytes;
+ uint64_t netdev_tx_packets;
+ uint64_t netdev_tx_bytes;
};
typedef struct FilterSendCo {
@@ -158,6 +163,59 @@ static int filter_send(MirrorState *s,
return data.ret;
}
+static ssize_t filter_redirector_send_netdev_packet(MirrorState *s,
+ const struct iovec *iov,
+ int iovcnt)
+{
+ ssize_t size = iov_size(iov, iovcnt);
+ g_autofree uint8_t *buf = NULL;
+
+ if (s->out_netfd < 0) {
+ return -ENODEV;
+ }
+ if (size > NET_BUFSIZE) {
+ return -EINVAL;
+ }
+
+ buf = g_malloc(size);
+ iov_to_buf(iov, iovcnt, 0, buf, size);
+
+ ssize_t ret = send(s->out_netfd, buf, size, 0);
+ if (ret < 0) {
+ return -errno;
+ }
+ if (ret > 0) {
+ s->netdev_tx_packets++;
+ s->netdev_tx_bytes += ret;
+ }
+ return ret;
+}
+static ssize_t filter_redirector_send_chardev_iov(MirrorState *s,
+ const struct iovec *iov,
+ int iovcnt)
+{
+ if (!s->outdev) {
+ return -ENODEV;
+ }
+
+ if (!qemu_chr_fe_backend_connected(&s->chr_out)) {
+ return 0;
+ }
+
+ return filter_send(s, iov, iovcnt);
+}
+
+static ssize_t filter_redirector_send_netdev_iov(MirrorState *s,
+ const struct iovec *iov,
+ int iovcnt)
+{
+ if (!s->out_netdev) {
+ return -ENODEV;
+ }
+
+ return filter_redirector_send_netdev_packet(s, iov, iovcnt);
+}
+
static void redirector_to_filter(NetFilterState *nf,
const uint8_t *buf,
int len)
@@ -230,6 +288,75 @@ static void redirector_chr_event(void *opaque, QEMUChrEvent event)
}
}
+static void filter_redirector_recv_from_chardev(NetFilterState *nf,
+ const uint8_t *buf,
+ int len)
+{
+ MirrorState *s = FILTER_REDIRECTOR(nf);
+ ssize_t ret;
+ struct iovec iov = {
+ .iov_base = (void *)buf,
+ .iov_len = len,
+ };
+
+ if (len <= 0) {
+ return;
+ }
+
+ /* chardev indev */
+ s->indev_packets++;
+ s->indev_bytes += len;
+
+ if (s->out_netdev) {
+ ret = filter_redirector_send_netdev_iov(s, &iov, 1);
+ if (ret < 0) {
+ error_report("filter redirector send failed(%s)", strerror(-ret));
+ }
+ return;
+ }
+
+ if (s->outdev) {
+ ret = filter_redirector_send_chardev_iov(s, &iov, 1);
+ if (ret < 0) {
+ error_report("filter redirector send failed(%s)", strerror(-ret));
+ } else if (ret > 0) {
+ s->outdev_packets++;
+ s->outdev_bytes += ret;
+ }
+ return;
+ }
+
+ redirector_to_filter(nf, buf, len);
+}
+
+static bool filter_redirector_recv_from_netdev(NetFilterState *nf,
+ const uint8_t *buf,
+ int len)
+{
+ MirrorState *s = FILTER_REDIRECTOR(nf);
+ ssize_t ret;
+ struct iovec iov = {
+ .iov_base = (void *)buf,
+ .iov_len = len,
+ };
+
+ if (len <= 0) {
+ return false;
+ }
+ if (s->outdev) {
+ ret = filter_redirector_send_chardev_iov(s, &iov, 1);
+ } else {
+ redirector_to_filter(nf, buf, len);
+ return true;
+ }
+
+ if (ret < 0) {
+ error_report("filter redirector send failed(%s)", strerror(-ret));
+ return false;
+ }
+ return true;
+}
+
static void filter_redirector_netdev_read(void *opaque)
{
NetFilterState *nf = opaque;
@@ -254,7 +381,9 @@ static void filter_redirector_netdev_read(void *opaque)
continue;
}
- redirector_to_filter(nf, s->in_netbuf, len);
+ s->netdev_rx_packets++;
+ s->netdev_rx_bytes += len;
+ filter_redirector_recv_from_netdev(nf, s->in_netbuf, len);
}
if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK &&
@@ -296,19 +425,33 @@ static ssize_t filter_redirector_receive_iov(NetFilterState *nf,
MirrorState *s = FILTER_REDIRECTOR(nf);
int ret;
- if (qemu_chr_fe_backend_connected(&s->chr_out)) {
- ret = filter_send(s, iov, iovcnt);
+ /*
+ * If this redirector has an explicit input endpoint (indev/in_netdev),
+ * it acts as an injector for that endpoint and must not consume packets
+ * from the regular netdev data path. Consuming here can create loops when
+ * out_netdev points back to the same TAP netdev.
+ */
+ if (s->indev || s->in_netdev) {
+ return 0;
+ }
+
+ if (s->out_netdev || s->outdev) {
+ if (s->out_netdev) {
+ ret = filter_redirector_send_netdev_iov(s, iov, iovcnt);
+ } else {
+ ret = filter_redirector_send_chardev_iov(s, iov, iovcnt);
+ }
if (ret < 0) {
error_report("filter redirector send failed(%s)", strerror(-ret));
- } else if (ret > 0) {
+ } else if (ret > 0 && !s->out_netdev) {
/* Update outdev statistics on successful send */
s->outdev_packets++;
s->outdev_bytes += ret;
}
return iov_size(iov, iovcnt);
- } else {
- return 0;
}
+
+ return 0;
}
static void filter_mirror_cleanup(NetFilterState *nf)
@@ -369,6 +512,16 @@ static void redirector_rs_finalize(SocketReadState *rs)
MirrorState *s = container_of(rs, MirrorState, rs);
NetFilterState *nf = NETFILTER(s);
+ /*
+ * If redirector has an explicit output endpoint, keep the redirect path
+ * (e.g. indev=red0 -> out_netdev=net0).
+ * Fallback to direct netfilter injection only when no output is set.
+ */
+ if (s->outdev || s->out_netdev) {
+ filter_redirector_recv_from_chardev(nf, rs->buf, rs->packet_len);
+ return;
+ }
+
/* Update indev statistics */
s->indev_packets++;
s->indev_bytes += rs->packet_len;
@@ -826,6 +979,18 @@ static GList *filter_redirector_get_stats(NetFilterState *nf)
counter->bytes = s->outdev_bytes;
list = g_list_append(list, counter);
+ counter = g_new0(NetFilterCounter, 1);
+ counter->name = g_strdup("netdev_rx");
+ counter->packets = s->netdev_rx_packets;
+ counter->bytes = s->netdev_rx_bytes;
+ list = g_list_append(list, counter);
+
+ counter = g_new0(NetFilterCounter, 1);
+ counter->name = g_strdup("netdev_tx");
+ counter->packets = s->netdev_tx_packets;
+ counter->bytes = s->netdev_tx_bytes;
+ list = g_list_append(list, counter);
+
return list;
}
--
2.52.0
© 2016 - 2026 Red Hat, Inc.