From nobody Fri Nov 21 22:44:34 2025 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4dCqz64jF1z6HtRx for ; Fri, 21 Nov 2025 22:44:34 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4dCqz6411Yz46Hf for ; Fri, 21 Nov 2025 22:44:34 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1763765074; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=T+koJZ+qu6kCiuo4PMMTndCfZ9zCcuPuD3DR+HWwJ4E=; b=Y0Ua8KEsVQ6VXi1ostTvbuKH5GTetJCJ7tu7+Q8xx7W3nsFS8nsxH9LqRdTZZhDqkM1ySB eM/WcvuOK8Fqc/cB44StkSjRoesb23XyimnIltuu7dLwu1UIg9OnFLuW5QXRUUUQnxgBWf OC5rKbIhJA/dtyIh6u1ljlOOIQffR/akQChp8pSN8KIkHeMj8SrIZCZ9rl5o5R8yQBaaHV w21izXwB2ywqWNZEOQdmjblSRwI2J97UVGkZQ7bWHyzno+MlWkGczOQCzMgEApgmv3OZK7 DyLgwluN6Vl9fhVVUoDanJq0lWA3/4XIY5JHSt12C2DzqD3wjqv9Hl+FGjXttg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1763765074; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=T+koJZ+qu6kCiuo4PMMTndCfZ9zCcuPuD3DR+HWwJ4E=; b=CtUaabfnNgppY8i2wVB4TUpaRbQWo+BBkGy9MkaGosTQ1ljvLnNix7xFD8AywU+0JQhgrR w1HuJDnXJ9TuxxTLga6yy7YtwBU3bcE0ILn3WKboH30RcAy4LD+1MgDJ0KmdbQmXEJmMqA qWHxuelakGX993sURcprnyd6Qnua3tqydSQpz2uReIulwN/aBZQ1oDgdAloTq3f/L6XyUK CQsWLdqC11hGgHlxQ22977Q6BxA3kGzHOalc8D+NkhsM3RQqsUGIxIh3EFpz/PVPCsNwNB DqIsCnExkHtgxxtCEdYk9YQDkQAxQmjPomvfLtJm/CHhpN0o2/yrlBfXHYO3cg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1763765074; a=rsa-sha256; cv=none; b=LB9yFv/9TyFUDu2fa+t40MzXZM1gGI9Pwi19wDx7xMx5Q6kA7lmWVDcKuQmhv5nQFN6pgx vkXyCbgy/6IifkpKJ9bEgqIIgLIGNakU1vVLz7mo/t6NddHqTcSe7nIAvw0iFI1X1YlIIV 3MXG0gPQmR+h0MUvvqenx9ZTcKaeqNXzJNnzRA0TUs8y1sfsm4J/3ruJ2ghV8DQY/xWZCI AveO5ZcUwAAFg4XhT6waXiuD7SSV04/lPGiV/Hw6eZ9hhw9HFnY5sfGSoMfolCWcM09rXJ 4tJVSZZ7lQzRnVH0MtorbkOpoLzTajwHphbC73490FEovPpQbBHXTZua0l7ExA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4dCqz63TTwzXgX for ; Fri, 21 Nov 2025 22:44:34 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3f8f6 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Fri, 21 Nov 2025 22:44:34 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Gleb Smirnoff Subject: git: 1ea3eda3d016 - main - tests/net: add some bpf(4) tests List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: glebius X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 1ea3eda3d016e62434eb9cc3576faa911fd88034 Auto-Submitted: auto-generated Date: Fri, 21 Nov 2025 22:44:34 +0000 Message-Id: <6920eb52.3f8f6.7c358087@gitrepo.freebsd.org> The branch main has been updated by glebius: URL: https://cgit.FreeBSD.org/src/commit/?id=1ea3eda3d016e62434eb9cc3576faa911fd88034 commit 1ea3eda3d016e62434eb9cc3576faa911fd88034 Author: Gleb Smirnoff AuthorDate: 2025-11-21 22:43:47 +0000 Commit: Gleb Smirnoff CommitDate: 2025-11-21 22:43:47 +0000 tests/net: add some bpf(4) tests A test helper program pcap-test allows to capture, inject and compare. Build a simple test case on top of it. More test cases can be easily constructed. --- tests/sys/net/bpf/Makefile | 4 +- tests/sys/net/bpf/bpf.sh | 61 +++++++++- tests/sys/net/bpf/pcap-test.c | 268 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+), 3 deletions(-) diff --git a/tests/sys/net/bpf/Makefile b/tests/sys/net/bpf/Makefile index 9c8a25b15d16..641d1aaef676 100644 --- a/tests/sys/net/bpf/Makefile +++ b/tests/sys/net/bpf/Makefile @@ -7,8 +7,10 @@ BINDIR= ${TESTSDIR} LIBADD+= nv -PROGS= bpf_multi_read +PROGS= bpf_multi_read pcap-test LIBADD.bpf_multi_read+= pcap +CFLAGS.pcap-test.c+= -Wno-cast-align +LIBADD.pcap-test+= pcap ATF_TESTS_SH= bpf diff --git a/tests/sys/net/bpf/bpf.sh b/tests/sys/net/bpf/bpf.sh index 2830c4862de9..f2d647b61de0 100644 --- a/tests/sys/net/bpf/bpf.sh +++ b/tests/sys/net/bpf/bpf.sh @@ -32,7 +32,6 @@ multi_read_head() atf_set descr 'Test multiple readers on /dev/bpf' atf_set require.user root } - multi_read_body() { vnet_init @@ -55,13 +54,71 @@ multi_read_body() # Now let this run for 10 seconds sleep 10 } - multi_read_cleanup() { vnet_cleanup } +atf_test_case "inject" "cleanup" +inject_head() +{ + atf_set descr 'Catch packets, re-inject and check' + atf_set require.user root +} +inject_body() +{ + vnet_init + + epair=$(vnet_mkepair) + ifconfig ${epair}a inet 192.0.2.1/24 up + vnet_mkjail alcatraz ${epair}b + jexec alcatraz ifconfig ${epair}b inet 192.0.2.2/24 up + + in=$(pwd)/$(mktemp in.pcap.XXXXXXXXXX) + in2=$(pwd)/$(mktemp in2.pcap.XXXXXXXXXX) + out=$(pwd)/$(mktemp out.pcap.XXXXXXXXXX) + + # write dump on jail side, with "in" direction + jexec alcatraz $(atf_get_srcdir)/pcap-test \ + capture epair0b $in 3 in > out & pid=$! + while ! jexec alcatraz netstat -B | grep -q epair0b.*pcap-test; do + sleep 0.01; + done + atf_check -s exit:0 -o ignore ping -c 3 -i 0.1 192.0.2.2 + atf_check -s exit:0 sh -c "wait $pid; exit $?" + atf_check -s exit:0 -o empty cat out + + # inject dump on host side, recording on both sides + jexec alcatraz $(atf_get_srcdir)/pcap-test \ + capture epair0b $in2 3 in > jout & jpid=$! + while ! jexec alcatraz netstat -B | grep -q epair0b.*pcap-test; do + sleep 0.01; + done + $(atf_get_srcdir)/pcap-test \ + capture epair0a $out 3 out > hout & hpid=$! + while ! netstat -B | grep -q epair0a.*pcap-test; do + sleep 0.01; + done + atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \ + inject epair0a $in 3 + atf_check -s exit:0 sh -c "wait $jpid; exit $?" + atf_check -s exit:0 -o empty cat jout + atf_check -s exit:0 sh -c "wait $hpid; exit $?" + atf_check -s exit:0 -o empty cat hout + + # all 3 dumps should be equal + atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \ + compare $in $out + atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \ + compare $in $in2 +} +inject_cleanup() +{ + vnet_cleanup +} + atf_init_test_cases() { atf_add_test_case "multi_read" + atf_add_test_case "inject" } diff --git a/tests/sys/net/bpf/pcap-test.c b/tests/sys/net/bpf/pcap-test.c new file mode 100644 index 000000000000..9d01548f7aae --- /dev/null +++ b/tests/sys/net/bpf/pcap-test.c @@ -0,0 +1,268 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Gleb Smirnoff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +strtolerr(const char *s) +{ + int rv; + + if ((rv = (int)strtol(s, NULL, 10)) < 1) + errx(1, "bad count %s", s); + return (rv); +} + +static pcap_direction_t +strtodir(const char *s) +{ + static const struct dirstr { + const char *str; + pcap_direction_t dir; + } dirs[] = { + { "in", PCAP_D_IN }, + { "out", PCAP_D_OUT }, + { "both", PCAP_D_INOUT }, + { "inout", PCAP_D_INOUT }, + }; + + for (u_int i = 0; i < nitems(dirs); i++) + if (strcasecmp(s, dirs[i].str) == 0) + return (dirs[i].dir); + errx(1, "bad directions %s", s); +} + +static char errbuf[PCAP_ERRBUF_SIZE]; + +static pcap_t * +pcap_open(const char *name, pcap_direction_t dir) +{ + pcap_t *p; + + if ((p = pcap_create(name, errbuf)) == NULL) + errx(1, "pcap_create: %s", errbuf); + if (pcap_set_timeout(p, 10) != 0) + errx(1, "pcap_set_timeout: %s", pcap_geterr(p)); + if (pcap_activate(p) != 0) + errx(1, "pcap_activate: %s", errbuf); + if (pcap_setdirection(p, dir) != 0) + errx(1, "pcap_setdirection: %s", pcap_geterr(p)); + return (p); +} + +#if 0 +/* + * Deal with the FreeBSD writer only optimization hack in bpf(4). + * Needed only when net.bpf.optimize_writers=1. + */ +static pcap_t * +pcap_rwopen(const char *name, pcap_direction_t dir) +{ + pcap_t *p; + struct bpf_program fp; + + p = pcap_open(name, dir); + if (pcap_compile(p, &fp, "", 0, PCAP_NETMASK_UNKNOWN) != 0) + errx(1, "pcap_compile: %s", pcap_geterr(p)); + if (pcap_setfilter(p, &fp) != 0) + errx(1, "pcap_setfilter: %s", pcap_geterr(p)); + pcap_freecode(&fp); + return (p); +} +#endif + +static void +list(int argc __unused, char *argv[] __unused) +{ + pcap_if_t *all, *p; + + if (pcap_findalldevs(&all, errbuf) != 0) + errx(1, "pcap_findalldevs: %s", errbuf); + for (p = all; p != NULL; p = p->next) + printf("%s ", p->name); + printf("\n"); + pcap_freealldevs(all); +} + +/* args: tap file count direction */ +static void +capture(int argc __unused, char *argv[]) +{ + pcap_t *p; + pcap_dumper_t *d; + pcap_direction_t dir; + int cnt; + + cnt = strtolerr(argv[2]); + dir = strtodir(argv[3]); + p = pcap_open(argv[0], dir); + + if ((d = pcap_dump_open(p, argv[1])) == NULL) + errx(1, "pcap_dump_open: %s", pcap_geterr(p)); + + if (pcap_loop(p, cnt, pcap_dump, (u_char *)d) != 0) + errx(1, "pcap_loop: %s", pcap_geterr(p)); + pcap_dump_close(d); +} + +static void +inject_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes) +{ + pcap_t *p = (pcap_t *)user; + + if (h->caplen != h->len) + errx(1, "incomplete packet %u of %u", h->caplen, h->len); + + if (pcap_inject(p, bytes, h->caplen) != (int)h->caplen) + errx(1, "pcap_inject: %s", errbuf); +} + +/* args: tap file count */ +static void +inject(int argc __unused, char *argv[]) +{ + pcap_t *p, *d; + int cnt; + + cnt = strtolerr(argv[2]); + p = pcap_open(argv[0], PCAP_D_INOUT); + + if ((d = pcap_open_offline(argv[1], errbuf)) == NULL) + errx(1, "pcap_open_offline: %s", errbuf); + if (pcap_loop(d, cnt, inject_packet, (u_char *)p) != 0) + errx(1, "pcap_loop: %s", pcap_geterr(p)); + pcap_close(p); + pcap_close(d); +} + +struct packet { + STAILQ_ENTRY(packet) next; + const void *data; + u_int caplen; + u_int len; +}; +STAILQ_HEAD(plist, packet); + +static void +store_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes) +{ + struct plist *list = (struct plist *)user; + struct packet *p; + + p = malloc(sizeof(*p)); + p->data = bytes; + p->caplen = h->caplen; + p->len = h->len; + STAILQ_INSERT_TAIL(list, p, next); +} + +/* args: file1 file2 */ +static void +compare(int argc __unused, char *argv[]) +{ + pcap_t *f1, *f2; + struct plist + list1 = STAILQ_HEAD_INITIALIZER(list1), + list2 = STAILQ_HEAD_INITIALIZER(list2); + struct packet *p1, *p2; + u_int cnt; + + if ((f1 = pcap_open_offline(argv[0], errbuf)) == NULL) + errx(1, "pcap_open_offline: %s", errbuf); + if (pcap_loop(f1, 0, store_packet, (u_char *)&list1) != 0) + errx(1, "pcap_loop: %s", pcap_geterr(f1)); + if ((f2 = pcap_open_offline(argv[1], errbuf)) == NULL) + errx(1, "pcap_open_offline: %s", errbuf); + if (pcap_loop(f2, 0, store_packet, (u_char *)&list2) != 0) + errx(1, "pcap_loop: %s", pcap_geterr(f2)); + + for (p1 = STAILQ_FIRST(&list1), p2 = STAILQ_FIRST(&list2), cnt = 1; + p1 != NULL && p2 != NULL; + p1 = STAILQ_NEXT(p1, next), p2 = STAILQ_NEXT(p2, next), cnt++) { + if (p1->len != p2->len) + errx(1, "packet #%u length %u != %u", + cnt, p1->len, p2->len); + if (p1->caplen != p2->caplen) + errx(1, "packet #%u capture length %u != %u", + cnt, p1->caplen, p2->caplen); + if (memcmp(p1->data, p2->data, p1->caplen) != 0) + errx(1, "packet #%u payload different", cnt); + } + if (p1 != NULL || p2 != NULL) + errx(1, "packet count different"); + + pcap_close(f1); + pcap_close(f2); +} + +static const struct cmd { + const char *cmd; + void (*func)(int, char **); + u_int argc; +} cmds[] = { + { .cmd = "list", .func = list, .argc = 0 }, + { .cmd = "inject", .func = inject, .argc = 3 }, + { .cmd = "capture", .func = capture,.argc = 4 }, + { .cmd = "compare", .func = compare,.argc = 2 }, +}; + +int +main(int argc, char *argv[]) +{ + + if (argc < 2) { + fprintf(stderr, "Usage: %s ", argv[0]); + for (u_int i = 0; i < nitems(cmds); i++) + fprintf(stderr, "%s%s", cmds[i].cmd, + i != nitems(cmds) - 1 ? "|" : "\n"); + exit(1); + } + + for (u_int i = 0; i < nitems(cmds); i++) + if (strcasecmp(argv[1], cmds[i].cmd) == 0) { + argc -= 2; + argv += 2; + if (argc < (int)cmds[i].argc) + errx(1, "%s takes %u args", + cmds[i].cmd, cmds[i].argc); + cmds[i].func(argc, argv); + return (0); + } + + warnx("Unknown command %s\n", argv[1]); + return (1); +}