From: Jason Wang <[email protected]> Add test cases to verify the indev/outdev counters for filter-redirector:
test_query_netfilter_stats_rx(): - Extended to verify indev counter in the counters array - Uses filter-redirector with indev to inject packets - Verifies indev packets/bytes are updated test_query_netfilter_stats_redirector_outdev(): - New test for outdev statistics - Uses filter-redirector with outdev configured - Verifies initial outdev counters are zero - Sends packet and verifies outdev packets/bytes are updated These tests verify the new flexible counters array design where filter-specific statistics are returned via the get_stats callback. Signed-off-by: Jason Wang <[email protected]> Signed-off-by: Cindy Lu <[email protected]> --- tests/qtest/test-netfilter.c | 371 +++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) diff --git a/tests/qtest/test-netfilter.c b/tests/qtest/test-netfilter.c index 326d4bd85f..20b0373cf7 100644 --- a/tests/qtest/test-netfilter.c +++ b/tests/qtest/test-netfilter.c @@ -9,6 +9,7 @@ */ #include "qemu/osdep.h" +#include "qemu/iov.h" #include "libqtest-single.h" #include "qobject/qdict.h" @@ -124,6 +125,371 @@ static void add_multi_netfilter(void) qobject_unref(response); } +/* + * Test query-netfilter-stats for TX direction using filter-mirror. + * + * Traffic flow: + * test_sock -> socket backend (bn0) -> filter-mirror (TX stats++) -> chardev + */ +static void test_query_netfilter_stats_tx(void) +{ + QTestState *qts; + QDict *response; + QList *stats_list; + const QListEntry *entry; + int send_sock[2], recv_sock[2]; + int ret; + char send_buf[] = "Hello TX stats!"; + uint32_t size = sizeof(send_buf); + uint32_t net_size; + bool found_filter = false; + + net_size = htonl(size); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, send_sock); + g_assert_cmpint(ret, !=, -1); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, recv_sock); + g_assert_cmpint(ret, !=, -1); + + qts = qtest_initf( + "-nic socket,id=bn0,fd=%d " + "-chardev socket,id=mirror0,fd=%d " + "-object filter-mirror,id=f0,netdev=bn0,queue=tx,outdev=mirror0", + send_sock[1], recv_sock[1]); + + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* Query initial stats */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + stats_list = qdict_get_qlist(response, "return"); + g_assert(stats_list); + + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "f0") == 0) { + found_filter = true; + g_assert_cmpint(qdict_get_int(stats, "packets-tx"), ==, 0); + g_assert_cmpint(qdict_get_int(stats, "bytes-tx"), ==, 0); + g_assert_cmpstr(qdict_get_str(stats, "type"), ==, "filter-mirror"); + } + } + g_assert(found_filter); + qobject_unref(response); + + /* Send packet - triggers TX direction */ + struct iovec iov[] = { + { .iov_base = &net_size, .iov_len = sizeof(net_size) }, + { .iov_base = send_buf, .iov_len = sizeof(send_buf) }, + }; + ret = iov_send(send_sock[0], iov, 2, 0, + sizeof(net_size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(net_size)); + + g_usleep(100000); + + /* Verify TX stats updated */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + stats_list = qdict_get_qlist(response, "return"); + + found_filter = false; + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "f0") == 0) { + found_filter = true; + g_assert_cmpint(qdict_get_int(stats, "packets-tx"), >=, 1); + g_assert_cmpint(qdict_get_int(stats, "bytes-tx"), >=, + sizeof(send_buf)); + } + } + g_assert(found_filter); + qobject_unref(response); + + close(send_sock[0]); + close(send_sock[1]); + close(recv_sock[0]); + close(recv_sock[1]); + qtest_quit(qts); +} + +/* + * Test query-netfilter-stats for RX direction using filter-redirector. + * + * Traffic flow: + * indev_sock -> chardev (indev) -> filter-redirector -> netdev (RX path) + * + * When filter-redirector reads from indev and calls redirector_to_filter(), + * it passes the packet to the next filter in the chain via + * qemu_netfilter_pass_to_next(). We add a second filter (filter-buffer) + * that will receive the packet and update its RX stats. + */ +static void test_query_netfilter_stats_rx(void) +{ + QTestState *qts; + QDict *response; + QList *stats_list; + const QListEntry *entry; + int backend_sock[2], indev_sock[2]; + int ret; + char send_buf[] = "Hello RX stats!"; + uint32_t size = sizeof(send_buf); + uint32_t net_size; + bool found_buffer = false; + + net_size = htonl(size); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, indev_sock); + g_assert_cmpint(ret, !=, -1); + + /* + * Setup: + * - filter-redirector (rd0, queue=rx, indev) at tail - injects data + * - filter-buffer (fb0, queue=rx) at head - receives and counts + * + * RX direction traverses from tail to head, so: + * indev -> rd0 (reads, calls pass_to_next) -> fb0 (receive_iov, RX stats++) + */ + qts = qtest_initf( + "-nic socket,id=bn0,fd=%d " + "-chardev socket,id=indev0,fd=%d " + "-object filter-buffer,id=fb0,netdev=bn0,queue=rx,interval=1000000,position=head " + "-object filter-redirector,id=rd0,netdev=bn0,queue=rx,indev=indev0,position=tail", + backend_sock[1], indev_sock[1]); + + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* Query initial stats - fb0 should have zero RX */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + stats_list = qdict_get_qlist(response, "return"); + g_assert(stats_list); + + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "fb0") == 0) { + found_buffer = true; + g_assert_cmpint(qdict_get_int(stats, "packets-rx"), ==, 0); + g_assert_cmpint(qdict_get_int(stats, "bytes-rx"), ==, 0); + g_assert_cmpstr(qdict_get_str(stats, "type"), ==, "filter-buffer"); + } + } + g_assert(found_buffer); + qobject_unref(response); + + /* Inject packet via indev - triggers RX direction */ + struct iovec iov[] = { + { .iov_base = &net_size, .iov_len = sizeof(net_size) }, + { .iov_base = send_buf, .iov_len = sizeof(send_buf) }, + }; + ret = iov_send(indev_sock[0], iov, 2, 0, + sizeof(net_size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(net_size)); + + g_usleep(100000); + + /* Verify fb0's RX stats and rd0's indev stats updated */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + stats_list = qdict_get_qlist(response, "return"); + + found_buffer = false; + bool found_redirector = false; + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "fb0") == 0) { + found_buffer = true; + g_assert_cmpint(qdict_get_int(stats, "packets-rx"), >=, 1); + g_assert_cmpint(qdict_get_int(stats, "bytes-rx"), >=, + sizeof(send_buf)); + } + if (g_strcmp0(name, "rd0") == 0) { + QList *counters; + const QListEntry *centry; + bool found_indev = false; + + found_redirector = true; + /* Verify indev counter in counters array */ + g_assert(qdict_haskey(stats, "counters")); + counters = qdict_get_qlist(stats, "counters"); + for (centry = qlist_first(counters); centry; + centry = qlist_next(centry)) { + QDict *counter = qobject_to(QDict, qlist_entry_obj(centry)); + const char *cname = qdict_get_str(counter, "name"); + if (g_strcmp0(cname, "indev") == 0) { + found_indev = true; + g_assert_cmpint(qdict_get_int(counter, "packets"), >=, 1); + g_assert_cmpint(qdict_get_int(counter, "bytes"), + >=, sizeof(send_buf)); + } + } + g_assert(found_indev); + } + } + g_assert(found_buffer); + g_assert(found_redirector); + qobject_unref(response); + + /* Test query by name */ + response = qtest_qmp(qts, + "{'execute': 'query-netfilter-stats'," + " 'arguments': { 'name': 'fb0' }}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + stats_list = qdict_get_qlist(response, "return"); + g_assert_cmpint(qlist_size(stats_list), ==, 1); + qobject_unref(response); + + /* Test error for non-existent filter */ + response = qtest_qmp(qts, + "{'execute': 'query-netfilter-stats'," + " 'arguments': { 'name': 'nonexistent' }}"); + g_assert(response); + g_assert(qdict_haskey(response, "error")); + qobject_unref(response); + + close(backend_sock[0]); + close(backend_sock[1]); + close(indev_sock[0]); + close(indev_sock[1]); + qtest_quit(qts); +} + +/* + * Test filter-redirector outdev statistics. + * + * Traffic flow: + * test_sock -> socket backend -> filter-redirector (outdev) -> chardev + * + * When data passes through filter-redirector with outdev configured, + * it sends the data to outdev and updates outdev statistics. + */ +static void test_query_netfilter_stats_redirector_outdev(void) +{ + QTestState *qts; + QDict *response; + QList *stats_list; + const QListEntry *entry; + int send_sock[2], recv_sock[2]; + int ret; + char send_buf[] = "Hello outdev stats!"; + uint32_t size = sizeof(send_buf); + uint32_t net_size; + bool found_redirector = false; + + net_size = htonl(size); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, send_sock); + g_assert_cmpint(ret, !=, -1); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, recv_sock); + g_assert_cmpint(ret, !=, -1); + + qts = qtest_initf( + "-nic socket,id=bn0,fd=%d " + "-chardev socket,id=outdev0,fd=%d " + "-object filter-redirector,id=rd0,netdev=bn0,queue=tx,outdev=outdev0", + send_sock[1], recv_sock[1]); + + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* Query initial stats */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + stats_list = qdict_get_qlist(response, "return"); + + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "rd0") == 0) { + QList *counters; + const QListEntry *centry; + bool found_outdev = false; + + found_redirector = true; + g_assert(qdict_haskey(stats, "counters")); + counters = qdict_get_qlist(stats, "counters"); + for (centry = qlist_first(counters); centry; + centry = qlist_next(centry)) { + QDict *counter = qobject_to(QDict, qlist_entry_obj(centry)); + const char *cname = qdict_get_str(counter, "name"); + if (g_strcmp0(cname, "outdev") == 0) { + found_outdev = true; + g_assert_cmpint(qdict_get_int(counter, "packets"), ==, 0); + g_assert_cmpint(qdict_get_int(counter, "bytes"), ==, 0); + } + } + g_assert(found_outdev); + } + } + g_assert(found_redirector); + qobject_unref(response); + + /* Send packet - triggers TX direction and outdev */ + struct iovec iov[] = { + { .iov_base = &net_size, .iov_len = sizeof(net_size) }, + { .iov_base = send_buf, .iov_len = sizeof(send_buf) }, + }; + ret = iov_send(send_sock[0], iov, 2, 0, + sizeof(net_size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(net_size)); + + g_usleep(100000); + + /* Verify outdev stats updated */ + response = qtest_qmp(qts, "{'execute': 'query-netfilter-stats'}"); + g_assert(response); + stats_list = qdict_get_qlist(response, "return"); + + found_redirector = false; + for (entry = qlist_first(stats_list); entry; entry = qlist_next(entry)) { + QDict *stats = qobject_to(QDict, qlist_entry_obj(entry)); + const char *name = qdict_get_str(stats, "name"); + if (g_strcmp0(name, "rd0") == 0) { + QList *counters; + const QListEntry *centry; + bool found_outdev = false; + + found_redirector = true; + /* TX stats should also be updated */ + g_assert_cmpint(qdict_get_int(stats, "packets-tx"), >=, 1); + + /* Verify outdev counter */ + counters = qdict_get_qlist(stats, "counters"); + for (centry = qlist_first(counters); centry; + centry = qlist_next(centry)) { + QDict *counter = qobject_to(QDict, qlist_entry_obj(centry)); + const char *cname = qdict_get_str(counter, "name"); + if (g_strcmp0(cname, "outdev") == 0) { + found_outdev = true; + g_assert_cmpint(qdict_get_int(counter, "packets"), >=, 1); + g_assert_cmpint(qdict_get_int(counter, "bytes"), + >=, sizeof(send_buf)); + } + } + g_assert(found_outdev); + } + } + g_assert(found_redirector); + qobject_unref(response); + + close(send_sock[0]); + close(send_sock[1]); + close(recv_sock[0]); + close(recv_sock[1]); + qtest_quit(qts); +} + /* add multi(2) netfilters to a netdev and then remove the netdev */ static void remove_netdev_with_multi_netfilter(void) { @@ -187,6 +553,11 @@ int main(int argc, char **argv) qtest_add_func("/netfilter/remove_netdev_multi", remove_netdev_with_multi_netfilter); + qtest_add_func("/netfilter/query_stats_tx", test_query_netfilter_stats_tx); + qtest_add_func("/netfilter/query_stats_rx", test_query_netfilter_stats_rx); + qtest_add_func("/netfilter/query_stats_redirector_outdev", + test_query_netfilter_stats_redirector_outdev); + args = g_strdup_printf("-nic user,id=qtest-bn0"); qtest_start(args); ret = g_test_run(); -- 2.52.0
