diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -837,6 +837,8 @@ netlink .. netmap + ifnet + .. .. netpfil common diff --git a/tests/sys/netmap/Makefile b/tests/sys/netmap/Makefile --- a/tests/sys/netmap/Makefile +++ b/tests/sys/netmap/Makefile @@ -12,4 +12,6 @@ LIBADD+= pthread LIBADD+= netmap +TESTS_SUBDIRS+= ifnet + .include diff --git a/tests/sys/netmap/ifnet/Makefile b/tests/sys/netmap/ifnet/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/Makefile @@ -0,0 +1,10 @@ +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/netmap/ifnet + +ATF_TESTS_SH= ifnet_test + +SUBDIR+= pkt-gen +SUBDIR+= simplebridge + +.include diff --git a/tests/sys/netmap/ifnet/ifnet_test.sh b/tests/sys/netmap/ifnet/ifnet_test.sh new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/ifnet_test.sh @@ -0,0 +1,226 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2023 Klara, Inc. + +# +# Test basic netmap functionality with software interfaces. +# +# Two netmap applications are used: pkt-gen, and a simplified version of bridge +# which reports the number of packets forwarded in each direction. +# + +. $(atf_get_srcdir)/../../common/vnet.subr + +PKTGEN=$(atf_get_srcdir)/pkt-gen +NMBRIDGE=$(atf_get_srcdir)/simplebridge +BRIDGEPIDFILE=bridge.pid + +# Start a process that forwards packets between two interfaces in netmap mode. +join_interfaces() +{ + local jail ifa ifb outf + + jail=$1 + ifa=$2 + ifb=$3 + outf=$4 + + jexec $jail $NMBRIDGE -i $ifa -i $ifb >$outf 2>&1 & + echo $! > $BRIDGEPIDFILE + sleep 1 # Let the netmap program initialize itself. +} + +# Stop a bridge process previously started by join_interfaces(). +unjoin_interfaces() +{ + atf_check pkill -INT -F $BRIDGEPIDFILE + atf_check rm $BRIDGEPIDFILE +} + +# Return the ethernet address of the specified interface. +iface_etheraddr() +{ + local jail iface + + jail=$1 + iface=$2 + + jexec $jail ifconfig $iface ether | awk '/ether/{print $2}' +} + +# +# Verify that a netmap-enabled if_bridge interface can forward packets between +# interfaces. +# +atf_test_case "bridge_l2_forwarding" "cleanup" +bridge_l2_forwarding_head() +{ + atf_set descr 'Make sure that L2 forwarding works in netmap mode' + atf_set require.user root +} +bridge_l2_forwarding_body() +{ + vnet_init + + epair_left=$(vnet_mkepair) + epair_right=$(vnet_mkepair) + bridge=$(vnet_mkbridge) + + vnet_mkjail bridge ${bridge} ${epair_left}b ${epair_right}b + vnet_mkjail left ${epair_left}a + vnet_mkjail right ${epair_right}a + + jexec bridge ifconfig ${epair_left}b up + jexec bridge ifconfig ${epair_right}b up + jexec bridge ifconfig ${bridge} up addm ${epair_left}b addm ${epair_right}b + + jexec left ifconfig ${epair_left}a inet 169.254.0.1/16 + jexec right ifconfig ${epair_right}a inet 169.254.0.2/16 + + # Let the endpoints communicate without needing to ARP. + macleft=$(iface_etheraddr left ${epair_left}a) + jexec right arp -s 169.254.0.1 $macleft + macright=$(iface_etheraddr right ${epair_right}a) + jexec left arp -s 169.254.0.2 $macright + + join_interfaces bridge netmap:${bridge} netmap:${bridge}^ pktcount + + # Send five pings from each end to the other. + atf_check -o ignore jexec left ping -i 0.2 -t 3 -c 5 169.254.0.2 + atf_check -o ignore jexec right ping -i 0.2 -t 3 -c 5 169.254.0.1 + + unjoin_interfaces + + # The pings above should generate 10 echo requests and 10 echo replies, + # so we should have 20 packets arrive on the bridge, and we don't expect + # to see any packets transmitted from the bridge host. + atf_check -o match:"[[:space:]]1 pktcount" wc -l pktcount + atf_check -o inline:'20 0\n' cat pktcount +} +bridge_l2_forwarding_cleanup() +{ + vnet_cleanup +} + +# +# Verify that a netmap-enabled if_bridge interface can receive packets locally. +# +atf_test_case "bridge_l3" "cleanup" +bridge_l3_head() +{ + atf_set descr 'Test that a bridge interface can receive packets locally in netmap mode' + atf_set require.user root +} +bridge_l3_body() +{ + vnet_init + + epair=$(vnet_mkepair) + bridge=$(vnet_mkbridge) + + vnet_mkjail bridge ${bridge} ${epair}b + vnet_mkjail host ${epair}a + + jexec bridge ifconfig ${epair}b up + jexec bridge ifconfig ${bridge} up addm ${epair}b + jexec bridge ifconfig ${bridge} inet 169.254.0.2/16 + + jexec host ifconfig ${epair}a inet 169.254.0.1/16 + + machost=$(iface_etheraddr host ${epair}a) + jexec bridge arp -s 169.254.0.1 $machost + macbridge=$(iface_etheraddr bridge ${bridge}) + jexec host arp -s 169.254.0.2 $macbridge + + join_interfaces bridge netmap:${bridge} netmap:${bridge}^ pktcount + + # Send five pings from each end to the other. + atf_check -o ignore jexec host ping -i 0.2 -t 3 -c 5 169.254.0.2 + atf_check -o ignore jexec bridge ping -i 0.2 -t 3 -c 5 169.254.0.1 + + unjoin_interfaces + + # The pings above should generate 10 echo requests and 10 echo replies. + # We should see 10 packets arrive on the bridge, and 10 packets sent via + # the bridge. + atf_check -o match:"[[:space:]]1 pktcount" wc -l pktcount + atf_check -o inline:'10 10\n' cat pktcount +} +bridge_l3_cleanup() +{ + vnet_cleanup +} + +# +# Use netmap to transfer packets over an epair. +# +atf_test_case "epair_simple" "cleanup" +epair_simple_head() +{ + atf_set descr 'Test epair interfaces can be used in netmap mode' + atf_set require.user root + atf_set timeout 10 +} +epair_simple_body() +{ + vnet_init + + epair=$(vnet_mkepair) + + ifconfig ${epair}a up + ifconfig ${epair}b up + + $PKTGEN -i netmap:${epair}a -f rx -n 100000 & + sleep 1 # Give pkt-gen a chance to start. + + atf_check -o ignore -e ignore $PKTGEN -i netmap:${epair}b -f tx -n 100000 + + wait +} +epair_simple_cleanup() +{ + vnet_cleanup +} + +# +# Verify that pkt-gen can transfer packets over a vlan. +# +atf_test_case "vlan_simple" "cleanup" +vlan_simple_head() +{ + atf_set descr 'Test that vlan interfaces can be used in netmap mode' + atf_set require.user root +} +vlan_simple_body() +{ + vnet_init + + epair=$(vnet_mkepair) + vlana=$(vnet_mkvlan) + vlanb=$(vnet_mkvlan) + + ifconfig ${epair}a up + ifconfig ${vlana} up vlan 42 vlandev ${epair}a + + ifconfig ${epair}b up + ifconfig ${vlanb} up vlan 42 vlandev ${epair}b + + $PKTGEN -i netmap:${vlana} -f rx -n 100000 & + sleep 1 # Give pkt-gen a chance to start. + + atf_check -o ignore -e ignore $PKTGEN -i netmap:${vlanb} -f tx -n 100000 + + wait +} +vlan_simple_cleanup() +{ + vnet_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case bridge_l2_forwarding + atf_add_test_case bridge_l3 + atf_add_test_case epair_simple + atf_add_test_case vlan_simple +} diff --git a/tests/sys/netmap/ifnet/pkt-gen/Makefile b/tests/sys/netmap/ifnet/pkt-gen/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/pkt-gen/Makefile @@ -0,0 +1,13 @@ +.PATH: ${SRCTOP}/tools/tools/netmap + +BINDIR= ${TESTSBASE}/sys/netmap/ifnet + +PROG= pkt-gen + +CFLAGS+= -DNO_PCAP +LIBADD= netmap pthread +WARNS?= 3 + +MK_MAN= no + +.include diff --git a/tests/sys/netmap/ifnet/simplebridge/Makefile b/tests/sys/netmap/ifnet/simplebridge/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/simplebridge/Makefile @@ -0,0 +1,7 @@ +BINDIR= ${TESTSBASE}/sys/netmap/ifnet + +PROG= simplebridge +LIBADD= netmap +MK_MAN= no + +.include diff --git a/tests/sys/netmap/ifnet/simplebridge/simplebridge.c b/tests/sys/netmap/ifnet/simplebridge/simplebridge.c new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/simplebridge/simplebridge.c @@ -0,0 +1,215 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (C) 2011-2014 Luigi Rizzo, Matteo Landi + * + * A trimmed-down version of tools/tools/netmap/bridge.c which prints the number + * of packets sent in each direction before exiting. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +static int do_abort = 0; + +static void +sigint_h(int sig __unused) +{ + do_abort = 1; + signal(SIGINT, SIG_DFL); +} + +/* + * How many slots do we (user application) have on this + * set of queues ? + */ +static int +rx_slots_avail(struct nmport_d *d) +{ + u_int i, tot = 0; + + for (i = d->first_rx_ring; i <= d->last_rx_ring; i++) { + tot += nm_ring_space(NETMAP_RXRING(d->nifp, i)); + } + + return tot; +} + +/* + * Move up to 'limit' pkts from rxring to txring, swapping buffers + * if zerocopy is possible. Otherwise fall back on packet copying. + */ +static int +rings_move(struct netmap_ring *rxring, struct netmap_ring *txring, u_int limit) +{ + u_int j, k, m = 0; + + assert(rxring->flags == 0); + assert(txring->flags == 0); + + j = rxring->head; + k = txring->head; + m = nm_ring_space(rxring); + if (m < limit) + limit = m; + m = nm_ring_space(txring); + if (m < limit) + limit = m; + m = limit; + while (limit-- > 0) { + struct netmap_slot *rs = &rxring->slot[j]; + struct netmap_slot *ts = &txring->slot[k]; + uint32_t pkt; + + ts->len = rs->len; + pkt = ts->buf_idx; + ts->buf_idx = rs->buf_idx; + rs->buf_idx = pkt; + /* report the buffer change. */ + ts->flags |= NS_BUF_CHANGED; + rs->flags |= NS_BUF_CHANGED; + + /* + * Copy the NS_MOREFRAG from rs to ts, leaving any + * other flags unchanged. + */ + ts->flags = (ts->flags & ~NS_MOREFRAG) | (rs->flags & NS_MOREFRAG); + j = nm_ring_next(rxring, j); + k = nm_ring_next(txring, k); + } + rxring->head = rxring->cur = j; + txring->head = txring->cur = k; + + return (m); +} + +/* Move packets from source port to destination port. */ +static int +ports_move(struct nmport_d *src, struct nmport_d *dst, u_int limit) +{ + struct netmap_ring *txring, *rxring; + u_int m = 0, si = src->first_rx_ring, di = dst->first_tx_ring; + + while (si <= src->last_rx_ring && di <= dst->last_tx_ring) { + rxring = NETMAP_RXRING(src->nifp, si); + txring = NETMAP_TXRING(dst->nifp, di); + if (nm_ring_empty(rxring)) { + si++; + continue; + } + if (nm_ring_empty(txring)) { + di++; + continue; + } + m += rings_move(rxring, txring, limit); + } + + return (m); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-h] -i -i \n", + getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct pollfd pollfd[2]; + struct nmport_d *pa = NULL, *pb = NULL; + char *ifa = NULL, *ifb = NULL; + uint64_t atob, btoa; + int ch; + + while ((ch = getopt(argc, argv, "hi:")) != -1) { + switch (ch) { + default: + case 'h': + usage(); + break; + case 'i': + if (ifa == NULL) + ifa = optarg; + else if (ifb == NULL) + ifb = optarg; + else + D("%s ignored, already have 2 interfaces", + optarg); + break; + } + + } + argc -= optind; + argv += optind; + + if (argc != 0 || ifa == NULL || ifb == NULL) + usage(); + if (strcmp(ifa, ifb) == 0) + errx(1, "specific interfaces must be distinct"); + + pa = nmport_open(ifa); + if (pa == NULL) + errx(1, "cannot open %s", ifa); + pb = nmport_open(ifb); + if (pb == NULL) + errx(1, "cannot open %s", ifa); + + memset(pollfd, 0, sizeof(pollfd)); + pollfd[0].fd = pa->fd; + pollfd[1].fd = pb->fd; + + signal(SIGINT, sigint_h); + + atob = btoa = 0; + while (!do_abort) { + int n0, n1, ret; + + pollfd[0].events = pollfd[1].events = 0; + pollfd[0].revents = pollfd[1].revents = 0; + + n0 = rx_slots_avail(pa); + n1 = rx_slots_avail(pb); + if (n0) + pollfd[1].events |= POLLOUT; + else + pollfd[0].events |= POLLIN; + if (n1) + pollfd[0].events |= POLLOUT; + else + pollfd[1].events |= POLLIN; + + ret = poll(pollfd, 2, -1); + if (ret < 0) { + if (errno == EINTR) + break; + err(1, "poll"); + } + if (pollfd[0].revents & POLLERR) + errx(1, "poll error on iface1"); + if (pollfd[1].revents & POLLERR) + errx(1, "poll error on iface2"); + if (pollfd[0].revents & POLLOUT) + btoa += ports_move(pb, pa, 128); + if (pollfd[1].revents & POLLOUT) + atob += ports_move(pa, pb, 128); + } + nmport_close(pb); + nmport_close(pa); + + printf("%ju %ju\n", (uintmax_t)atob, (uintmax_t)btoa); + + return (0); +} diff --git a/tests/sys/netmap/ifnet/tools/Makefile b/tests/sys/netmap/ifnet/tools/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/netmap/ifnet/tools/Makefile @@ -0,0 +1,8 @@ +PROGS= bridge + +BINDIR= ${TESTSBASE}/sys/netmap/if_bridge/tools +MK_MAN= no +LIBADD.bridge= netmap +WARNS= 6 + +.include