From a1708203b583ec04fb9b793840dc282d3c89a20d Mon Sep 17 00:00:00 2001 From: "Houqi (Nick) Zuo" Date: Mon, 8 Dec 2025 12:35:18 +0800 Subject: [PATCH 01/13] net/tap-linux.c: avoid abort when setting invalid fd This patch removes abort() call in the tap_fd_set_vnet_hdr_len() function. If the fd is found to be in a bad state (e.g., EBADFD or ENODEV), the function will print an error message. When QEMU creates a tap device automatically and the tap device is manually removed from the host while the guest is running, the tap device file descriptor becomes invalid. Later, when the guest executes shutdown, the tap_fd_set_vnet_hdr_len() function may be called and abort QEMU with a core dump when attempting to use the invalid fd. The expected behavior for this negative test case is that QEMU should report an error but continue running rather than aborting. Testing: - Start QEMU with automatically created tap device - Manually remove the tap device on the host - Execute shutdown in the guest - Verify QEMU reports an error but does not abort Fixes: 0caed25cd171 ("virtio: Call set_features during reset") Signed-off-by: Houqi (Nick) Zuo Signed-off-by: Jason Wang --- net/tap-linux.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/tap-linux.c b/net/tap-linux.c index 909c4f1fcf..54de3cd06b 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -214,10 +214,11 @@ bool tap_probe_has_tunnel(int fd) void tap_fd_set_vnet_hdr_len(int fd, int len) { - if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { - fprintf(stderr, "TUNSETVNETHDRSZ ioctl() failed: %s. Exiting.\n", - strerror(errno)); - abort(); + int ret; + + ret = ioctl(fd, TUNSETVNETHDRSZ, &len); + if (ret != 0) { + error_report("TUNSETVNETHDRSZ ioctl() failed: %s.", strerror(errno)); } } From f96f34d132dcd99d3ea8187176ecd71ca2c44082 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:17 +0300 Subject: [PATCH 02/13] net/tap: net_init_tap_one(): drop extra error propagation Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/tap.c b/net/tap.c index bfba3fd7a7..c162523a03 100644 --- a/net/tap.c +++ b/net/tap.c @@ -736,9 +736,8 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, } if (vhostfdname) { - vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, &err); + vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, errp); if (vhostfd == -1) { - error_propagate(errp, err); goto failed; } if (!qemu_set_blocking(vhostfd, false, errp)) { From fec61d48d1a4552710259be30c90c8bd4f46e8c3 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:18 +0300 Subject: [PATCH 03/13] net/tap: net_init_tap_one(): move parameter checking earlier Let's keep all similar argument checking in net_init_tap() function. Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/tap.c b/net/tap.c index c162523a03..abeee6be11 100644 --- a/net/tap.c +++ b/net/tap.c @@ -767,9 +767,6 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, "vhost-net requested but could not be initialized"); goto failed; } - } else if (vhostfdname) { - error_setg(errp, "vhostfd(s)= is not valid without vhost"); - goto failed; } return; @@ -831,6 +828,11 @@ int net_init_tap(const Netdev *netdev, const char *name, return -1; } + if (tap->has_vhost && !tap->vhost && (tap->vhostfds || tap->vhostfd)) { + error_setg(errp, "vhostfd(s)= is not valid without vhost"); + return -1; + } + if (tap->fd) { if (tap->ifname || tap->script || tap->downscript || tap->has_vnet_hdr || tap->helper || tap->has_queues || From dd6709b851d7bd2119963e256fad53fdcda2ebde Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:19 +0300 Subject: [PATCH 04/13] net/tap: pass NULL to net_init_tap_one() in cases when scripts are NULL Directly pass NULL in cases where we report an error if script or downscript are set. Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/net/tap.c b/net/tap.c index abeee6be11..8182f39851 100644 --- a/net/tap.c +++ b/net/tap.c @@ -807,8 +807,6 @@ int net_init_tap(const Netdev *netdev, const char *name, const NetdevTapOptions *tap; int fd, vnet_hdr = 0, i = 0, queues; /* for the no-fd, no-helper case */ - const char *script; - const char *downscript; Error *err = NULL; const char *vhostfdname; char ifname[128]; @@ -818,8 +816,6 @@ int net_init_tap(const Netdev *netdev, const char *name, tap = &netdev->u.tap; queues = tap->has_queues ? tap->queues : 1; vhostfdname = tap->vhostfd; - script = tap->script; - downscript = tap->downscript; /* QEMU hubs do not support multiqueue tap, in this case peer is set. * For -netdev, peer is always NULL. */ @@ -860,7 +856,7 @@ int net_init_tap(const Netdev *netdev, const char *name, } net_init_tap_one(tap, peer, "tap", name, NULL, - script, downscript, + NULL, NULL, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); @@ -921,7 +917,7 @@ int net_init_tap(const Netdev *netdev, const char *name, } net_init_tap_one(tap, peer, "tap", name, ifname, - script, downscript, + NULL, NULL, tap->vhostfds ? vhost_fds[i] : NULL, vnet_hdr, fd, &err); if (err) { @@ -966,7 +962,7 @@ free_fail: } net_init_tap_one(tap, peer, "bridge", name, ifname, - script, downscript, vhostfdname, + NULL, NULL, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); @@ -974,6 +970,8 @@ free_fail: return -1; } } else { + const char *script = tap->script; + const char *downscript = tap->downscript; g_autofree char *default_script = NULL; g_autofree char *default_downscript = NULL; if (tap->vhostfds) { From fcc56cd23eb23fa5b66e52fded2fe33152c07794 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:20 +0300 Subject: [PATCH 05/13] net/tap: rework scripts handling Simplify handling scripts: parse all these "no" and '\0' once, and then keep simpler logic for net_tap_open() and net_init_tap_one(): NULL means no script to run, otherwise run script. Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap.c | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/net/tap.c b/net/tap.c index 8182f39851..c7d8e1a3be 100644 --- a/net/tap.c +++ b/net/tap.c @@ -91,6 +91,21 @@ static void launch_script(const char *setup_script, const char *ifname, static void tap_send(void *opaque); static void tap_writable(void *opaque); +static char *tap_parse_script(const char *script_arg, const char *default_path) +{ + g_autofree char *res = g_strdup(script_arg); + + if (!res) { + res = get_relocated_path(default_path); + } + + if (res[0] == '\0' || strcmp(res, "no") == 0) { + return NULL; + } + + return g_steal_pointer(&res); +} + static void tap_update_fd_handler(TAPState *s) { qemu_set_fd_handler(s->fd, @@ -676,9 +691,7 @@ static int net_tap_init(const NetdevTapOptions *tap, int *vnet_hdr, return -1; } - if (setup_script && - setup_script[0] != '\0' && - strcmp(setup_script, "no") != 0) { + if (setup_script) { launch_script(setup_script, ifname, fd, &err); if (err) { error_propagate(errp, err); @@ -714,9 +727,9 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, qemu_set_info_str(&s->nc, "helper=%s", tap->helper); } else { qemu_set_info_str(&s->nc, "ifname=%s,script=%s,downscript=%s", ifname, - script, downscript); + script ?: "no", downscript ?: "no"); - if (strcmp(downscript, "no") != 0) { + if (downscript) { snprintf(s->down_script, sizeof(s->down_script), "%s", downscript); snprintf(s->down_script_arg, sizeof(s->down_script_arg), "%s", ifname); @@ -970,23 +983,16 @@ free_fail: return -1; } } else { - const char *script = tap->script; - const char *downscript = tap->downscript; - g_autofree char *default_script = NULL; - g_autofree char *default_downscript = NULL; + g_autofree char *script = + tap_parse_script(tap->script, DEFAULT_NETWORK_SCRIPT); + g_autofree char *downscript = + tap_parse_script(tap->downscript, DEFAULT_NETWORK_DOWN_SCRIPT); + if (tap->vhostfds) { error_setg(errp, "vhostfds= is invalid if fds= wasn't specified"); return -1; } - if (!script) { - script = default_script = get_relocated_path(DEFAULT_NETWORK_SCRIPT); - } - if (!downscript) { - downscript = default_downscript = - get_relocated_path(DEFAULT_NETWORK_DOWN_SCRIPT); - } - if (tap->ifname) { pstrcpy(ifname, sizeof ifname, tap->ifname); } else { @@ -994,7 +1000,7 @@ free_fail: } for (i = 0; i < queues; i++) { - fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? "no" : script, + fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? NULL : script, ifname, sizeof ifname, queues > 1, errp); if (fd == -1) { return -1; @@ -1009,8 +1015,8 @@ free_fail: } net_init_tap_one(tap, peer, "tap", name, ifname, - i >= 1 ? "no" : script, - i >= 1 ? "no" : downscript, + i >= 1 ? NULL : script, + i >= 1 ? NULL : downscript, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); From 0b72ea1ff558b9a70859fc018f7c7485b6eb3ac9 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:21 +0300 Subject: [PATCH 06/13] net/tap: setup exit notifier only when needed No reason to setup notifier on each queue of multique tap, when we actually want to run downscript only once. As well, let's not setup notifier, when downscript is not enabled (downsciprt="no"). Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/net/tap.c b/net/tap.c index c7d8e1a3be..5af157164e 100644 --- a/net/tap.c +++ b/net/tap.c @@ -326,11 +326,9 @@ static void tap_exit_notify(Notifier *notifier, void *data) TAPState *s = container_of(notifier, TAPState, exit); Error *err = NULL; - if (s->down_script[0]) { - launch_script(s->down_script, s->down_script_arg, s->fd, &err); - if (err) { - error_report_err(err); - } + launch_script(s->down_script, s->down_script_arg, s->fd, &err); + if (err) { + error_report_err(err); } } @@ -346,8 +344,11 @@ static void tap_cleanup(NetClientState *nc) qemu_purge_queued_packets(nc); - tap_exit_notify(&s->exit, NULL); - qemu_remove_exit_notifier(&s->exit); + if (s->exit.notify) { + tap_exit_notify(&s->exit, NULL); + qemu_remove_exit_notifier(&s->exit); + s->exit.notify = NULL; + } tap_read_poll(s, false); tap_write_poll(s, false); @@ -443,9 +444,6 @@ static TAPState *net_tap_fd_init(NetClientState *peer, tap_read_poll(s, true); s->vhost_net = NULL; - s->exit.notify = tap_exit_notify; - qemu_add_exit_notifier(&s->exit); - return s; } @@ -733,6 +731,8 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, snprintf(s->down_script, sizeof(s->down_script), "%s", downscript); snprintf(s->down_script_arg, sizeof(s->down_script_arg), "%s", ifname); + s->exit.notify = tap_exit_notify; + qemu_add_exit_notifier(&s->exit); } } From 638a302b1b7c7b5453c2f31cf3b364e0b0cb0d85 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:22 +0300 Subject: [PATCH 07/13] net/tap: tap_set_sndbuf(): add return value Follow common recommendations in include/qapi/error.h of having a return value together with errp. This allows to avoid error propagation. Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap-bsd.c | 3 ++- net/tap-linux.c | 5 ++++- net/tap-solaris.c | 3 ++- net/tap-stub.c | 3 ++- net/tap.c | 5 +---- net/tap_int.h | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/net/tap-bsd.c b/net/tap-bsd.c index 3fd300d46f..0e62530aaa 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -206,8 +206,9 @@ error: } #endif /* __FreeBSD__ */ -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap-linux.c b/net/tap-linux.c index 54de3cd06b..2b2d75a823 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -145,7 +145,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, */ #define TAP_DEFAULT_SNDBUF 0 -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) { int sndbuf; @@ -159,7 +159,10 @@ void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) { error_setg_errno(errp, errno, "TUNSETSNDBUF ioctl failed"); + return false; } + + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap-solaris.c b/net/tap-solaris.c index faf7922ea8..049b551ae1 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -208,8 +208,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return fd; } -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap-stub.c b/net/tap-stub.c index f7a5e0c163..86d7d38e0f 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -33,8 +33,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return -1; } -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap.c b/net/tap.c index 5af157164e..07a450e68d 100644 --- a/net/tap.c +++ b/net/tap.c @@ -709,13 +709,10 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, const char *downscript, const char *vhostfdname, int vnet_hdr, int fd, Error **errp) { - Error *err = NULL; TAPState *s = net_tap_fd_init(peer, model, name, fd, vnet_hdr); int vhostfd; - tap_set_sndbuf(s->fd, tap, &err); - if (err) { - error_propagate(errp, err); + if (!tap_set_sndbuf(s->fd, tap, errp)) { goto failed; } diff --git a/net/tap_int.h b/net/tap_int.h index b76a05044b..7963dd6aae 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -34,7 +34,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); +bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); int tap_probe_vnet_hdr(int fd, Error **errp); int tap_probe_has_ufo(int fd); int tap_probe_has_uso(int fd); From 3183bd395aa4c4d5b9f5ec7cc8c6f7dd7054d2dd Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Thu, 30 Oct 2025 19:40:23 +0300 Subject: [PATCH 08/13] net/tap: rework tap_set_sndbuf() Keep NetdevTapOptions related logic in tap.c, and make tap_set_sndbuf a simple system call wrapper, more like other functions in tap-linux.c Signed-off-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Maksim Davydov Signed-off-by: Jason Wang --- net/tap-bsd.c | 2 +- net/tap-linux.c | 16 ++-------------- net/tap-solaris.c | 2 +- net/tap-stub.c | 2 +- net/tap.c | 6 +++++- net/tap_int.h | 3 +-- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/net/tap-bsd.c b/net/tap-bsd.c index 0e62530aaa..c39daf9385 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -206,7 +206,7 @@ error: } #endif /* __FreeBSD__ */ -bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { return true; } diff --git a/net/tap-linux.c b/net/tap-linux.c index 2b2d75a823..3cd7d26710 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -143,21 +143,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, * Ethernet NICs generally have txqueuelen=1000, so 1Mb is * a good value, given a 1500 byte MTU. */ -#define TAP_DEFAULT_SNDBUF 0 - -bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { - int sndbuf; - - sndbuf = !tap->has_sndbuf ? TAP_DEFAULT_SNDBUF : - tap->sndbuf > INT_MAX ? INT_MAX : - tap->sndbuf; - - if (!sndbuf) { - sndbuf = INT_MAX; - } - - if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) { + if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1) { error_setg_errno(errp, errno, "TUNSETSNDBUF ioctl failed"); return false; } diff --git a/net/tap-solaris.c b/net/tap-solaris.c index 049b551ae1..8704b1084b 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -208,7 +208,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return fd; } -bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { return true; } diff --git a/net/tap-stub.c b/net/tap-stub.c index 86d7d38e0f..6aa60d96ad 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -33,7 +33,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return -1; } -bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { return true; } diff --git a/net/tap.c b/net/tap.c index 07a450e68d..8d7ab6ba6f 100644 --- a/net/tap.c +++ b/net/tap.c @@ -711,8 +711,12 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, { TAPState *s = net_tap_fd_init(peer, model, name, fd, vnet_hdr); int vhostfd; + bool sndbuf_required = tap->has_sndbuf; + int sndbuf = + (tap->has_sndbuf && tap->sndbuf) ? MIN(tap->sndbuf, INT_MAX) : INT_MAX; - if (!tap_set_sndbuf(s->fd, tap, errp)) { + if (!tap_set_sndbuf(fd, sndbuf, sndbuf_required ? errp : NULL) && + sndbuf_required) { goto failed; } diff --git a/net/tap_int.h b/net/tap_int.h index 7963dd6aae..dc4f484006 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -26,7 +26,6 @@ #ifndef NET_TAP_INT_H #define NET_TAP_INT_H -#include "qapi/qapi-types-net.h" #include "net/net.h" int tap_open(char *ifname, int ifname_size, int *vnet_hdr, @@ -34,7 +33,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); -bool tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp); int tap_probe_vnet_hdr(int fd, Error **errp); int tap_probe_has_ufo(int fd); int tap_probe_has_uso(int fd); From 60a8b9b75a5b8db61d348c84ec418aa2b7a648bd Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 29 Dec 2025 16:45:40 +0800 Subject: [PATCH 09/13] net/filter-buffer: make interval change take effect immediately Previously, when the 'interval' property was modified at runtime via QMP, the new value would only take effect after the current timer period elapsed. This could lead to unexpected behavior when users expect immediate changes. Fix this by checking if the timer is already running when setting the interval property. If so, reschedule the timer with the new interval value immediately. Reviewed-by: Zhang Chen Signed-off-by: Jason Wang --- net/filter-buffer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/filter-buffer.c b/net/filter-buffer.c index a36be31dc8..427da24097 100644 --- a/net/filter-buffer.c +++ b/net/filter-buffer.c @@ -159,6 +159,7 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v, Error **errp) { FilterBufferState *s = FILTER_BUFFER(obj); + NetFilterState *nf = NETFILTER(obj); uint32_t value; if (!visit_type_uint32(v, name, &value, errp)) { @@ -170,6 +171,11 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v, return; } s->interval = value; + + if (nf->netdev && nf->on) { + timer_mod(&s->release_timer, + qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + s->interval); + } } static void filter_buffer_class_init(ObjectClass *oc, const void *data) From 50f6d44850f69af65ef221924b1c77b930fabe1d Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 29 Dec 2025 16:45:41 +0800 Subject: [PATCH 10/13] tests/qtest: add test for filter-buffer interval change Add test_change_interval_timer to verify that modifying the 'interval' property of filter-buffer at runtime takes effect immediately. The test uses socket backend and filter-redirector to verify timer behavior: - Creates filter-buffer with a very long interval (1000 seconds) - Sends a packet which gets buffered - Advances virtual clock by 1 second, verifies packet is still buffered - Changes interval to 1ms via qom-set (timer should be rescheduled) - Advances virtual clock by 2ms, verifies packet is now released - This proves the timer was rescheduled immediately when interval changed The test uses filter-redirector to observe when packets are released by filter-buffer, providing end-to-end verification of the timer rescheduling behavior. Reviewed-by: Zhang Chen Reviewed-by: Fabiano Rosas Signed-off-by: Jason Wang --- tests/qtest/meson.build | 1 + tests/qtest/test-filter-buffer.c | 169 +++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 tests/qtest/test-filter-buffer.c diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index a8b09d065f..dfb83650c6 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -46,6 +46,7 @@ qtests_cxl = \ # for the availability of the default NICs in the tests qtests_filter = \ (get_option('default_devices') and slirp.found() ? ['test-netfilter'] : []) + \ + (get_option('default_devices') and host_os != 'windows' ? ['test-filter-buffer'] : []) + \ (get_option('default_devices') and host_os != 'windows' ? ['test-filter-mirror'] : []) + \ (get_option('default_devices') and host_os != 'windows' ? ['test-filter-redirector'] : []) diff --git a/tests/qtest/test-filter-buffer.c b/tests/qtest/test-filter-buffer.c new file mode 100644 index 0000000000..441cbb975c --- /dev/null +++ b/tests/qtest/test-filter-buffer.c @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * QTest testcase for filter-buffer + * + * Copyright (c) 2025 Red Hat, Inc. + * Author: Jason Wang + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qemu/iov.h" +#include "qemu/sockets.h" + +/* + * Test that changing interval at runtime affects packet release timing. + * + * Traffic flow with filter-buffer and filter-redirector: + * + * test side | qemu side + * | + * +--------+ | +---------+ + * | send +------------------------>| backend | + * | sock[0]| | +----+----+ + * +--------+ | | + * | +----v----+ + * | | fbuf0 | filter-buffer (queue=tx) + * | +----+----+ + * | | + * | +----v----+ +----------+ + * | | rd0 +->| chardev0 | + * | +---------+ +----+-----+ + * | | + * +--------+ | | + * | recv |<--------------------------------------+ + * | sock | | + * +--------+ | + * + * The test verifies that when interval is changed via qom-set, the timer + * is rescheduled immediately, causing buffered packets to be released + * at the new interval rather than waiting for the old interval to elapse. + */ +static void test_change_interval_timer(void) +{ + QTestState *qts; + QDict *response; + int backend_sock[2], recv_sock; + int ret; + char send_buf[] = "Hello filter-buffer!"; + char recv_buf[128]; + char sock_path[] = "filter-buffer-test.XXXXXX"; + uint32_t size = sizeof(send_buf); + uint32_t len; + + size = htonl(size); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path); + g_assert_cmpint(ret, !=, -1); + + /* + * Start QEMU with: + * - socket backend connected to our socketpair + * - filter-buffer with a very long interval (1000 seconds) + * - filter-redirector to send released packets to a chardev socket + * + * queue=tx intercepts packets going from backend to the guest, + * i.e., data we send from the test side. + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=chardev0,path=%s,server=on,wait=off " + "-object filter-buffer,id=fbuf0,netdev=qtest-bn0," + "queue=tx,interval=1000000000 " + "-object filter-redirector,id=rd0,netdev=qtest-bn0," + "queue=tx,outdev=chardev0", + backend_sock[1], sock_path); + + /* Connect to the chardev socket to receive redirected packets */ + recv_sock = unix_connect(sock_path, NULL); + g_assert_cmpint(recv_sock, !=, -1); + + /* Send a QMP command to ensure chardev connection is established */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* + * Send a packet from the test side. + * It should be buffered by filter-buffer. + */ + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + ret = iov_send(backend_sock[0], iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + /* + * Advance virtual clock by 1 second (1,000,000,000 ns). + * This is much less than the 1000 second interval, so the packet + * should still be buffered. + */ + qtest_clock_step(qts, 1000000000LL); + + /* Try to receive with non-blocking - should fail (packet still buffered) */ + ret = recv(recv_sock, recv_buf, sizeof(recv_buf), MSG_DONTWAIT); + g_assert_cmpint(ret, ==, -1); + g_assert(errno == EAGAIN || errno == EWOULDBLOCK); + + /* + * Now change the interval to 1000 us (1ms) via qom-set. + * This should reschedule the timer to fire in 1ms from now. + */ + response = qtest_qmp(qts, + "{'execute': 'qom-set'," + " 'arguments': {" + " 'path': 'fbuf0'," + " 'property': 'interval'," + " 'value': 1000" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + qobject_unref(response); + + /* + * Advance virtual clock by 2ms (2,000,000 ns). + * This exceeds the new 1ms interval, so the timer should fire + * and release the buffered packet. + * + * If the interval change didn't take effect immediately, we would + * still be waiting for the original 1000 second interval to elapse, + * and the packet would not be released. + */ + qtest_clock_step(qts, 2000000LL); + + /* + * Now we should be able to receive the packet through the redirector. + * The packet was released by filter-buffer and sent to filter-redirector, + * which forwarded it to the chardev socket. + */ + ret = recv(recv_sock, &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf)); + + ret = recv(recv_sock, recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + close(recv_sock); + close(backend_sock[0]); + unlink(sock_path); + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + qtest_add_func("/netfilter/change_interval_timer", + test_change_interval_timer); + return g_test_run(); +} From 5193528b9f7c4e161d8695c9217b97aef2410d1f Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sun, 4 Jan 2026 15:54:10 +0800 Subject: [PATCH 11/13] net/filter-redirector: add support for dynamic status on/off switching Currently, filter-redirector does not implement the status_changed callback, which means the 'status' property cannot be used to dynamically enable/disable the filter at runtime. When status is set to 'off' via QMP/HMP, the filter still receives data from the indev chardev because the chardev handlers remain registered. This patch adds proper support for the 'status' property: 1. Implement filter_redirector_status_changed() callback: - When status changes to 'off': remove chardev read handlers - When status changes to 'on': re-register chardev handlers (only if chardev is already open) 2. Update filter_redirector_setup() to respect initial status: - If filter is created with status=off, do not register handlers - This allows creating disabled filters via command line or QMP 3. Handle chardev OPENED/CLOSED events to re-arm handlers on reconnect: - Keep the chr_event callback installed on CLOSE so a later OPENED can re-register the read handlers when nf->on - Use qemu_chr_fe_set_handlers_full(..., set_open=false, sync_state=false) instead of qemu_chr_fe_set_handlers() because the latter forces sync_state=true and may emit CHR_EVENT_OPENED for an already-open backend. Doing that from inside the chr_event callback would cause recursive/re-entrant OPENED handling. Signed-off-by: Jason Wang --- net/filter-mirror.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/net/filter-mirror.c b/net/filter-mirror.c index d2bfde42e8..6ac28067a2 100644 --- a/net/filter-mirror.c +++ b/net/filter-mirror.c @@ -179,9 +179,16 @@ static void redirector_chr_event(void *opaque, QEMUChrEvent event) MirrorState *s = FILTER_REDIRECTOR(nf); switch (event) { + case CHR_EVENT_OPENED: + if (nf->on) { + qemu_chr_fe_set_handlers_full(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, false, false); + } + break; case CHR_EVENT_CLOSED: - qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL, - NULL, NULL, NULL, true); + qemu_chr_fe_set_handlers_full(&s->chr_in, NULL, NULL, redirector_chr_event, + NULL, nf, NULL, false, false); break; default: break; @@ -306,9 +313,11 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp) return; } - qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, - redirector_chr_read, redirector_chr_event, - NULL, nf, NULL, true); + if (nf->on) { + qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, true); + } } if (s->outdev) { @@ -324,6 +333,24 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp) } } +static void filter_redirector_status_changed(NetFilterState *nf, Error **errp) +{ + MirrorState *s = FILTER_REDIRECTOR(nf); + + if (!s->indev) { + return; + } + + if (nf->on) { + qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, true); + } else { + qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL, + NULL, NULL, NULL, true); + } +} + static char *filter_redirector_get_indev(Object *obj, Error **errp) { MirrorState *s = FILTER_REDIRECTOR(obj); @@ -440,6 +467,7 @@ static void filter_redirector_class_init(ObjectClass *oc, const void *data) nfc->setup = filter_redirector_setup; nfc->cleanup = filter_redirector_cleanup; nfc->receive_iov = filter_redirector_receive_iov; + nfc->status_changed = filter_redirector_status_changed; } static void filter_mirror_init(Object *obj) From 414af49791bd2a1751f975ce753c9ba3d949bdde Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sun, 4 Jan 2026 15:54:11 +0800 Subject: [PATCH 12/13] qtest: add a test to test redirector status change This patch adds a qtest to test the status change of filter-redirector. Two subtests were added: - test_redirector_status: tests dynamic on/off switching at runtime using qom-set QMP command - test_redirector_init_status_off: tests creating filter-redirector with status=off from the start via command line Both tests verify that: 1. When status is off, data from indev chardev is not received 2. When status is switched to on, data is received correctly Reviewed-by: Zhang Chen Signed-off-by: Jason Wang --- tests/qtest/test-filter-redirector.c | 192 +++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/tests/qtest/test-filter-redirector.c b/tests/qtest/test-filter-redirector.c index a996a80c1c..da0c126314 100644 --- a/tests/qtest/test-filter-redirector.c +++ b/tests/qtest/test-filter-redirector.c @@ -196,10 +196,202 @@ static void test_redirector_rx(void) qtest_quit(qts); } +/* + * Test filter-redirector status on/off switching. + * + * This test verifies that: + * 1. When status is set to "off", the filter stops receiving data from indev + * 2. When status is set back to "on", the filter resumes receiving data + */ +static void test_redirector_status(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + size = htonl(size); + QTestState *qts; + struct timeval tv; + fd_set rfds; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + /* + * Setup a simple rx path: + * chardev (sock_path0) -> filter-redirector -> socket backend + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0 ", + backend_sock[1], sock_path0); + + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + + /* send a qmp command to guarantee that 'connected' is setting to true. */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + /* + * Test 1: Set status to "off" and verify data is not received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'off' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + /* + * Use select with timeout to check if data arrives. + * When status is off, no data should arrive. + */ + FD_ZERO(&rfds); + FD_SET(backend_sock[0], &rfds); + tv.tv_sec = 0; + tv.tv_usec = 500000; /* 500ms timeout */ + ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv); + g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */ + + /* + * Test 2: Set status back to "on" and verify data is received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + g_free(recv_buf); + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); +} + +/* + * Test filter-redirector created with status=off. + * + * This test verifies that when a filter-redirector is created with + * status=off, it does not receive data until status is set to on. + */ +static void test_redirector_init_status_off(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + size = htonl(size); + QTestState *qts; + struct timeval tv; + fd_set rfds; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + /* + * Create filter-redirector with status=off from the start + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0,status=off ", + backend_sock[1], sock_path0); + + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + /* + * Test 1: Filter was created with status=off, data should not be received + */ + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + FD_ZERO(&rfds); + FD_SET(backend_sock[0], &rfds); + tv.tv_sec = 0; + tv.tv_usec = 500000; + ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv); + g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */ + + /* + * Test 2: Set status to "on" and verify data is received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + g_free(recv_buf); + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); qtest_add_func("/netfilter/redirector_tx", test_redirector_tx); qtest_add_func("/netfilter/redirector_rx", test_redirector_rx); + qtest_add_func("/netfilter/redirector_status", test_redirector_status); + qtest_add_func("/netfilter/redirector_init_status_off", + test_redirector_init_status_off); return g_test_run(); } From 014c06435f96c82d0e0be67832d40be43133dc71 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sun, 4 Jan 2026 15:54:12 +0800 Subject: [PATCH 13/13] tests/qtest: Add test for filter-redirector rx event opened Add a new test case 'test_redirector_rx_event_opened' to verify the handling of the CHR_EVENT_OPENED event in filter-redirector. The test simulates a scenario where the backend character device (socket) is disconnected and then reconnected. It works by: 1. Connecting to the redirector's socket (triggers CHR_EVENT_OPENED). 2. Sending a packet to verify initial connectivity. 3. Disconnecting (triggers CHR_EVENT_CLOSED). 4. Reconnecting (triggers CHR_EVENT_OPENED again). 5. Sending another packet to verify that the redirector correctly re-registers its handlers and resumes passing traffic. This ensures that the filter-redirector can recover and function correctly after a backend reconnection. Reviewed-by: Zhang Chen Signed-off-by: Jason Wang --- tests/qtest/test-filter-redirector.c | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/qtest/test-filter-redirector.c b/tests/qtest/test-filter-redirector.c index da0c126314..5540c232c0 100644 --- a/tests/qtest/test-filter-redirector.c +++ b/tests/qtest/test-filter-redirector.c @@ -385,6 +385,100 @@ static void test_redirector_init_status_off(void) qtest_quit(qts); } +static void test_redirector_rx_event_opened(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char send_buf2[] = "Hello2!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + uint32_t size2 = sizeof(send_buf2); + size = htonl(size); + size2 = htonl(size2); + QTestState *qts; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0 ", + backend_sock[1], sock_path0); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + struct iovec iov2[] = { + { + .iov_base = &size2, + .iov_len = sizeof(size2), + }, { + .iov_base = send_buf2, + .iov_len = sizeof(send_buf2), + }, + }; + + /* First connection */ + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + g_free(recv_buf); + + close(send_sock); + + /* Verify disconnection handling if needed, but mainly we want to test Reconnection */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* Second connection */ + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + ret = iov_send(send_sock, iov2, 2, 0, sizeof(size2) + sizeof(send_buf2)); + g_assert_cmpint(ret, ==, sizeof(send_buf2) + sizeof(size2)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf2)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf2); + g_free(recv_buf); + + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); + close(backend_sock[0]); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -393,5 +487,7 @@ int main(int argc, char **argv) qtest_add_func("/netfilter/redirector_status", test_redirector_status); qtest_add_func("/netfilter/redirector_init_status_off", test_redirector_init_status_off); + qtest_add_func("/netfilter/redirector_rx_event_opened", + test_redirector_rx_event_opened); return g_test_run(); }