aboutsummaryrefslogtreecommitdiff
path: root/tools/tools/netmap/pkt-gen.c
diff options
context:
space:
mode:
authorLuigi Rizzo <luigi@FreeBSD.org>2011-11-17 12:17:39 +0000
committerLuigi Rizzo <luigi@FreeBSD.org>2011-11-17 12:17:39 +0000
commit68b8534bdfeb5078e84d668124e7585e43b03502 (patch)
tree8be7a4f824011375a281269e79b86fa172e84386 /tools/tools/netmap/pkt-gen.c
parenta93c40bb620da1a24e2e07b9bc0734736c1dae77 (diff)
downloadsrc-68b8534bdfeb5078e84d668124e7585e43b03502.tar.gz
src-68b8534bdfeb5078e84d668124e7585e43b03502.zip
Bring in support for netmap, a framework for very efficient packet
I/O from userspace, capable of line rate at 10G, see http://info.iet.unipi.it/~luigi/netmap/ At this time I am bringing in only the generic code (sys/dev/netmap/ plus two headers under sys/net/), and some sample applications in tools/tools/netmap. There is also a manpage in share/man/man4 [1] In order to make use of the framework you need to build a kernel with "device netmap", and patch individual drivers with the code that you can find in sys/dev/netmap/head.diff The file will go away as the relevant pieces are committed to the various device drivers, which should happen in a few days after talking to the driver maintainers. Netmap support is available at the moment for Intel 10G and 1G cards (ixgbe, em/lem/igb), and for the Realtek 1G card ("re"). I have partial patches for "bge" and am starting to work on "cxgbe". Hopefully changes are trivial enough so interested third parties can submit their patches. Interested people can contact me for advice on how to add netmap support to specific devices. CREDITS: Netmap has been developed by Luigi Rizzo and other collaborators at the Universita` di Pisa, and supported by EU project CHANGE (http://www.change-project.eu/) The code is distributed under a BSD Copyright. [1] In my opinion is a bad idea to have all manpage in one directory. We should place kernel documentation in the same dir that contains the code, which would make it much simpler to keep doc and code in sync, reduce the clutter in share/man/ and incidentally is the policy used for all of userspace code. Makefiles and doc tools can be trivially adjusted to find the manpages in the relevant subdirs.
Notes
Notes: svn path=/head/; revision=227614
Diffstat (limited to 'tools/tools/netmap/pkt-gen.c')
-rw-r--r--tools/tools/netmap/pkt-gen.c1021
1 files changed, 1021 insertions, 0 deletions
diff --git a/tools/tools/netmap/pkt-gen.c b/tools/tools/netmap/pkt-gen.c
new file mode 100644
index 000000000000..747bd9dde00b
--- /dev/null
+++ b/tools/tools/netmap/pkt-gen.c
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (C) 2011 Matteo Landi, Luigi Rizzo. All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * $FreeBSD$
+ * $Id: pkt-gen.c 9638 2011-11-07 18:07:43Z luigi $
+ *
+ * Example program to show how to build a multithreaded packet
+ * source/sink using the netmap device.
+ *
+ * In this example we create a programmable number of threads
+ * to take care of all the queues of the interface used to
+ * send or receive traffic.
+ *
+ */
+
+const char *default_payload="netmap pkt-gen Luigi Rizzo and Matteo Landi\n"
+ "http://info.iet.unipi.it/~luigi/netmap/ ";
+
+#include <errno.h>
+#include <pthread.h> /* pthread_* */
+#include <pthread_np.h> /* pthread w/ affinity */
+#include <signal.h> /* signal */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h> /* strcmp */
+#include <fcntl.h> /* open */
+#include <unistd.h> /* close */
+#include <ifaddrs.h> /* getifaddrs */
+
+#include <sys/mman.h> /* PROT_* */
+#include <sys/ioctl.h> /* ioctl */
+#include <sys/poll.h>
+#include <sys/socket.h> /* sockaddr.. */
+#include <arpa/inet.h> /* ntohs */
+#include <sys/param.h>
+#include <sys/cpuset.h> /* cpu_set */
+#include <sys/sysctl.h> /* sysctl */
+#include <sys/time.h> /* timersub */
+
+#include <net/ethernet.h>
+#include <net/if.h> /* ifreq */
+#include <net/if_dl.h> /* LLADDR */
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <net/netmap.h>
+#include <net/netmap_user.h>
+#include <pcap/pcap.h>
+
+
+static inline int min(int a, int b) { return a < b ? a : b; }
+
+/* debug support */
+#define D(format, ...) \
+ fprintf(stderr, "%s [%d] " format "\n", \
+ __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+#ifndef EXPERIMENTAL
+#define EXPERIMENTAL 0
+#endif
+
+int verbose = 0;
+#define MAX_QUEUES 64 /* no need to limit */
+
+#define SKIP_PAYLOAD 1 /* do not check payload. */
+
+#if EXPERIMENTAL
+/* Wrapper around `rdtsc' to take reliable timestamps flushing the pipeline */
+#define netmap_rdtsc(t) \
+ do { \
+ u_int __regs[4]; \
+ \
+ do_cpuid(0, __regs); \
+ (t) = rdtsc(); \
+ } while (0)
+
+static __inline void
+do_cpuid(u_int ax, u_int *p)
+{
+ __asm __volatile("cpuid"
+ : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
+ : "0" (ax));
+}
+
+static __inline uint64_t
+rdtsc(void)
+{
+ uint64_t rv;
+
+ __asm __volatile("rdtsc" : "=A" (rv));
+ return (rv);
+}
+#define MAX_SAMPLES 100000
+#endif /* EXPERIMENTAL */
+
+
+struct pkt {
+ struct ether_header eh;
+ struct ip ip;
+ struct udphdr udp;
+ uint8_t body[NETMAP_BUF_SIZE];
+} __attribute__((__packed__));
+
+/*
+ * global arguments for all threads
+ */
+struct glob_arg {
+ const char *src_ip;
+ const char *dst_ip;
+ const char *src_mac;
+ const char *dst_mac;
+ int pkt_size;
+ int burst;
+ int npackets; /* total packets to send */
+ int nthreads;
+ int cpus;
+ int use_pcap;
+ pcap_t *p;
+};
+
+struct mystat {
+ uint64_t containers[8];
+};
+
+/*
+ * Arguments for a new thread. The same structure is used by
+ * the source and the sink
+ */
+struct targ {
+ struct glob_arg *g;
+ int used;
+ int completed;
+ int fd;
+ struct nmreq nmr;
+ struct netmap_if *nifp;
+ uint16_t qfirst, qlast; /* range of queues to scan */
+ uint64_t count;
+ struct timeval tic, toc;
+ int me;
+ pthread_t thread;
+ int affinity;
+
+ uint8_t dst_mac[6];
+ uint8_t src_mac[6];
+ u_int dst_mac_range;
+ u_int src_mac_range;
+ uint32_t dst_ip;
+ uint32_t src_ip;
+ u_int dst_ip_range;
+ u_int src_ip_range;
+
+ struct pkt pkt;
+};
+
+
+static struct targ *targs;
+static int global_nthreads;
+
+/* control-C handler */
+static void
+sigint_h(__unused int sig)
+{
+ for (int i = 0; i < global_nthreads; i++) {
+ /* cancel active threads. */
+ if (targs[i].used == 0)
+ continue;
+
+ D("Cancelling thread #%d\n", i);
+ pthread_cancel(targs[i].thread);
+ targs[i].used = 0;
+ }
+
+ signal(SIGINT, SIG_DFL);
+}
+
+
+/* sysctl wrapper to return the number of active CPUs */
+static int
+system_ncpus(void)
+{
+ int mib[2], ncpus;
+ size_t len;
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ len = sizeof(mib);
+ sysctl(mib, 2, &ncpus, &len, NULL, 0);
+
+ return (ncpus);
+}
+
+/*
+ * locate the src mac address for our interface, put it
+ * into the user-supplied buffer. return 0 if ok, -1 on error.
+ */
+static int
+source_hwaddr(const char *ifname, char *buf)
+{
+ struct ifaddrs *ifaphead, *ifap;
+ int l = sizeof(ifap->ifa_name);
+
+ if (getifaddrs(&ifaphead) != 0) {
+ D("getifaddrs %s failed", ifname);
+ return (-1);
+ }
+
+ for (ifap = ifaphead; ifap; ifap = ifap->ifa_next) {
+ struct sockaddr_dl *sdl =
+ (struct sockaddr_dl *)ifap->ifa_addr;
+ uint8_t *mac;
+
+ if (!sdl || sdl->sdl_family != AF_LINK)
+ continue;
+ if (strncmp(ifap->ifa_name, ifname, l) != 0)
+ continue;
+ mac = (uint8_t *)LLADDR(sdl);
+ sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
+ mac[0], mac[1], mac[2],
+ mac[3], mac[4], mac[5]);
+ if (verbose)
+ D("source hwaddr %s", buf);
+ break;
+ }
+ freeifaddrs(ifaphead);
+ return ifap ? 0 : 1;
+}
+
+
+/* set the thread affinity. */
+static int
+setaffinity(pthread_t me, int i)
+{
+ cpuset_t cpumask;
+
+ if (i == -1)
+ return 0;
+
+ /* Set thread affinity affinity.*/
+ CPU_ZERO(&cpumask);
+ CPU_SET(i, &cpumask);
+
+ if (pthread_setaffinity_np(me, sizeof(cpuset_t), &cpumask) != 0) {
+ D("Unable to set affinity");
+ return 1;
+ }
+ return 0;
+}
+
+/* Compute the checksum of the given ip header. */
+static uint16_t
+checksum(const void *data, uint16_t len)
+{
+ const uint8_t *addr = data;
+ uint32_t sum = 0;
+
+ while (len > 1) {
+ sum += addr[0] * 256 + addr[1];
+ addr += 2;
+ len -= 2;
+ }
+
+ if (len == 1)
+ sum += *addr * 256;
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ sum = htons(sum);
+
+ return ~sum;
+}
+
+/*
+ * Fill a packet with some payload.
+ */
+static void
+initialize_packet(struct targ *targ)
+{
+ struct pkt *pkt = &targ->pkt;
+ struct ether_header *eh;
+ struct ip *ip;
+ struct udphdr *udp;
+ uint16_t paylen = targ->g->pkt_size - sizeof(*eh) - sizeof(*ip);
+ int i, l, l0 = strlen(default_payload);
+ char *p;
+
+ for (i = 0; i < paylen;) {
+ l = min(l0, paylen - i);
+ bcopy(default_payload, pkt->body + i, l);
+ i += l;
+ }
+ pkt->body[i-1] = '\0';
+
+ udp = &pkt->udp;
+ udp->uh_sport = htons(1234);
+ udp->uh_dport = htons(4321);
+ udp->uh_ulen = htons(paylen);
+ udp->uh_sum = 0; // checksum(udp, sizeof(*udp));
+
+ ip = &pkt->ip;
+ ip->ip_v = IPVERSION;
+ ip->ip_hl = 5;
+ ip->ip_id = 0;
+ ip->ip_tos = IPTOS_LOWDELAY;
+ ip->ip_len = ntohs(targ->g->pkt_size - sizeof(*eh));
+ ip->ip_id = 0;
+ ip->ip_off = htons(IP_DF); /* Don't fragment */
+ ip->ip_ttl = IPDEFTTL;
+ ip->ip_p = IPPROTO_UDP;
+ inet_aton(targ->g->src_ip, (struct in_addr *)&ip->ip_src);
+ inet_aton(targ->g->dst_ip, (struct in_addr *)&ip->ip_dst);
+ targ->dst_ip = ip->ip_dst.s_addr;
+ targ->src_ip = ip->ip_src.s_addr;
+ p = index(targ->g->src_ip, '-');
+ if (p) {
+ targ->dst_ip_range = atoi(p+1);
+ D("dst-ip sweep %d addresses", targ->dst_ip_range);
+ }
+ ip->ip_sum = checksum(ip, sizeof(*ip));
+
+ eh = &pkt->eh;
+ bcopy(ether_aton(targ->g->src_mac), targ->src_mac, 6);
+ bcopy(targ->src_mac, eh->ether_shost, 6);
+ p = index(targ->g->src_mac, '-');
+ if (p)
+ targ->src_mac_range = atoi(p+1);
+
+ bcopy(ether_aton(targ->g->dst_mac), targ->dst_mac, 6);
+ bcopy(targ->dst_mac, eh->ether_dhost, 6);
+ p = index(targ->g->dst_mac, '-');
+ if (p)
+ targ->dst_mac_range = atoi(p+1);
+ eh->ether_type = htons(ETHERTYPE_IP);
+}
+
+/* Check the payload of the packet for errors (use it for debug).
+ * Look for consecutive ascii representations of the size of the packet.
+ */
+static void
+check_payload(char *p, int psize)
+{
+ char temp[64];
+ int n_read, size, sizelen;
+
+ /* get the length in ASCII of the length of the packet. */
+ sizelen = sprintf(temp, "%d", psize) + 1; // include a whitespace
+
+ /* dummy payload. */
+ p += 14; /* skip packet header. */
+ n_read = 14;
+ while (psize - n_read >= sizelen) {
+ sscanf(p, "%d", &size);
+ if (size != psize) {
+ D("Read %d instead of %d", size, psize);
+ break;
+ }
+
+ p += sizelen;
+ n_read += sizelen;
+ }
+}
+
+
+/*
+ * create and enqueue a batch of packets on a ring.
+ * On the last one set NS_REPORT to tell the driver to generate
+ * an interrupt when done.
+ */
+static int
+send_packets(struct netmap_ring *ring, struct pkt *pkt,
+ int size, u_int count, int fill_all)
+{
+ u_int sent, cur = ring->cur;
+
+ if (ring->avail < count)
+ count = ring->avail;
+
+ for (sent = 0; sent < count; sent++) {
+ struct netmap_slot *slot = &ring->slot[cur];
+ char *p = NETMAP_BUF(ring, slot->buf_idx);
+
+ if (fill_all)
+ memcpy(p, pkt, size);
+
+ slot->len = size;
+ if (sent == count - 1)
+ slot->flags |= NS_REPORT;
+ cur = NETMAP_RING_NEXT(ring, cur);
+ }
+ ring->avail -= sent;
+ ring->cur = cur;
+
+ return (sent);
+}
+
+static void *
+sender_body(void *data)
+{
+ struct targ *targ = (struct targ *) data;
+
+ struct pollfd fds[1];
+ struct netmap_if *nifp = targ->nifp;
+ struct netmap_ring *txring;
+ int i, n = targ->g->npackets / targ->g->nthreads, sent = 0;
+ int fill_all = 1;
+
+ if (setaffinity(targ->thread, targ->affinity))
+ goto quit;
+ /* setup poll(2) machanism. */
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = targ->fd;
+ fds[0].events = (POLLOUT);
+
+ /* main loop.*/
+ gettimeofday(&targ->tic, NULL);
+ if (targ->g->use_pcap) {
+ int size = targ->g->pkt_size;
+ void *pkt = &targ->pkt;
+ pcap_t *p = targ->g->p;
+
+ for (; sent < n; sent++) {
+ if (pcap_inject(p, pkt, size) == -1)
+ break;
+ }
+ } else {
+ while (sent < n) {
+
+ /*
+ * wait for available room in the send queue(s)
+ */
+ if (poll(fds, 1, 2000) <= 0) {
+ D("poll error/timeout on queue %d\n", targ->me);
+ goto quit;
+ }
+ /*
+ * scan our queues and send on those with room
+ */
+ if (sent > 100000)
+ fill_all = 0;
+ for (i = targ->qfirst; i < targ->qlast; i++) {
+ int m, limit = MIN(n - sent, targ->g->burst);
+
+ txring = NETMAP_TXRING(nifp, i);
+ if (txring->avail == 0)
+ continue;
+ m = send_packets(txring, &targ->pkt, targ->g->pkt_size,
+ limit, fill_all);
+ sent += m;
+ targ->count = sent;
+ }
+ }
+ /* Tell the interface that we have new packets. */
+ ioctl(fds[0].fd, NIOCTXSYNC, NULL);
+
+ /* final part: wait all the TX queues to be empty. */
+ for (i = targ->qfirst; i < targ->qlast; i++) {
+ txring = NETMAP_TXRING(nifp, i);
+ while (!NETMAP_TX_RING_EMPTY(txring)) {
+ ioctl(fds[0].fd, NIOCTXSYNC, NULL);
+ usleep(1); /* wait 1 tick */
+ }
+ }
+ }
+
+ gettimeofday(&targ->toc, NULL);
+ targ->completed = 1;
+ targ->count = sent;
+
+quit:
+ /* reset the ``used`` flag. */
+ targ->used = 0;
+
+ return (NULL);
+}
+
+
+static void
+receive_pcap(u_char *user, __unused const struct pcap_pkthdr * h,
+ __unused const u_char * bytes)
+{
+ int *count = (int *)user;
+ (*count)++;
+}
+
+static int
+receive_packets(struct netmap_ring *ring, u_int limit, int skip_payload)
+{
+ u_int cur, rx;
+
+ cur = ring->cur;
+ if (ring->avail < limit)
+ limit = ring->avail;
+ for (rx = 0; rx < limit; rx++) {
+ struct netmap_slot *slot = &ring->slot[cur];
+ char *p = NETMAP_BUF(ring, slot->buf_idx);
+
+ if (!skip_payload)
+ check_payload(p, slot->len);
+
+ cur = NETMAP_RING_NEXT(ring, cur);
+ }
+ ring->avail -= rx;
+ ring->cur = cur;
+
+ return (rx);
+}
+
+static void *
+receiver_body(void *data)
+{
+ struct targ *targ = (struct targ *) data;
+ struct pollfd fds[1];
+ struct netmap_if *nifp = targ->nifp;
+ struct netmap_ring *rxring;
+ int i, received = 0;
+
+ if (setaffinity(targ->thread, targ->affinity))
+ goto quit;
+
+ /* setup poll(2) machanism. */
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = targ->fd;
+ fds[0].events = (POLLIN);
+
+ /* unbounded wait for the first packet. */
+ for (;;) {
+ i = poll(fds, 1, 1000);
+ if (i > 0 && !(fds[0].revents & POLLERR))
+ break;
+ D("waiting for initial packets, poll returns %d %d", i, fds[0].revents);
+ }
+
+ /* main loop, exit after 1s silence */
+ gettimeofday(&targ->tic, NULL);
+ if (targ->g->use_pcap) {
+ for (;;) {
+ pcap_dispatch(targ->g->p, targ->g->burst, receive_pcap, NULL);
+ }
+ } else {
+ while (1) {
+ /* Once we started to receive packets, wait at most 1 seconds
+ before quitting. */
+ if (poll(fds, 1, 1 * 1000) <= 0) {
+ gettimeofday(&targ->toc, NULL);
+ targ->toc.tv_sec -= 1; /* Substract timeout time. */
+ break;
+ }
+
+ for (i = targ->qfirst; i < targ->qlast; i++) {
+ int m;
+
+ rxring = NETMAP_RXRING(nifp, i);
+ if (rxring->avail == 0)
+ continue;
+
+ m = receive_packets(rxring, targ->g->burst,
+ SKIP_PAYLOAD);
+ received += m;
+ targ->count = received;
+ }
+
+ // tell the card we have read the data
+ //ioctl(fds[0].fd, NIOCRXSYNC, NULL);
+ }
+ }
+
+ targ->completed = 1;
+ targ->count = received;
+
+quit:
+ /* reset the ``used`` flag. */
+ targ->used = 0;
+
+ return (NULL);
+}
+
+static void
+tx_output(uint64_t sent, int size, double delta)
+{
+ double amount = 8.0 * (1.0 * size * sent) / delta;
+ double pps = sent / delta;
+ char units[4] = { '\0', 'K', 'M', 'G' };
+ int aunit = 0, punit = 0;
+
+ while (amount >= 1000) {
+ amount /= 1000;
+ aunit += 1;
+ }
+ while (pps >= 1000) {
+ pps /= 1000;
+ punit += 1;
+ }
+
+ printf("Sent %llu packets, %d bytes each, in %.2f seconds.\n",
+ sent, size, delta);
+ printf("Speed: %.2f%cpps. Bandwidth: %.2f%cbps.\n",
+ pps, units[punit], amount, units[aunit]);
+}
+
+
+static void
+rx_output(uint64_t received, double delta)
+{
+
+ double pps = received / delta;
+ char units[4] = { '\0', 'K', 'M', 'G' };
+ int punit = 0;
+
+ while (pps >= 1000) {
+ pps /= 1000;
+ punit += 1;
+ }
+
+ printf("Received %llu packets, in %.2f seconds.\n", received, delta);
+ printf("Speed: %.2f%cpps.\n", pps, units[punit]);
+}
+
+static void
+usage(void)
+{
+ const char *cmd = "pkt-gen";
+ fprintf(stderr,
+ "Usage:\n"
+ "%s arguments\n"
+ "\t-i interface interface name\n"
+ "\t-t pkts_to_send also forces send mode\n"
+ "\t-r pkts_to_receive also forces receive mode\n"
+ "\t-l pkts_size in bytes excluding CRC\n"
+ "\t-d dst-ip end with %%n to sweep n addresses\n"
+ "\t-s src-ip end with %%n to sweep n addresses\n"
+ "\t-D dst-mac end with %%n to sweep n addresses\n"
+ "\t-S src-mac end with %%n to sweep n addresses\n"
+ "\t-b burst size testing, mostly\n"
+ "\t-c cores cores to use\n"
+ "\t-p threads processes/threads to use\n"
+ "\t-T report_ms milliseconds between reports\n"
+ "\t-w wait_for_link_time in seconds\n"
+ "",
+ cmd);
+
+ exit(0);
+}
+
+
+int
+main(int arc, char **argv)
+{
+ int i, fd;
+
+ struct glob_arg g;
+
+ struct nmreq nmr;
+ void *mmap_addr; /* the mmap address */
+ void *(*td_body)(void *) = receiver_body;
+ int ch;
+ int report_interval = 1000; /* report interval */
+ char *ifname = NULL;
+ int wait_link = 2;
+ int devqueues = 1; /* how many device queues */
+
+ bzero(&g, sizeof(g));
+
+ g.src_ip = "10.0.0.1";
+ g.dst_ip = "10.1.0.1";
+ g.dst_mac = "ff:ff:ff:ff:ff:ff";
+ g.src_mac = NULL;
+ g.pkt_size = 60;
+ g.burst = 512; // default
+ g.nthreads = 1;
+ g.cpus = 1;
+
+ while ( (ch = getopt(arc, argv,
+ "i:t:r:l:d:s:D:S:b:c:p:T:w:v")) != -1) {
+ switch(ch) {
+ default:
+ D("bad option %c %s", ch, optarg);
+ usage();
+ break;
+ case 'i': /* interface */
+ ifname = optarg;
+ break;
+ case 't': /* send */
+ td_body = sender_body;
+ g.npackets = atoi(optarg);
+ break;
+ case 'r': /* receive */
+ td_body = receiver_body;
+ g.npackets = atoi(optarg);
+ break;
+ case 'l': /* pkt_size */
+ g.pkt_size = atoi(optarg);
+ break;
+ case 'd':
+ g.dst_ip = optarg;
+ break;
+ case 's':
+ g.src_ip = optarg;
+ break;
+ case 'T': /* report interval */
+ report_interval = atoi(optarg);
+ break;
+ case 'w':
+ wait_link = atoi(optarg);
+ break;
+ case 'b': /* burst */
+ g.burst = atoi(optarg);
+ break;
+ case 'c':
+ g.cpus = atoi(optarg);
+ break;
+ case 'p':
+ g.nthreads = atoi(optarg);
+ break;
+
+ case 'P':
+ g.use_pcap = 1;
+ break;
+
+ case 'D': /* destination mac */
+ g.dst_mac = optarg;
+ {
+ struct ether_addr *mac = ether_aton(g.dst_mac);
+ D("ether_aton(%s) gives %p", g.dst_mac, mac);
+ }
+ break;
+ case 'S': /* source mac */
+ g.src_mac = optarg;
+ break;
+ case 'v':
+ verbose++;
+ }
+ }
+
+ if (ifname == NULL) {
+ D("missing ifname");
+ usage();
+ }
+ {
+ int n = system_ncpus();
+ if (g.cpus < 0 || g.cpus > n) {
+ D("%d cpus is too high, have only %d cpus", g.cpus, n);
+ usage();
+ }
+ if (g.cpus == 0)
+ g.cpus = n;
+ }
+ if (g.pkt_size < 16 || g.pkt_size > 1536) {
+ D("bad pktsize %d\n", g.pkt_size);
+ usage();
+ }
+
+ bzero(&nmr, sizeof(nmr));
+ /*
+ * Open the netmap device to fetch the number of queues of our
+ * interface.
+ *
+ * The first NIOCREGIF also detaches the card from the
+ * protocol stack and may cause a reset of the card,
+ * which in turn may take some time for the PHY to
+ * reconfigure.
+ */
+ fd = open("/dev/netmap", O_RDWR);
+ if (fd == -1) {
+ D("Unable to open /dev/netmap");
+ // fail later
+ } else {
+ if ((ioctl(fd, NIOCGINFO, &nmr)) == -1) {
+ D("Unable to get if info without name");
+ } else {
+ D("map size is %d Kb", nmr.nr_memsize >> 10);
+ }
+ bzero(&nmr, sizeof(nmr));
+ strncpy(nmr.nr_name, ifname, sizeof(nmr.nr_name));
+ if ((ioctl(fd, NIOCGINFO, &nmr)) == -1) {
+ D("Unable to get if info for %s", ifname);
+ }
+ devqueues = nmr.nr_numrings;
+ }
+
+ /* validate provided nthreads. */
+ if (g.nthreads < 1 || g.nthreads > devqueues) {
+ D("bad nthreads %d, have %d queues", g.nthreads, devqueues);
+ // continue, fail later
+ }
+
+ if (td_body == sender_body && g.src_mac == NULL) {
+ static char mybuf[20] = "ff:ff:ff:ff:ff:ff";
+ /* retrieve source mac address. */
+ if (source_hwaddr(ifname, mybuf) == -1) {
+ D("Unable to retrieve source mac");
+ // continue, fail later
+ }
+ g.src_mac = mybuf;
+ }
+
+ /*
+ * Map the netmap shared memory: instead of issuing mmap()
+ * inside the body of the threads, we prefer to keep this
+ * operation here to simplify the thread logic.
+ */
+ D("mmapping %d Kbytes", nmr.nr_memsize>>10);
+ mmap_addr = (struct netmap_d *) mmap(0, nmr.nr_memsize,
+ PROT_WRITE | PROT_READ,
+ MAP_SHARED, fd, 0);
+ if (mmap_addr == MAP_FAILED) {
+ D("Unable to mmap %d KB", nmr.nr_memsize >> 10);
+ // continue, fail later
+ }
+
+ /*
+ * Register the interface on the netmap device: from now on,
+ * we can operate on the network interface without any
+ * interference from the legacy network stack.
+ *
+ * We decide to put the first interface registration here to
+ * give time to cards that take a long time to reset the PHY.
+ */
+ if (ioctl(fd, NIOCREGIF, &nmr) == -1) {
+ D("Unable to register interface %s", ifname);
+ //continue, fail later
+ }
+
+
+ /* Print some debug information. */
+ fprintf(stdout,
+ "%s %s: %d queues, %d threads and %d cpus.\n",
+ (td_body == sender_body) ? "Sending on" : "Receiving from",
+ ifname,
+ devqueues,
+ g.nthreads,
+ g.cpus);
+ if (td_body == sender_body) {
+ fprintf(stdout, "%s -> %s (%s -> %s)\n",
+ g.src_ip, g.dst_ip,
+ g.src_mac, g.dst_mac);
+ }
+
+ /* Exit if something went wrong. */
+ if (fd < 0) {
+ D("aborting");
+ usage();
+ }
+
+
+ /* Wait for PHY reset. */
+ D("Wait %d secs for phy reset", wait_link);
+ sleep(wait_link);
+ D("Ready...");
+
+ /* Install ^C handler. */
+ global_nthreads = g.nthreads;
+ signal(SIGINT, sigint_h);
+
+ if (g.use_pcap) {
+ // XXX g.p = pcap_open_live(..);
+ }
+
+ targs = calloc(g.nthreads, sizeof(*targs));
+ /*
+ * Now create the desired number of threads, each one
+ * using a single descriptor.
+ */
+ for (i = 0; i < g.nthreads; i++) {
+ struct netmap_if *tnifp;
+ struct nmreq tifreq;
+ int tfd;
+
+ if (g.use_pcap) {
+ tfd = -1;
+ tnifp = NULL;
+ } else {
+ /* register interface. */
+ tfd = open("/dev/netmap", O_RDWR);
+ if (tfd == -1) {
+ D("Unable to open /dev/netmap");
+ continue;
+ }
+
+ bzero(&tifreq, sizeof(tifreq));
+ strncpy(tifreq.nr_name, ifname, sizeof(tifreq.nr_name));
+ tifreq.nr_ringid = (g.nthreads > 1) ? (i | NETMAP_HW_RING) : 0;
+
+ /*
+ * if we are acting as a receiver only, do not touch the transmit ring.
+ * This is not the default because many apps may use the interface
+ * in both directions, but a pure receiver does not.
+ */
+ if (td_body == receiver_body) {
+ tifreq.nr_ringid |= NETMAP_NO_TX_POLL;
+ }
+
+ if ((ioctl(tfd, NIOCREGIF, &tifreq)) == -1) {
+ D("Unable to register %s", ifname);
+ continue;
+ }
+ tnifp = NETMAP_IF(mmap_addr, tifreq.nr_offset);
+ }
+ /* start threads. */
+ bzero(&targs[i], sizeof(targs[i]));
+ targs[i].g = &g;
+ targs[i].used = 1;
+ targs[i].completed = 0;
+ targs[i].fd = tfd;
+ targs[i].nmr = tifreq;
+ targs[i].nifp = tnifp;
+ targs[i].qfirst = (g.nthreads > 1) ? i : 0;
+ targs[i].qlast = (g.nthreads > 1) ? i+1 : tifreq.nr_numrings;
+ targs[i].me = i;
+ targs[i].affinity = g.cpus ? i % g.cpus : -1;
+ if (td_body == sender_body) {
+ /* initialize the packet to send. */
+ initialize_packet(&targs[i]);
+ }
+
+ if (pthread_create(&targs[i].thread, NULL, td_body,
+ &targs[i]) == -1) {
+ D("Unable to create thread %d", i);
+ targs[i].used = 0;
+ }
+ }
+
+ {
+ uint64_t my_count = 0, prev = 0;
+ uint64_t count = 0;
+ double delta_t;
+ struct timeval tic, toc;
+
+ gettimeofday(&toc, NULL);
+ for (;;) {
+ struct timeval now, delta;
+ uint64_t pps;
+ int done = 0;
+
+ delta.tv_sec = report_interval/1000;
+ delta.tv_usec = (report_interval%1000)*1000;
+ select(0, NULL, NULL, NULL, &delta);
+ gettimeofday(&now, NULL);
+ timersub(&now, &toc, &toc);
+ my_count = 0;
+ for (i = 0; i < g.nthreads; i++) {
+ my_count += targs[i].count;
+ if (targs[i].used == 0)
+ done++;
+ }
+ pps = toc.tv_sec* 1000000 + toc.tv_usec;
+ if (pps < 10000)
+ continue;
+ pps = (my_count - prev)*1000000 / pps;
+ D("%llu pps", pps);
+ prev = my_count;
+ toc = now;
+ if (done == g.nthreads)
+ break;
+ }
+
+ timerclear(&tic);
+ timerclear(&toc);
+ for (i = 0; i < g.nthreads; i++) {
+ /*
+ * Join active threads, unregister interfaces and close
+ * file descriptors.
+ */
+ pthread_join(targs[i].thread, NULL);
+ ioctl(targs[i].fd, NIOCUNREGIF, &targs[i].nmr);
+ close(targs[i].fd);
+
+ if (targs[i].completed == 0)
+ continue;
+
+ /*
+ * Collect threads o1utput and extract information about
+ * how log it took to send all the packets.
+ */
+ count += targs[i].count;
+ if (!timerisset(&tic) || timercmp(&targs[i].tic, &tic, <))
+ tic = targs[i].tic;
+ if (!timerisset(&toc) || timercmp(&targs[i].toc, &toc, >))
+ toc = targs[i].toc;
+ }
+
+ /* print output. */
+ timersub(&toc, &tic, &toc);
+ delta_t = toc.tv_sec + 1e-6* toc.tv_usec;
+ if (td_body == sender_body)
+ tx_output(count, g.pkt_size, delta_t);
+ else
+ rx_output(count, delta_t);
+ }
+
+ ioctl(fd, NIOCUNREGIF, &nmr);
+ munmap(mmap_addr, nmr.nr_memsize);
+ close(fd);
+
+ return (0);
+}
+/* end of file */