pkt-gen.c revision 246896
1227614Sluigi/* 2246896Sluigi * Copyright (C) 2011-2012 Matteo Landi, Luigi Rizzo. All rights reserved. 3227614Sluigi * 4227614Sluigi * Redistribution and use in source and binary forms, with or without 5227614Sluigi * modification, are permitted provided that the following conditions 6227614Sluigi * are met: 7228276Sluigi * 1. Redistributions of source code must retain the above copyright 8228276Sluigi * notice, this list of conditions and the following disclaimer. 9228276Sluigi * 2. Redistributions in binary form must reproduce the above copyright 10228276Sluigi * notice, this list of conditions and the following disclaimer in the 11227614Sluigi * documentation and/or other materials provided with the distribution. 12227614Sluigi * 13227614Sluigi * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14227614Sluigi * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15227614Sluigi * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16227614Sluigi * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17227614Sluigi * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18227614Sluigi * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19227614Sluigi * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20227614Sluigi * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21227614Sluigi * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22227614Sluigi * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23227614Sluigi * SUCH DAMAGE. 24227614Sluigi */ 25227614Sluigi 26227614Sluigi/* 27227614Sluigi * $FreeBSD: head/tools/tools/netmap/pkt-gen.c 246896 2013-02-17 04:43:22Z luigi $ 28246896Sluigi * $Id: pkt-gen.c 12024 2013-01-25 05:41:51Z luigi $ 29227614Sluigi * 30227614Sluigi * Example program to show how to build a multithreaded packet 31227614Sluigi * source/sink using the netmap device. 32227614Sluigi * 33227614Sluigi * In this example we create a programmable number of threads 34227614Sluigi * to take care of all the queues of the interface used to 35227614Sluigi * send or receive traffic. 36227614Sluigi * 37227614Sluigi */ 38227614Sluigi 39246896Sluigi#include "nm_util.h" 40246896Sluigi 41246896Sluigiconst char *default_payload="netmap pkt-gen payload\n" 42227614Sluigi "http://info.iet.unipi.it/~luigi/netmap/ "; 43227614Sluigi 44246896Sluigiint time_second; // support for RD() debugging macro 45227614Sluigi 46227614Sluigiint verbose = 0; 47227614Sluigi 48227614Sluigi#define SKIP_PAYLOAD 1 /* do not check payload. */ 49227614Sluigi 50227614Sluigistruct pkt { 51227614Sluigi struct ether_header eh; 52227614Sluigi struct ip ip; 53227614Sluigi struct udphdr udp; 54231198Sluigi uint8_t body[2048]; // XXX hardwired 55227614Sluigi} __attribute__((__packed__)); 56227614Sluigi 57246896Sluigistruct ip_range { 58246896Sluigi char *name; 59246896Sluigi struct in_addr start, end, cur; 60246896Sluigi uint16_t port0, port1, cur_p; 61246896Sluigi}; 62246896Sluigi 63246896Sluigistruct mac_range { 64246896Sluigi char *name; 65246896Sluigi struct ether_addr start, end; 66246896Sluigi}; 67246896Sluigi 68227614Sluigi/* 69227614Sluigi * global arguments for all threads 70227614Sluigi */ 71246896Sluigi 72227614Sluigistruct glob_arg { 73246896Sluigi struct ip_range src_ip; 74246896Sluigi struct ip_range dst_ip; 75246896Sluigi struct mac_range dst_mac; 76246896Sluigi struct mac_range src_mac; 77227614Sluigi int pkt_size; 78227614Sluigi int burst; 79246896Sluigi int forever; 80227614Sluigi int npackets; /* total packets to send */ 81227614Sluigi int nthreads; 82227614Sluigi int cpus; 83234956Sluigi int options; /* testing */ 84234956Sluigi#define OPT_PREFETCH 1 85234956Sluigi#define OPT_ACCESS 2 86234956Sluigi#define OPT_COPY 4 87234956Sluigi#define OPT_MEMCPY 8 88246896Sluigi#define OPT_TS 16 /* add a timestamp */ 89246896Sluigi int dev_type; 90227614Sluigi pcap_t *p; 91227614Sluigi 92246896Sluigi int affinity; 93246896Sluigi int main_fd; 94246896Sluigi int report_interval; 95246896Sluigi void *(*td_body)(void *); 96246896Sluigi void *mmap_addr; 97246896Sluigi int mmap_size; 98246896Sluigi char *ifname; 99227614Sluigi}; 100246896Sluigienum dev_type { DEV_NONE, DEV_NETMAP, DEV_PCAP, DEV_TAP }; 101227614Sluigi 102246896Sluigi 103227614Sluigi/* 104227614Sluigi * Arguments for a new thread. The same structure is used by 105227614Sluigi * the source and the sink 106227614Sluigi */ 107227614Sluigistruct targ { 108227614Sluigi struct glob_arg *g; 109227614Sluigi int used; 110227614Sluigi int completed; 111238165Semaste int cancel; 112227614Sluigi int fd; 113227614Sluigi struct nmreq nmr; 114227614Sluigi struct netmap_if *nifp; 115227614Sluigi uint16_t qfirst, qlast; /* range of queues to scan */ 116246896Sluigi volatile uint64_t count; 117227614Sluigi struct timeval tic, toc; 118227614Sluigi int me; 119227614Sluigi pthread_t thread; 120227614Sluigi int affinity; 121227614Sluigi 122227614Sluigi struct pkt pkt; 123227614Sluigi}; 124227614Sluigi 125227614Sluigi 126246896Sluigi/* 127246896Sluigi * extract the extremes from a range of ipv4 addresses. 128246896Sluigi * addr_lo[-addr_hi][:port_lo[-port_hi]] 129246896Sluigi */ 130246896Sluigistatic void 131246896Sluigiextract_ip_range(struct ip_range *r) 132246896Sluigi{ 133246896Sluigi char *p_lo, *p_hi; 134246896Sluigi char buf1[16]; // one ip address 135246896Sluigi 136246896Sluigi D("extract IP range from %s", r->name); 137246896Sluigi p_lo = index(r->name, ':'); /* do we have ports ? */ 138246896Sluigi if (p_lo) { 139246896Sluigi D(" found ports at %s", p_lo); 140246896Sluigi *p_lo++ = '\0'; 141246896Sluigi p_hi = index(p_lo, '-'); 142246896Sluigi if (p_hi) 143246896Sluigi *p_hi++ = '\0'; 144246896Sluigi else 145246896Sluigi p_hi = p_lo; 146246896Sluigi r->port0 = strtol(p_lo, NULL, 0); 147246896Sluigi r->port1 = strtol(p_hi, NULL, 0); 148246896Sluigi if (r->port1 < r->port0) { 149246896Sluigi r->cur_p = r->port0; 150246896Sluigi r->port0 = r->port1; 151246896Sluigi r->port1 = r->cur_p; 152246896Sluigi } 153246896Sluigi r->cur_p = r->port0; 154246896Sluigi D("ports are %d to %d", r->port0, r->port1); 155246896Sluigi } 156246896Sluigi p_hi = index(r->name, '-'); /* do we have upper ip ? */ 157246896Sluigi if (p_hi) { 158246896Sluigi *p_hi++ = '\0'; 159246896Sluigi } else 160246896Sluigi p_hi = r->name; 161246896Sluigi inet_aton(r->name, &r->start); 162246896Sluigi inet_aton(p_hi, &r->end); 163246896Sluigi if (r->start.s_addr > r->end.s_addr) { 164246896Sluigi r->cur = r->start; 165246896Sluigi r->start = r->end; 166246896Sluigi r->end = r->cur; 167246896Sluigi } 168246896Sluigi r->cur = r->start; 169246896Sluigi strncpy(buf1, inet_ntoa(r->end), sizeof(buf1)); 170246896Sluigi D("range is %s %d to %s %d", inet_ntoa(r->start), r->port0, 171246896Sluigi buf1, r->port1); 172246896Sluigi} 173246896Sluigi 174246896Sluigistatic void 175246896Sluigiextract_mac_range(struct mac_range *r) 176246896Sluigi{ 177246896Sluigi D("extract MAC range from %s", r->name); 178246896Sluigi bcopy(ether_aton(r->name), &r->start, 6); 179246896Sluigi bcopy(ether_aton(r->name), &r->end, 6); 180246896Sluigi#if 0 181246896Sluigi bcopy(targ->src_mac, eh->ether_shost, 6); 182246896Sluigi p = index(targ->g->src_mac, '-'); 183246896Sluigi if (p) 184246896Sluigi targ->src_mac_range = atoi(p+1); 185246896Sluigi 186246896Sluigi bcopy(ether_aton(targ->g->dst_mac), targ->dst_mac, 6); 187246896Sluigi bcopy(targ->dst_mac, eh->ether_dhost, 6); 188246896Sluigi p = index(targ->g->dst_mac, '-'); 189246896Sluigi if (p) 190246896Sluigi targ->dst_mac_range = atoi(p+1); 191246896Sluigi#endif 192246896Sluigi D("%s starts at %s", r->name, ether_ntoa(&r->start)); 193246896Sluigi} 194246896Sluigi 195227614Sluigistatic struct targ *targs; 196227614Sluigistatic int global_nthreads; 197227614Sluigi 198227614Sluigi/* control-C handler */ 199227614Sluigistatic void 200246896Sluigisigint_h(int sig) 201227614Sluigi{ 202246896Sluigi int i; 203246896Sluigi 204246896Sluigi (void)sig; /* UNUSED */ 205246896Sluigi for (i = 0; i < global_nthreads; i++) { 206238165Semaste targs[i].cancel = 1; 207246896Sluigi } 208227614Sluigi signal(SIGINT, SIG_DFL); 209227614Sluigi} 210227614Sluigi 211227614Sluigi/* sysctl wrapper to return the number of active CPUs */ 212227614Sluigistatic int 213227614Sluigisystem_ncpus(void) 214227614Sluigi{ 215246896Sluigi#ifdef __FreeBSD__ 216227614Sluigi int mib[2], ncpus; 217227614Sluigi size_t len; 218227614Sluigi 219227614Sluigi mib[0] = CTL_HW; 220227614Sluigi mib[1] = HW_NCPU; 221227614Sluigi len = sizeof(mib); 222227614Sluigi sysctl(mib, 2, &ncpus, &len, NULL, 0); 223227614Sluigi 224227614Sluigi return (ncpus); 225246896Sluigi#else 226246896Sluigi return 1; 227246896Sluigi#endif /* !__FreeBSD__ */ 228227614Sluigi} 229227614Sluigi 230246896Sluigi#ifdef __linux__ 231246896Sluigi#define sockaddr_dl sockaddr_ll 232246896Sluigi#define sdl_family sll_family 233246896Sluigi#define AF_LINK AF_PACKET 234246896Sluigi#define LLADDR(s) s->sll_addr; 235246896Sluigi#include <linux/if_tun.h> 236246896Sluigi#define TAP_CLONEDEV "/dev/net/tun" 237246896Sluigi#endif /* __linux__ */ 238246896Sluigi 239246896Sluigi#ifdef __FreeBSD__ 240246896Sluigi#include <net/if_tun.h> 241246896Sluigi#define TAP_CLONEDEV "/dev/tap" 242246896Sluigi#endif /* __FreeBSD */ 243246896Sluigi 244246896Sluigi#ifdef __APPLE__ 245246896Sluigi// #warning TAP not supported on apple ? 246246896Sluigi#include <net/if_utun.h> 247246896Sluigi#define TAP_CLONEDEV "/dev/tap" 248246896Sluigi#endif /* __APPLE__ */ 249246896Sluigi 250246896Sluigi 251227614Sluigi/* 252227614Sluigi * locate the src mac address for our interface, put it 253227614Sluigi * into the user-supplied buffer. return 0 if ok, -1 on error. 254227614Sluigi */ 255227614Sluigistatic int 256227614Sluigisource_hwaddr(const char *ifname, char *buf) 257227614Sluigi{ 258227614Sluigi struct ifaddrs *ifaphead, *ifap; 259227614Sluigi int l = sizeof(ifap->ifa_name); 260227614Sluigi 261227614Sluigi if (getifaddrs(&ifaphead) != 0) { 262227614Sluigi D("getifaddrs %s failed", ifname); 263227614Sluigi return (-1); 264227614Sluigi } 265227614Sluigi 266227614Sluigi for (ifap = ifaphead; ifap; ifap = ifap->ifa_next) { 267227614Sluigi struct sockaddr_dl *sdl = 268227614Sluigi (struct sockaddr_dl *)ifap->ifa_addr; 269227614Sluigi uint8_t *mac; 270227614Sluigi 271227614Sluigi if (!sdl || sdl->sdl_family != AF_LINK) 272227614Sluigi continue; 273227614Sluigi if (strncmp(ifap->ifa_name, ifname, l) != 0) 274227614Sluigi continue; 275227614Sluigi mac = (uint8_t *)LLADDR(sdl); 276227614Sluigi sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", 277227614Sluigi mac[0], mac[1], mac[2], 278227614Sluigi mac[3], mac[4], mac[5]); 279227614Sluigi if (verbose) 280227614Sluigi D("source hwaddr %s", buf); 281227614Sluigi break; 282227614Sluigi } 283227614Sluigi freeifaddrs(ifaphead); 284227614Sluigi return ifap ? 0 : 1; 285227614Sluigi} 286227614Sluigi 287227614Sluigi 288227614Sluigi/* set the thread affinity. */ 289227614Sluigistatic int 290227614Sluigisetaffinity(pthread_t me, int i) 291227614Sluigi{ 292246896Sluigi#ifdef __FreeBSD__ 293227614Sluigi cpuset_t cpumask; 294227614Sluigi 295227614Sluigi if (i == -1) 296227614Sluigi return 0; 297227614Sluigi 298227614Sluigi /* Set thread affinity affinity.*/ 299227614Sluigi CPU_ZERO(&cpumask); 300227614Sluigi CPU_SET(i, &cpumask); 301227614Sluigi 302227614Sluigi if (pthread_setaffinity_np(me, sizeof(cpuset_t), &cpumask) != 0) { 303227614Sluigi D("Unable to set affinity"); 304227614Sluigi return 1; 305227614Sluigi } 306246896Sluigi#else 307246896Sluigi (void)me; /* suppress 'unused' warnings */ 308246896Sluigi (void)i; 309246896Sluigi#endif /* __FreeBSD__ */ 310227614Sluigi return 0; 311227614Sluigi} 312227614Sluigi 313227614Sluigi/* Compute the checksum of the given ip header. */ 314227614Sluigistatic uint16_t 315246896Sluigichecksum(const void *data, uint16_t len, uint32_t sum) 316227614Sluigi{ 317227614Sluigi const uint8_t *addr = data; 318246896Sluigi uint32_t i; 319227614Sluigi 320246896Sluigi /* Checksum all the pairs of bytes first... */ 321246896Sluigi for (i = 0; i < (len & ~1U); i += 2) { 322246896Sluigi sum += (u_int16_t)ntohs(*((u_int16_t *)(addr + i))); 323246896Sluigi if (sum > 0xFFFF) 324246896Sluigi sum -= 0xFFFF; 325227614Sluigi } 326246896Sluigi /* 327246896Sluigi * If there's a single byte left over, checksum it, too. 328246896Sluigi * Network byte order is big-endian, so the remaining byte is 329246896Sluigi * the high byte. 330246896Sluigi */ 331246896Sluigi if (i < len) { 332246896Sluigi sum += addr[i] << 8; 333246896Sluigi if (sum > 0xFFFF) 334246896Sluigi sum -= 0xFFFF; 335246896Sluigi } 336246896Sluigi return sum; 337246896Sluigi} 338227614Sluigi 339246896Sluigistatic u_int16_t 340246896Sluigiwrapsum(u_int32_t sum) 341246896Sluigi{ 342246896Sluigi sum = ~sum & 0xFFFF; 343246896Sluigi return (htons(sum)); 344227614Sluigi} 345227614Sluigi 346227614Sluigi/* 347227614Sluigi * Fill a packet with some payload. 348246896Sluigi * We create a UDP packet so the payload starts at 349246896Sluigi * 14+20+8 = 42 bytes. 350227614Sluigi */ 351246896Sluigi#ifdef __linux__ 352246896Sluigi#define uh_sport source 353246896Sluigi#define uh_dport dest 354246896Sluigi#define uh_ulen len 355246896Sluigi#define uh_sum check 356246896Sluigi#endif /* linux */ 357227614Sluigistatic void 358227614Sluigiinitialize_packet(struct targ *targ) 359227614Sluigi{ 360227614Sluigi struct pkt *pkt = &targ->pkt; 361227614Sluigi struct ether_header *eh; 362227614Sluigi struct ip *ip; 363227614Sluigi struct udphdr *udp; 364246896Sluigi uint16_t paylen = targ->g->pkt_size - sizeof(*eh) - sizeof(struct ip); 365227614Sluigi int i, l, l0 = strlen(default_payload); 366227614Sluigi 367227614Sluigi for (i = 0; i < paylen;) { 368227614Sluigi l = min(l0, paylen - i); 369227614Sluigi bcopy(default_payload, pkt->body + i, l); 370227614Sluigi i += l; 371227614Sluigi } 372227614Sluigi pkt->body[i-1] = '\0'; 373246896Sluigi ip = &pkt->ip; 374227614Sluigi 375227614Sluigi ip->ip_v = IPVERSION; 376227614Sluigi ip->ip_hl = 5; 377227614Sluigi ip->ip_id = 0; 378227614Sluigi ip->ip_tos = IPTOS_LOWDELAY; 379227614Sluigi ip->ip_len = ntohs(targ->g->pkt_size - sizeof(*eh)); 380227614Sluigi ip->ip_id = 0; 381227614Sluigi ip->ip_off = htons(IP_DF); /* Don't fragment */ 382227614Sluigi ip->ip_ttl = IPDEFTTL; 383227614Sluigi ip->ip_p = IPPROTO_UDP; 384246896Sluigi ip->ip_dst.s_addr = targ->g->dst_ip.cur.s_addr; 385246896Sluigi if (++targ->g->dst_ip.cur.s_addr > targ->g->dst_ip.end.s_addr) 386246896Sluigi targ->g->dst_ip.cur.s_addr = targ->g->dst_ip.start.s_addr; 387246896Sluigi ip->ip_src.s_addr = targ->g->src_ip.cur.s_addr; 388246896Sluigi if (++targ->g->src_ip.cur.s_addr > targ->g->src_ip.end.s_addr) 389246896Sluigi targ->g->src_ip.cur.s_addr = targ->g->src_ip.start.s_addr; 390246896Sluigi ip->ip_sum = wrapsum(checksum(ip, sizeof(*ip), 0)); 391227614Sluigi 392246896Sluigi 393246896Sluigi udp = &pkt->udp; 394246896Sluigi udp->uh_sport = htons(targ->g->src_ip.cur_p); 395246896Sluigi if (++targ->g->src_ip.cur_p > targ->g->src_ip.port1) 396246896Sluigi targ->g->src_ip.cur_p = targ->g->src_ip.port0; 397246896Sluigi udp->uh_dport = htons(targ->g->dst_ip.cur_p); 398246896Sluigi if (++targ->g->dst_ip.cur_p > targ->g->dst_ip.port1) 399246896Sluigi targ->g->dst_ip.cur_p = targ->g->dst_ip.port0; 400246896Sluigi udp->uh_ulen = htons(paylen); 401246896Sluigi /* Magic: taken from sbin/dhclient/packet.c */ 402246896Sluigi udp->uh_sum = wrapsum(checksum(udp, sizeof(*udp), 403246896Sluigi checksum(pkt->body, 404246896Sluigi paylen - sizeof(*udp), 405246896Sluigi checksum(&ip->ip_src, 2 * sizeof(ip->ip_src), 406246896Sluigi IPPROTO_UDP + (u_int32_t)ntohs(udp->uh_ulen) 407246896Sluigi ) 408246896Sluigi ) 409246896Sluigi )); 410246896Sluigi 411227614Sluigi eh = &pkt->eh; 412246896Sluigi bcopy(&targ->g->src_mac.start, eh->ether_shost, 6); 413246896Sluigi bcopy(&targ->g->dst_mac.start, eh->ether_dhost, 6); 414227614Sluigi eh->ether_type = htons(ETHERTYPE_IP); 415227614Sluigi} 416227614Sluigi 417227614Sluigi/* Check the payload of the packet for errors (use it for debug). 418227614Sluigi * Look for consecutive ascii representations of the size of the packet. 419227614Sluigi */ 420227614Sluigistatic void 421227614Sluigicheck_payload(char *p, int psize) 422227614Sluigi{ 423227614Sluigi char temp[64]; 424227614Sluigi int n_read, size, sizelen; 425227614Sluigi 426227614Sluigi /* get the length in ASCII of the length of the packet. */ 427227614Sluigi sizelen = sprintf(temp, "%d", psize) + 1; // include a whitespace 428227614Sluigi 429227614Sluigi /* dummy payload. */ 430227614Sluigi p += 14; /* skip packet header. */ 431227614Sluigi n_read = 14; 432227614Sluigi while (psize - n_read >= sizelen) { 433227614Sluigi sscanf(p, "%d", &size); 434227614Sluigi if (size != psize) { 435227614Sluigi D("Read %d instead of %d", size, psize); 436227614Sluigi break; 437227614Sluigi } 438227614Sluigi 439227614Sluigi p += sizelen; 440227614Sluigi n_read += sizelen; 441227614Sluigi } 442227614Sluigi} 443227614Sluigi 444227614Sluigi 445227614Sluigi/* 446227614Sluigi * create and enqueue a batch of packets on a ring. 447227614Sluigi * On the last one set NS_REPORT to tell the driver to generate 448227614Sluigi * an interrupt when done. 449227614Sluigi */ 450227614Sluigistatic int 451227614Sluigisend_packets(struct netmap_ring *ring, struct pkt *pkt, 452234956Sluigi int size, u_int count, int options) 453227614Sluigi{ 454227614Sluigi u_int sent, cur = ring->cur; 455227614Sluigi 456227614Sluigi if (ring->avail < count) 457227614Sluigi count = ring->avail; 458227614Sluigi 459234956Sluigi#if 0 460234956Sluigi if (options & (OPT_COPY | OPT_PREFETCH) ) { 461234956Sluigi for (sent = 0; sent < count; sent++) { 462234956Sluigi struct netmap_slot *slot = &ring->slot[cur]; 463234956Sluigi char *p = NETMAP_BUF(ring, slot->buf_idx); 464234956Sluigi 465234956Sluigi prefetch(p); 466234956Sluigi cur = NETMAP_RING_NEXT(ring, cur); 467234956Sluigi } 468234956Sluigi cur = ring->cur; 469234956Sluigi } 470234956Sluigi#endif 471227614Sluigi for (sent = 0; sent < count; sent++) { 472227614Sluigi struct netmap_slot *slot = &ring->slot[cur]; 473227614Sluigi char *p = NETMAP_BUF(ring, slot->buf_idx); 474227614Sluigi 475234956Sluigi if (options & OPT_COPY) 476234956Sluigi pkt_copy(pkt, p, size); 477234956Sluigi else if (options & OPT_MEMCPY) 478227614Sluigi memcpy(p, pkt, size); 479234956Sluigi else if (options & OPT_PREFETCH) 480234956Sluigi prefetch(p); 481227614Sluigi slot->len = size; 482227614Sluigi if (sent == count - 1) 483227614Sluigi slot->flags |= NS_REPORT; 484227614Sluigi cur = NETMAP_RING_NEXT(ring, cur); 485227614Sluigi } 486227614Sluigi ring->avail -= sent; 487227614Sluigi ring->cur = cur; 488227614Sluigi 489227614Sluigi return (sent); 490227614Sluigi} 491227614Sluigi 492246896Sluigi/* 493246896Sluigi * Send a packet, and wait for a response. 494246896Sluigi * The payload (after UDP header, ofs 42) has a 4-byte sequence 495246896Sluigi * followed by a struct timeval (or bintime?) 496246896Sluigi */ 497246896Sluigi#define PAY_OFS 42 /* where in the pkt... */ 498246896Sluigi 499227614Sluigistatic void * 500246896Sluigipinger_body(void *data) 501246896Sluigi{ 502246896Sluigi struct targ *targ = (struct targ *) data; 503246896Sluigi struct pollfd fds[1]; 504246896Sluigi struct netmap_if *nifp = targ->nifp; 505246896Sluigi int i, rx = 0, n = targ->g->npackets; 506246896Sluigi 507246896Sluigi fds[0].fd = targ->fd; 508246896Sluigi fds[0].events = (POLLIN); 509246896Sluigi static uint32_t sent; 510246896Sluigi struct timespec ts, now, last_print; 511246896Sluigi uint32_t count = 0, min = 1000000000, av = 0; 512246896Sluigi 513246896Sluigi if (targ->g->nthreads > 1) { 514246896Sluigi D("can only ping with 1 thread"); 515246896Sluigi return NULL; 516246896Sluigi } 517246896Sluigi 518246896Sluigi clock_gettime(CLOCK_REALTIME_PRECISE, &last_print); 519246896Sluigi while (n == 0 || (int)sent < n) { 520246896Sluigi struct netmap_ring *ring = NETMAP_TXRING(nifp, 0); 521246896Sluigi struct netmap_slot *slot; 522246896Sluigi char *p; 523246896Sluigi for (i = 0; i < 1; i++) { 524246896Sluigi slot = &ring->slot[ring->cur]; 525246896Sluigi slot->len = targ->g->pkt_size; 526246896Sluigi p = NETMAP_BUF(ring, slot->buf_idx); 527246896Sluigi 528246896Sluigi if (ring->avail == 0) { 529246896Sluigi D("-- ouch, cannot send"); 530246896Sluigi } else { 531246896Sluigi pkt_copy(&targ->pkt, p, targ->g->pkt_size); 532246896Sluigi clock_gettime(CLOCK_REALTIME_PRECISE, &ts); 533246896Sluigi bcopy(&sent, p+42, sizeof(sent)); 534246896Sluigi bcopy(&ts, p+46, sizeof(ts)); 535246896Sluigi sent++; 536246896Sluigi ring->cur = NETMAP_RING_NEXT(ring, ring->cur); 537246896Sluigi ring->avail--; 538246896Sluigi } 539246896Sluigi } 540246896Sluigi /* should use a parameter to decide how often to send */ 541246896Sluigi if (poll(fds, 1, 3000) <= 0) { 542246896Sluigi D("poll error/timeout on queue %d", targ->me); 543246896Sluigi continue; 544246896Sluigi } 545246896Sluigi /* see what we got back */ 546246896Sluigi for (i = targ->qfirst; i < targ->qlast; i++) { 547246896Sluigi ring = NETMAP_RXRING(nifp, i); 548246896Sluigi while (ring->avail > 0) { 549246896Sluigi uint32_t seq; 550246896Sluigi slot = &ring->slot[ring->cur]; 551246896Sluigi p = NETMAP_BUF(ring, slot->buf_idx); 552246896Sluigi 553246896Sluigi clock_gettime(CLOCK_REALTIME_PRECISE, &now); 554246896Sluigi bcopy(p+42, &seq, sizeof(seq)); 555246896Sluigi bcopy(p+46, &ts, sizeof(ts)); 556246896Sluigi ts.tv_sec = now.tv_sec - ts.tv_sec; 557246896Sluigi ts.tv_nsec = now.tv_nsec - ts.tv_nsec; 558246896Sluigi if (ts.tv_nsec < 0) { 559246896Sluigi ts.tv_nsec += 1000000000; 560246896Sluigi ts.tv_sec--; 561246896Sluigi } 562246896Sluigi if (1) D("seq %d/%d delta %d.%09d", seq, sent, 563246896Sluigi (int)ts.tv_sec, (int)ts.tv_nsec); 564246896Sluigi if (ts.tv_nsec < (int)min) 565246896Sluigi min = ts.tv_nsec; 566246896Sluigi count ++; 567246896Sluigi av += ts.tv_nsec; 568246896Sluigi ring->avail--; 569246896Sluigi ring->cur = NETMAP_RING_NEXT(ring, ring->cur); 570246896Sluigi rx++; 571246896Sluigi } 572246896Sluigi } 573246896Sluigi //D("tx %d rx %d", sent, rx); 574246896Sluigi //usleep(100000); 575246896Sluigi ts.tv_sec = now.tv_sec - last_print.tv_sec; 576246896Sluigi ts.tv_nsec = now.tv_nsec - last_print.tv_nsec; 577246896Sluigi if (ts.tv_nsec < 0) { 578246896Sluigi ts.tv_nsec += 1000000000; 579246896Sluigi ts.tv_sec--; 580246896Sluigi } 581246896Sluigi if (ts.tv_sec >= 1) { 582246896Sluigi D("count %d min %d av %d", 583246896Sluigi count, min, av/count); 584246896Sluigi count = 0; 585246896Sluigi av = 0; 586246896Sluigi min = 100000000; 587246896Sluigi last_print = now; 588246896Sluigi } 589246896Sluigi } 590246896Sluigi return NULL; 591246896Sluigi} 592246896Sluigi 593246896Sluigi 594246896Sluigi/* 595246896Sluigi * reply to ping requests 596246896Sluigi */ 597246896Sluigistatic void * 598246896Sluigiponger_body(void *data) 599246896Sluigi{ 600246896Sluigi struct targ *targ = (struct targ *) data; 601246896Sluigi struct pollfd fds[1]; 602246896Sluigi struct netmap_if *nifp = targ->nifp; 603246896Sluigi struct netmap_ring *txring, *rxring; 604246896Sluigi int i, rx = 0, sent = 0, n = targ->g->npackets; 605246896Sluigi fds[0].fd = targ->fd; 606246896Sluigi fds[0].events = (POLLIN); 607246896Sluigi 608246896Sluigi if (targ->g->nthreads > 1) { 609246896Sluigi D("can only reply ping with 1 thread"); 610246896Sluigi return NULL; 611246896Sluigi } 612246896Sluigi D("understood ponger %d but don't know how to do it", n); 613246896Sluigi while (n == 0 || sent < n) { 614246896Sluigi uint32_t txcur, txavail; 615246896Sluigi//#define BUSYWAIT 616246896Sluigi#ifdef BUSYWAIT 617246896Sluigi ioctl(fds[0].fd, NIOCRXSYNC, NULL); 618246896Sluigi#else 619246896Sluigi if (poll(fds, 1, 1000) <= 0) { 620246896Sluigi D("poll error/timeout on queue %d", targ->me); 621246896Sluigi continue; 622246896Sluigi } 623246896Sluigi#endif 624246896Sluigi txring = NETMAP_TXRING(nifp, 0); 625246896Sluigi txcur = txring->cur; 626246896Sluigi txavail = txring->avail; 627246896Sluigi /* see what we got back */ 628246896Sluigi for (i = targ->qfirst; i < targ->qlast; i++) { 629246896Sluigi rxring = NETMAP_RXRING(nifp, i); 630246896Sluigi while (rxring->avail > 0) { 631246896Sluigi uint16_t *spkt, *dpkt; 632246896Sluigi uint32_t cur = rxring->cur; 633246896Sluigi struct netmap_slot *slot = &rxring->slot[cur]; 634246896Sluigi char *src, *dst; 635246896Sluigi src = NETMAP_BUF(rxring, slot->buf_idx); 636246896Sluigi //D("got pkt %p of size %d", src, slot->len); 637246896Sluigi rxring->avail--; 638246896Sluigi rxring->cur = NETMAP_RING_NEXT(rxring, cur); 639246896Sluigi rx++; 640246896Sluigi if (txavail == 0) 641246896Sluigi continue; 642246896Sluigi dst = NETMAP_BUF(txring, 643246896Sluigi txring->slot[txcur].buf_idx); 644246896Sluigi /* copy... */ 645246896Sluigi dpkt = (uint16_t *)dst; 646246896Sluigi spkt = (uint16_t *)src; 647246896Sluigi pkt_copy(src, dst, slot->len); 648246896Sluigi dpkt[0] = spkt[3]; 649246896Sluigi dpkt[1] = spkt[4]; 650246896Sluigi dpkt[2] = spkt[5]; 651246896Sluigi dpkt[3] = spkt[0]; 652246896Sluigi dpkt[4] = spkt[1]; 653246896Sluigi dpkt[5] = spkt[2]; 654246896Sluigi txring->slot[txcur].len = slot->len; 655246896Sluigi /* XXX swap src dst mac */ 656246896Sluigi txcur = NETMAP_RING_NEXT(txring, txcur); 657246896Sluigi txavail--; 658246896Sluigi sent++; 659246896Sluigi } 660246896Sluigi } 661246896Sluigi txring->cur = txcur; 662246896Sluigi txring->avail = txavail; 663246896Sluigi targ->count = sent; 664246896Sluigi#ifdef BUSYWAIT 665246896Sluigi ioctl(fds[0].fd, NIOCTXSYNC, NULL); 666246896Sluigi#endif 667246896Sluigi //D("tx %d rx %d", sent, rx); 668246896Sluigi } 669246896Sluigi return NULL; 670246896Sluigi} 671246896Sluigi 672246896Sluigi 673246896Sluigistatic void * 674227614Sluigisender_body(void *data) 675227614Sluigi{ 676227614Sluigi struct targ *targ = (struct targ *) data; 677246896Sluigi 678227614Sluigi struct pollfd fds[1]; 679227614Sluigi struct netmap_if *nifp = targ->nifp; 680227614Sluigi struct netmap_ring *txring; 681246896Sluigi int i, n = targ->g->npackets / targ->g->nthreads, sent = 0; 682234956Sluigi int options = targ->g->options | OPT_COPY; 683234956SluigiD("start"); 684227614Sluigi if (setaffinity(targ->thread, targ->affinity)) 685227614Sluigi goto quit; 686228975Suqs /* setup poll(2) mechanism. */ 687227614Sluigi memset(fds, 0, sizeof(fds)); 688227614Sluigi fds[0].fd = targ->fd; 689227614Sluigi fds[0].events = (POLLOUT); 690227614Sluigi 691227614Sluigi /* main loop.*/ 692227614Sluigi gettimeofday(&targ->tic, NULL); 693227614Sluigi 694246896Sluigi if (targ->g->dev_type == DEV_PCAP) { 695246896Sluigi int size = targ->g->pkt_size; 696246896Sluigi void *pkt = &targ->pkt; 697246896Sluigi pcap_t *p = targ->g->p; 698246896Sluigi 699246896Sluigi for (i = 0; !targ->cancel && (n == 0 || sent < n); i++) { 700234956Sluigi if (pcap_inject(p, pkt, size) != -1) 701234956Sluigi sent++; 702234956Sluigi if (i > 10000) { 703234956Sluigi targ->count = sent; 704234956Sluigi i = 0; 705234956Sluigi } 706246896Sluigi } 707246896Sluigi } else if (targ->g->dev_type == DEV_TAP) { /* tap */ 708246896Sluigi int size = targ->g->pkt_size; 709246896Sluigi void *pkt = &targ->pkt; 710246896Sluigi D("writing to file desc %d", targ->g->main_fd); 711246896Sluigi 712246896Sluigi for (i = 0; !targ->cancel && (n == 0 || sent < n); i++) { 713246896Sluigi if (write(targ->g->main_fd, pkt, size) != -1) 714246896Sluigi sent++; 715246896Sluigi if (i > 10000) { 716246896Sluigi targ->count = sent; 717246896Sluigi i = 0; 718246896Sluigi } 719246896Sluigi } 720227614Sluigi } else { 721246896Sluigi while (!targ->cancel && (n == 0 || sent < n)) { 722227614Sluigi 723227614Sluigi /* 724227614Sluigi * wait for available room in the send queue(s) 725227614Sluigi */ 726246896Sluigi if (poll(fds, 1, 2000) <= 0) { 727238165Semaste if (targ->cancel) 728238165Semaste break; 729246896Sluigi D("poll error/timeout on queue %d", targ->me); 730227614Sluigi goto quit; 731227614Sluigi } 732227614Sluigi /* 733227614Sluigi * scan our queues and send on those with room 734227614Sluigi */ 735246896Sluigi if (options & OPT_COPY && sent > 100000 && !(targ->g->options & OPT_COPY) ) { 736246896Sluigi D("drop copy"); 737234956Sluigi options &= ~OPT_COPY; 738246896Sluigi } 739246896Sluigi for (i = targ->qfirst; i < targ->qlast; i++) { 740238175Semaste int m, limit = targ->g->burst; 741246896Sluigi if (n > 0 && n - sent < limit) 742246896Sluigi limit = n - sent; 743227614Sluigi txring = NETMAP_TXRING(nifp, i); 744227614Sluigi if (txring->avail == 0) 745227614Sluigi continue; 746227614Sluigi m = send_packets(txring, &targ->pkt, targ->g->pkt_size, 747234956Sluigi limit, options); 748227614Sluigi sent += m; 749227614Sluigi targ->count = sent; 750227614Sluigi } 751227614Sluigi } 752234956Sluigi /* flush any remaining packets */ 753227614Sluigi ioctl(fds[0].fd, NIOCTXSYNC, NULL); 754227614Sluigi 755227614Sluigi /* final part: wait all the TX queues to be empty. */ 756227614Sluigi for (i = targ->qfirst; i < targ->qlast; i++) { 757227614Sluigi txring = NETMAP_TXRING(nifp, i); 758227614Sluigi while (!NETMAP_TX_RING_EMPTY(txring)) { 759227614Sluigi ioctl(fds[0].fd, NIOCTXSYNC, NULL); 760227614Sluigi usleep(1); /* wait 1 tick */ 761227614Sluigi } 762227614Sluigi } 763227614Sluigi } 764227614Sluigi 765227614Sluigi gettimeofday(&targ->toc, NULL); 766227614Sluigi targ->completed = 1; 767227614Sluigi targ->count = sent; 768227614Sluigi 769227614Sluigiquit: 770227614Sluigi /* reset the ``used`` flag. */ 771227614Sluigi targ->used = 0; 772227614Sluigi 773227614Sluigi return (NULL); 774227614Sluigi} 775227614Sluigi 776227614Sluigi 777227614Sluigistatic void 778246896Sluigireceive_pcap(u_char *user, const struct pcap_pkthdr * h, 779246896Sluigi const u_char * bytes) 780227614Sluigi{ 781227614Sluigi int *count = (int *)user; 782246896Sluigi (void)h; /* UNUSED */ 783246896Sluigi (void)bytes; /* UNUSED */ 784227614Sluigi (*count)++; 785227614Sluigi} 786227614Sluigi 787227614Sluigistatic int 788227614Sluigireceive_packets(struct netmap_ring *ring, u_int limit, int skip_payload) 789227614Sluigi{ 790227614Sluigi u_int cur, rx; 791227614Sluigi 792227614Sluigi cur = ring->cur; 793227614Sluigi if (ring->avail < limit) 794227614Sluigi limit = ring->avail; 795227614Sluigi for (rx = 0; rx < limit; rx++) { 796227614Sluigi struct netmap_slot *slot = &ring->slot[cur]; 797227614Sluigi char *p = NETMAP_BUF(ring, slot->buf_idx); 798227614Sluigi 799227614Sluigi if (!skip_payload) 800227614Sluigi check_payload(p, slot->len); 801227614Sluigi 802227614Sluigi cur = NETMAP_RING_NEXT(ring, cur); 803227614Sluigi } 804227614Sluigi ring->avail -= rx; 805227614Sluigi ring->cur = cur; 806227614Sluigi 807227614Sluigi return (rx); 808227614Sluigi} 809227614Sluigi 810227614Sluigistatic void * 811227614Sluigireceiver_body(void *data) 812227614Sluigi{ 813227614Sluigi struct targ *targ = (struct targ *) data; 814227614Sluigi struct pollfd fds[1]; 815227614Sluigi struct netmap_if *nifp = targ->nifp; 816227614Sluigi struct netmap_ring *rxring; 817246896Sluigi int i; 818246896Sluigi uint64_t received = 0; 819227614Sluigi 820227614Sluigi if (setaffinity(targ->thread, targ->affinity)) 821227614Sluigi goto quit; 822227614Sluigi 823228975Suqs /* setup poll(2) mechanism. */ 824227614Sluigi memset(fds, 0, sizeof(fds)); 825227614Sluigi fds[0].fd = targ->fd; 826227614Sluigi fds[0].events = (POLLIN); 827227614Sluigi 828227614Sluigi /* unbounded wait for the first packet. */ 829246896Sluigi for (;;) { 830227614Sluigi i = poll(fds, 1, 1000); 831227614Sluigi if (i > 0 && !(fds[0].revents & POLLERR)) 832227614Sluigi break; 833227614Sluigi D("waiting for initial packets, poll returns %d %d", i, fds[0].revents); 834227614Sluigi } 835227614Sluigi 836227614Sluigi /* main loop, exit after 1s silence */ 837227614Sluigi gettimeofday(&targ->tic, NULL); 838246896Sluigi if (targ->g->dev_type == DEV_PCAP) { 839238165Semaste while (!targ->cancel) { 840246896Sluigi /* XXX should we poll ? */ 841227614Sluigi pcap_dispatch(targ->g->p, targ->g->burst, receive_pcap, NULL); 842227614Sluigi } 843246896Sluigi } else if (targ->g->dev_type == DEV_TAP) { 844246896Sluigi D("reading from %s fd %d", targ->g->ifname, targ->g->main_fd); 845246896Sluigi while (!targ->cancel) { 846246896Sluigi char buf[2048]; 847246896Sluigi /* XXX should we poll ? */ 848246896Sluigi if (read(targ->g->main_fd, buf, sizeof(buf)) > 0) 849246896Sluigi targ->count++; 850246896Sluigi } 851227614Sluigi } else { 852238165Semaste while (!targ->cancel) { 853227614Sluigi /* Once we started to receive packets, wait at most 1 seconds 854227614Sluigi before quitting. */ 855246896Sluigi if (poll(fds, 1, 1 * 1000) <= 0 && targ->g->forever == 0) { 856227614Sluigi gettimeofday(&targ->toc, NULL); 857228975Suqs targ->toc.tv_sec -= 1; /* Subtract timeout time. */ 858227614Sluigi break; 859227614Sluigi } 860227614Sluigi 861227614Sluigi for (i = targ->qfirst; i < targ->qlast; i++) { 862227614Sluigi int m; 863227614Sluigi 864227614Sluigi rxring = NETMAP_RXRING(nifp, i); 865227614Sluigi if (rxring->avail == 0) 866227614Sluigi continue; 867227614Sluigi 868227614Sluigi m = receive_packets(rxring, targ->g->burst, 869227614Sluigi SKIP_PAYLOAD); 870227614Sluigi received += m; 871227614Sluigi } 872246896Sluigi targ->count = received; 873227614Sluigi 874227614Sluigi // tell the card we have read the data 875227614Sluigi //ioctl(fds[0].fd, NIOCRXSYNC, NULL); 876227614Sluigi } 877227614Sluigi } 878227614Sluigi 879227614Sluigi targ->completed = 1; 880227614Sluigi targ->count = received; 881227614Sluigi 882227614Sluigiquit: 883227614Sluigi /* reset the ``used`` flag. */ 884227614Sluigi targ->used = 0; 885227614Sluigi 886227614Sluigi return (NULL); 887227614Sluigi} 888227614Sluigi 889246896Sluigi/* very crude code to print a number in normalized form. 890246896Sluigi * Caller has to make sure that the buffer is large enough. 891246896Sluigi */ 892246896Sluigistatic const char * 893246896Sluiginorm(char *buf, double val) 894238170Semaste{ 895246896Sluigi char *units[] = { "", "K", "M", "G" }; 896246896Sluigi u_int i; 897238170Semaste 898246896Sluigi for (i = 0; val >=1000 && i < sizeof(units)/sizeof(char *); i++) 899238170Semaste val /= 1000; 900246896Sluigi sprintf(buf, "%.2f %s", val, units[i]); 901246896Sluigi return buf; 902238170Semaste} 903238170Semaste 904227614Sluigistatic void 905227614Sluigitx_output(uint64_t sent, int size, double delta) 906227614Sluigi{ 907246896Sluigi double bw, raw_bw, pps; 908246896Sluigi char b1[40], b2[80], b3[80]; 909227614Sluigi 910228276Sluigi printf("Sent %" PRIu64 " packets, %d bytes each, in %.2f seconds.\n", 911227614Sluigi sent, size, delta); 912246896Sluigi if (delta == 0) 913246896Sluigi delta = 1e-6; 914246896Sluigi if (size < 60) /* correct for min packet size */ 915246896Sluigi size = 60; 916246896Sluigi pps = sent / delta; 917246896Sluigi bw = (8.0 * size * sent) / delta; 918246896Sluigi /* raw packets have4 bytes crc + 20 bytes framing */ 919246896Sluigi raw_bw = (8.0 * (size + 24) * sent) / delta; 920238170Semaste 921246896Sluigi printf("Speed: %spps Bandwidth: %sbps (raw %sbps)\n", 922246896Sluigi norm(b1, pps), norm(b2, bw), norm(b3, raw_bw) ); 923227614Sluigi} 924227614Sluigi 925227614Sluigi 926227614Sluigistatic void 927227614Sluigirx_output(uint64_t received, double delta) 928227614Sluigi{ 929246896Sluigi double pps; 930246896Sluigi char b1[40]; 931227614Sluigi 932246896Sluigi printf("Received %" PRIu64 " packets, in %.2f seconds.\n", received, delta); 933227614Sluigi 934246896Sluigi if (delta == 0) 935246896Sluigi delta = 1e-6; 936246896Sluigi pps = received / delta; 937246896Sluigi printf("Speed: %spps\n", norm(b1, pps)); 938227614Sluigi} 939227614Sluigi 940227614Sluigistatic void 941227614Sluigiusage(void) 942227614Sluigi{ 943227614Sluigi const char *cmd = "pkt-gen"; 944227614Sluigi fprintf(stderr, 945227614Sluigi "Usage:\n" 946227614Sluigi "%s arguments\n" 947227614Sluigi "\t-i interface interface name\n" 948246896Sluigi "\t-f function tx rx ping pong\n" 949246896Sluigi "\t-n count number of iterations (can be 0)\n" 950246896Sluigi "\t-t pkts_to_send also forces tx mode\n" 951246896Sluigi "\t-r pkts_to_receive also forces rx mode\n" 952227614Sluigi "\t-l pkts_size in bytes excluding CRC\n" 953227614Sluigi "\t-d dst-ip end with %%n to sweep n addresses\n" 954227614Sluigi "\t-s src-ip end with %%n to sweep n addresses\n" 955227614Sluigi "\t-D dst-mac end with %%n to sweep n addresses\n" 956227614Sluigi "\t-S src-mac end with %%n to sweep n addresses\n" 957246896Sluigi "\t-a cpu_id use setaffinity\n" 958227614Sluigi "\t-b burst size testing, mostly\n" 959227614Sluigi "\t-c cores cores to use\n" 960227614Sluigi "\t-p threads processes/threads to use\n" 961227614Sluigi "\t-T report_ms milliseconds between reports\n" 962246896Sluigi "\t-P use libpcap instead of netmap\n" 963227614Sluigi "\t-w wait_for_link_time in seconds\n" 964227614Sluigi "", 965227614Sluigi cmd); 966227614Sluigi 967227614Sluigi exit(0); 968227614Sluigi} 969227614Sluigi 970246896Sluigistatic void 971246896Sluigistart_threads(struct glob_arg *g) 972246896Sluigi{ 973246896Sluigi int i; 974227614Sluigi 975246896Sluigi targs = calloc(g->nthreads, sizeof(*targs)); 976246896Sluigi /* 977246896Sluigi * Now create the desired number of threads, each one 978246896Sluigi * using a single descriptor. 979246896Sluigi */ 980246896Sluigi for (i = 0; i < g->nthreads; i++) { 981246896Sluigi bzero(&targs[i], sizeof(targs[i])); 982246896Sluigi targs[i].fd = -1; /* default, with pcap */ 983246896Sluigi targs[i].g = g; 984246896Sluigi 985246896Sluigi if (g->dev_type == DEV_NETMAP) { 986246896Sluigi struct nmreq tifreq; 987246896Sluigi int tfd; 988246896Sluigi 989246896Sluigi /* register interface. */ 990246896Sluigi tfd = open("/dev/netmap", O_RDWR); 991246896Sluigi if (tfd == -1) { 992246896Sluigi D("Unable to open /dev/netmap"); 993246896Sluigi continue; 994246896Sluigi } 995246896Sluigi targs[i].fd = tfd; 996246896Sluigi 997246896Sluigi bzero(&tifreq, sizeof(tifreq)); 998246896Sluigi strncpy(tifreq.nr_name, g->ifname, sizeof(tifreq.nr_name)); 999246896Sluigi tifreq.nr_version = NETMAP_API; 1000246896Sluigi tifreq.nr_ringid = (g->nthreads > 1) ? (i | NETMAP_HW_RING) : 0; 1001246896Sluigi 1002246896Sluigi /* 1003246896Sluigi * if we are acting as a receiver only, do not touch the transmit ring. 1004246896Sluigi * This is not the default because many apps may use the interface 1005246896Sluigi * in both directions, but a pure receiver does not. 1006246896Sluigi */ 1007246896Sluigi if (g->td_body == receiver_body) { 1008246896Sluigi tifreq.nr_ringid |= NETMAP_NO_TX_POLL; 1009246896Sluigi } 1010246896Sluigi 1011246896Sluigi if ((ioctl(tfd, NIOCREGIF, &tifreq)) == -1) { 1012246896Sluigi D("Unable to register %s", g->ifname); 1013246896Sluigi continue; 1014246896Sluigi } 1015246896Sluigi targs[i].nmr = tifreq; 1016246896Sluigi targs[i].nifp = NETMAP_IF(g->mmap_addr, tifreq.nr_offset); 1017246896Sluigi /* start threads. */ 1018246896Sluigi targs[i].qfirst = (g->nthreads > 1) ? i : 0; 1019246896Sluigi targs[i].qlast = (g->nthreads > 1) ? i+1 : 1020246896Sluigi (g->td_body == receiver_body ? tifreq.nr_rx_rings : tifreq.nr_tx_rings); 1021246896Sluigi } else { 1022246896Sluigi targs[i].fd = g->main_fd; 1023246896Sluigi } 1024246896Sluigi targs[i].used = 1; 1025246896Sluigi targs[i].me = i; 1026246896Sluigi if (g->affinity >= 0) { 1027246896Sluigi if (g->affinity < g->cpus) 1028246896Sluigi targs[i].affinity = g->affinity; 1029246896Sluigi else 1030246896Sluigi targs[i].affinity = i % g->cpus; 1031246896Sluigi } else 1032246896Sluigi targs[i].affinity = -1; 1033246896Sluigi /* default, init packets */ 1034246896Sluigi initialize_packet(&targs[i]); 1035246896Sluigi 1036246896Sluigi if (pthread_create(&targs[i].thread, NULL, g->td_body, 1037246896Sluigi &targs[i]) == -1) { 1038246896Sluigi D("Unable to create thread %d", i); 1039246896Sluigi targs[i].used = 0; 1040246896Sluigi } 1041246896Sluigi } 1042246896Sluigi} 1043246896Sluigi 1044246896Sluigistatic void 1045246896Sluigimain_thread(struct glob_arg *g) 1046246896Sluigi{ 1047246896Sluigi int i; 1048246896Sluigi 1049246896Sluigi uint64_t prev = 0; 1050246896Sluigi uint64_t count = 0; 1051246896Sluigi double delta_t; 1052246896Sluigi struct timeval tic, toc; 1053246896Sluigi 1054246896Sluigi gettimeofday(&toc, NULL); 1055246896Sluigi for (;;) { 1056246896Sluigi struct timeval now, delta; 1057246896Sluigi uint64_t pps, usec, my_count, npkts; 1058246896Sluigi int done = 0; 1059246896Sluigi 1060246896Sluigi delta.tv_sec = g->report_interval/1000; 1061246896Sluigi delta.tv_usec = (g->report_interval%1000)*1000; 1062246896Sluigi select(0, NULL, NULL, NULL, &delta); 1063246896Sluigi gettimeofday(&now, NULL); 1064246896Sluigi time_second = now.tv_sec; 1065246896Sluigi timersub(&now, &toc, &toc); 1066246896Sluigi my_count = 0; 1067246896Sluigi for (i = 0; i < g->nthreads; i++) { 1068246896Sluigi my_count += targs[i].count; 1069246896Sluigi if (targs[i].used == 0) 1070246896Sluigi done++; 1071246896Sluigi } 1072246896Sluigi usec = toc.tv_sec* 1000000 + toc.tv_usec; 1073246896Sluigi if (usec < 10000) 1074246896Sluigi continue; 1075246896Sluigi npkts = my_count - prev; 1076246896Sluigi pps = (npkts*1000000 + usec/2) / usec; 1077246896Sluigi D("%" PRIu64 " pps (%" PRIu64 " pkts in %" PRIu64 " usec)", 1078246896Sluigi pps, npkts, usec); 1079246896Sluigi prev = my_count; 1080246896Sluigi toc = now; 1081246896Sluigi if (done == g->nthreads) 1082246896Sluigi break; 1083246896Sluigi } 1084246896Sluigi 1085246896Sluigi timerclear(&tic); 1086246896Sluigi timerclear(&toc); 1087246896Sluigi for (i = 0; i < g->nthreads; i++) { 1088246896Sluigi /* 1089246896Sluigi * Join active threads, unregister interfaces and close 1090246896Sluigi * file descriptors. 1091246896Sluigi */ 1092246896Sluigi pthread_join(targs[i].thread, NULL); 1093246896Sluigi close(targs[i].fd); 1094246896Sluigi 1095246896Sluigi if (targs[i].completed == 0) 1096246896Sluigi D("ouch, thread %d exited with error", i); 1097246896Sluigi 1098246896Sluigi /* 1099246896Sluigi * Collect threads output and extract information about 1100246896Sluigi * how long it took to send all the packets. 1101246896Sluigi */ 1102246896Sluigi count += targs[i].count; 1103246896Sluigi if (!timerisset(&tic) || timercmp(&targs[i].tic, &tic, <)) 1104246896Sluigi tic = targs[i].tic; 1105246896Sluigi if (!timerisset(&toc) || timercmp(&targs[i].toc, &toc, >)) 1106246896Sluigi toc = targs[i].toc; 1107246896Sluigi } 1108246896Sluigi 1109246896Sluigi /* print output. */ 1110246896Sluigi timersub(&toc, &tic, &toc); 1111246896Sluigi delta_t = toc.tv_sec + 1e-6* toc.tv_usec; 1112246896Sluigi if (g->td_body == sender_body) 1113246896Sluigi tx_output(count, g->pkt_size, delta_t); 1114246896Sluigi else 1115246896Sluigi rx_output(count, delta_t); 1116246896Sluigi 1117246896Sluigi if (g->dev_type == DEV_NETMAP) { 1118246896Sluigi ioctl(g->main_fd, NIOCUNREGIF, NULL); // XXX deprecated 1119246896Sluigi munmap(g->mmap_addr, g->mmap_size); 1120246896Sluigi close(g->main_fd); 1121246896Sluigi } 1122246896Sluigi} 1123246896Sluigi 1124246896Sluigi 1125246896Sluigistruct sf { 1126246896Sluigi char *key; 1127246896Sluigi void *f; 1128246896Sluigi}; 1129246896Sluigi 1130246896Sluigistatic struct sf func[] = { 1131246896Sluigi { "tx", sender_body }, 1132246896Sluigi { "rx", receiver_body }, 1133246896Sluigi { "ping", pinger_body }, 1134246896Sluigi { "pong", ponger_body }, 1135246896Sluigi { NULL, NULL } 1136246896Sluigi}; 1137246896Sluigi 1138246896Sluigistatic int 1139246896Sluigitap_alloc(char *dev) 1140246896Sluigi{ 1141246896Sluigi struct ifreq ifr; 1142246896Sluigi int fd, err; 1143246896Sluigi char *clonedev = TAP_CLONEDEV; 1144246896Sluigi 1145246896Sluigi (void)err; 1146246896Sluigi (void)dev; 1147246896Sluigi /* Arguments taken by the function: 1148246896Sluigi * 1149246896Sluigi * char *dev: the name of an interface (or '\0'). MUST have enough 1150246896Sluigi * space to hold the interface name if '\0' is passed 1151246896Sluigi * int flags: interface flags (eg, IFF_TUN etc.) 1152246896Sluigi */ 1153246896Sluigi 1154246896Sluigi#ifdef __FreeBSD__ 1155246896Sluigi if (dev[3]) { /* tapSomething */ 1156246896Sluigi static char buf[128]; 1157246896Sluigi snprintf(buf, sizeof(buf), "/dev/%s", dev); 1158246896Sluigi clonedev = buf; 1159246896Sluigi } 1160246896Sluigi#endif 1161246896Sluigi /* open the device */ 1162246896Sluigi if( (fd = open(clonedev, O_RDWR)) < 0 ) { 1163246896Sluigi return fd; 1164246896Sluigi } 1165246896Sluigi D("%s open successful", clonedev); 1166246896Sluigi 1167246896Sluigi /* preparation of the struct ifr, of type "struct ifreq" */ 1168246896Sluigi memset(&ifr, 0, sizeof(ifr)); 1169246896Sluigi 1170246896Sluigi#ifdef linux 1171246896Sluigi ifr.ifr_flags = IFF_TAP | IFF_NO_PI; 1172246896Sluigi 1173246896Sluigi if (*dev) { 1174246896Sluigi /* if a device name was specified, put it in the structure; otherwise, 1175246896Sluigi * the kernel will try to allocate the "next" device of the 1176246896Sluigi * specified type */ 1177246896Sluigi strncpy(ifr.ifr_name, dev, IFNAMSIZ); 1178246896Sluigi } 1179246896Sluigi 1180246896Sluigi /* try to create the device */ 1181246896Sluigi if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) { 1182246896Sluigi D("failed to to a TUNSETIFF"); 1183246896Sluigi close(fd); 1184246896Sluigi return err; 1185246896Sluigi } 1186246896Sluigi 1187246896Sluigi /* if the operation was successful, write back the name of the 1188246896Sluigi * interface to the variable "dev", so the caller can know 1189246896Sluigi * it. Note that the caller MUST reserve space in *dev (see calling 1190246896Sluigi * code below) */ 1191246896Sluigi strcpy(dev, ifr.ifr_name); 1192246896Sluigi D("new name is %s", dev); 1193246896Sluigi#endif /* linux */ 1194246896Sluigi 1195246896Sluigi /* this is the special file descriptor that the caller will use to talk 1196246896Sluigi * with the virtual interface */ 1197246896Sluigi return fd; 1198246896Sluigi} 1199246896Sluigi 1200227614Sluigiint 1201227614Sluigimain(int arc, char **argv) 1202227614Sluigi{ 1203246896Sluigi int i; 1204227614Sluigi 1205227614Sluigi struct glob_arg g; 1206227614Sluigi 1207227614Sluigi struct nmreq nmr; 1208227614Sluigi int ch; 1209227614Sluigi int wait_link = 2; 1210227614Sluigi int devqueues = 1; /* how many device queues */ 1211227614Sluigi 1212227614Sluigi bzero(&g, sizeof(g)); 1213227614Sluigi 1214246896Sluigi g.main_fd = -1; 1215246896Sluigi g.td_body = receiver_body; 1216246896Sluigi g.report_interval = 1000; /* report interval */ 1217246896Sluigi g.affinity = -1; 1218246896Sluigi /* ip addresses can also be a range x.x.x.x-x.x.x.y */ 1219246896Sluigi g.src_ip.name = "10.0.0.1"; 1220246896Sluigi g.dst_ip.name = "10.1.0.1"; 1221246896Sluigi g.dst_mac.name = "ff:ff:ff:ff:ff:ff"; 1222246896Sluigi g.src_mac.name = NULL; 1223227614Sluigi g.pkt_size = 60; 1224227614Sluigi g.burst = 512; // default 1225227614Sluigi g.nthreads = 1; 1226227614Sluigi g.cpus = 1; 1227227614Sluigi 1228227614Sluigi while ( (ch = getopt(arc, argv, 1229246896Sluigi "a:f:n:i:t:r:l:d:s:D:S:b:c:o:p:PT:w:Wv")) != -1) { 1230246896Sluigi struct sf *fn; 1231246896Sluigi 1232227614Sluigi switch(ch) { 1233227614Sluigi default: 1234227614Sluigi D("bad option %c %s", ch, optarg); 1235227614Sluigi usage(); 1236227614Sluigi break; 1237246896Sluigi 1238246896Sluigi case 'n': 1239246896Sluigi g.npackets = atoi(optarg); 1240246896Sluigi break; 1241246896Sluigi 1242246896Sluigi case 'f': 1243246896Sluigi for (fn = func; fn->key; fn++) { 1244246896Sluigi if (!strcmp(fn->key, optarg)) 1245246896Sluigi break; 1246246896Sluigi } 1247246896Sluigi if (fn->key) 1248246896Sluigi g.td_body = fn->f; 1249246896Sluigi else 1250246896Sluigi D("unrecognised function %s", optarg); 1251246896Sluigi break; 1252246896Sluigi 1253246896Sluigi case 'o': /* data generation options */ 1254234956Sluigi g.options = atoi(optarg); 1255234956Sluigi break; 1256246896Sluigi 1257246896Sluigi case 'a': /* force affinity */ 1258246896Sluigi g.affinity = atoi(optarg); 1259246896Sluigi break; 1260246896Sluigi 1261227614Sluigi case 'i': /* interface */ 1262246896Sluigi g.ifname = optarg; 1263246896Sluigi if (!strncmp(optarg, "tap", 3)) 1264246896Sluigi g.dev_type = DEV_TAP; 1265246896Sluigi else 1266246896Sluigi g.dev_type = DEV_NETMAP; 1267227614Sluigi break; 1268246896Sluigi 1269246896Sluigi case 't': /* send, deprecated */ 1270246896Sluigi D("-t deprecated, please use -f tx -n %s", optarg); 1271246896Sluigi g.td_body = sender_body; 1272227614Sluigi g.npackets = atoi(optarg); 1273227614Sluigi break; 1274246896Sluigi 1275227614Sluigi case 'r': /* receive */ 1276246896Sluigi D("-r deprecated, please use -f rx -n %s", optarg); 1277246896Sluigi g.td_body = receiver_body; 1278227614Sluigi g.npackets = atoi(optarg); 1279227614Sluigi break; 1280246896Sluigi 1281227614Sluigi case 'l': /* pkt_size */ 1282227614Sluigi g.pkt_size = atoi(optarg); 1283227614Sluigi break; 1284246896Sluigi 1285227614Sluigi case 'd': 1286246896Sluigi g.dst_ip.name = optarg; 1287227614Sluigi break; 1288246896Sluigi 1289227614Sluigi case 's': 1290246896Sluigi g.src_ip.name = optarg; 1291227614Sluigi break; 1292246896Sluigi 1293227614Sluigi case 'T': /* report interval */ 1294246896Sluigi g.report_interval = atoi(optarg); 1295227614Sluigi break; 1296246896Sluigi 1297227614Sluigi case 'w': 1298227614Sluigi wait_link = atoi(optarg); 1299227614Sluigi break; 1300246896Sluigi 1301246896Sluigi case 'W': 1302246896Sluigi g.forever = 1; /* do not exit rx even with no traffic */ 1303246896Sluigi break; 1304246896Sluigi 1305227614Sluigi case 'b': /* burst */ 1306227614Sluigi g.burst = atoi(optarg); 1307227614Sluigi break; 1308227614Sluigi case 'c': 1309227614Sluigi g.cpus = atoi(optarg); 1310227614Sluigi break; 1311227614Sluigi case 'p': 1312227614Sluigi g.nthreads = atoi(optarg); 1313227614Sluigi break; 1314227614Sluigi 1315227614Sluigi case 'P': 1316246896Sluigi g.dev_type = DEV_PCAP; 1317227614Sluigi break; 1318227614Sluigi 1319227614Sluigi case 'D': /* destination mac */ 1320246896Sluigi g.dst_mac.name = optarg; 1321227614Sluigi break; 1322246896Sluigi 1323227614Sluigi case 'S': /* source mac */ 1324246896Sluigi g.src_mac.name = optarg; 1325227614Sluigi break; 1326227614Sluigi case 'v': 1327227614Sluigi verbose++; 1328227614Sluigi } 1329227614Sluigi } 1330227614Sluigi 1331246896Sluigi if (g.ifname == NULL) { 1332227614Sluigi D("missing ifname"); 1333227614Sluigi usage(); 1334227614Sluigi } 1335246896Sluigi 1336246896Sluigi i = system_ncpus(); 1337246896Sluigi if (g.cpus < 0 || g.cpus > i) { 1338246896Sluigi D("%d cpus is too high, have only %d cpus", g.cpus, i); 1339246896Sluigi usage(); 1340227614Sluigi } 1341246896Sluigi if (g.cpus == 0) 1342246896Sluigi g.cpus = i; 1343246896Sluigi 1344227614Sluigi if (g.pkt_size < 16 || g.pkt_size > 1536) { 1345227614Sluigi D("bad pktsize %d\n", g.pkt_size); 1346227614Sluigi usage(); 1347227614Sluigi } 1348227614Sluigi 1349246896Sluigi if (g.src_mac.name == NULL) { 1350246896Sluigi static char mybuf[20] = "00:00:00:00:00:00"; 1351234956Sluigi /* retrieve source mac address. */ 1352246896Sluigi if (source_hwaddr(g.ifname, mybuf) == -1) { 1353234956Sluigi D("Unable to retrieve source mac"); 1354234956Sluigi // continue, fail later 1355234956Sluigi } 1356246896Sluigi g.src_mac.name = mybuf; 1357234956Sluigi } 1358246896Sluigi /* extract address ranges */ 1359246896Sluigi extract_ip_range(&g.src_ip); 1360246896Sluigi extract_ip_range(&g.dst_ip); 1361246896Sluigi extract_mac_range(&g.src_mac); 1362246896Sluigi extract_mac_range(&g.dst_mac); 1363234956Sluigi 1364246896Sluigi if (g.dev_type == DEV_TAP) { 1365246896Sluigi D("want to use tap %s", g.ifname); 1366246896Sluigi g.main_fd = tap_alloc(g.ifname); 1367246896Sluigi if (g.main_fd < 0) { 1368246896Sluigi D("cannot open tap %s", g.ifname); 1369246896Sluigi usage(); 1370246896Sluigi } 1371246896Sluigi } else if (g.dev_type > DEV_NETMAP) { 1372246896Sluigi char pcap_errbuf[PCAP_ERRBUF_SIZE]; 1373246896Sluigi 1374246896Sluigi D("using pcap on %s", g.ifname); 1375246896Sluigi pcap_errbuf[0] = '\0'; // init the buffer 1376246896Sluigi g.p = pcap_open_live(g.ifname, 0, 1, 100, pcap_errbuf); 1377234956Sluigi if (g.p == NULL) { 1378246896Sluigi D("cannot open pcap on %s", g.ifname); 1379234956Sluigi usage(); 1380234956Sluigi } 1381234956Sluigi } else { 1382227614Sluigi bzero(&nmr, sizeof(nmr)); 1383232238Sluigi nmr.nr_version = NETMAP_API; 1384227614Sluigi /* 1385227614Sluigi * Open the netmap device to fetch the number of queues of our 1386227614Sluigi * interface. 1387227614Sluigi * 1388227614Sluigi * The first NIOCREGIF also detaches the card from the 1389227614Sluigi * protocol stack and may cause a reset of the card, 1390227614Sluigi * which in turn may take some time for the PHY to 1391227614Sluigi * reconfigure. 1392227614Sluigi */ 1393246896Sluigi g.main_fd = open("/dev/netmap", O_RDWR); 1394246896Sluigi if (g.main_fd == -1) { 1395227614Sluigi D("Unable to open /dev/netmap"); 1396246896Sluigi // fail later 1397227614Sluigi } else { 1398246896Sluigi if ((ioctl(g.main_fd, NIOCGINFO, &nmr)) == -1) { 1399227614Sluigi D("Unable to get if info without name"); 1400227614Sluigi } else { 1401227614Sluigi D("map size is %d Kb", nmr.nr_memsize >> 10); 1402227614Sluigi } 1403227614Sluigi bzero(&nmr, sizeof(nmr)); 1404232238Sluigi nmr.nr_version = NETMAP_API; 1405246896Sluigi strncpy(nmr.nr_name, g.ifname, sizeof(nmr.nr_name)); 1406246896Sluigi if ((ioctl(g.main_fd, NIOCGINFO, &nmr)) == -1) { 1407246896Sluigi D("Unable to get if info for %s", g.ifname); 1408227614Sluigi } 1409232238Sluigi devqueues = nmr.nr_rx_rings; 1410227614Sluigi } 1411227614Sluigi 1412227614Sluigi /* validate provided nthreads. */ 1413227614Sluigi if (g.nthreads < 1 || g.nthreads > devqueues) { 1414227614Sluigi D("bad nthreads %d, have %d queues", g.nthreads, devqueues); 1415227614Sluigi // continue, fail later 1416227614Sluigi } 1417227614Sluigi 1418227614Sluigi /* 1419227614Sluigi * Map the netmap shared memory: instead of issuing mmap() 1420227614Sluigi * inside the body of the threads, we prefer to keep this 1421227614Sluigi * operation here to simplify the thread logic. 1422227614Sluigi */ 1423246896Sluigi D("mapping %d Kbytes", nmr.nr_memsize>>10); 1424246896Sluigi g.mmap_size = nmr.nr_memsize; 1425246896Sluigi g.mmap_addr = (struct netmap_d *) mmap(0, nmr.nr_memsize, 1426227614Sluigi PROT_WRITE | PROT_READ, 1427246896Sluigi MAP_SHARED, g.main_fd, 0); 1428246896Sluigi if (g.mmap_addr == MAP_FAILED) { 1429227614Sluigi D("Unable to mmap %d KB", nmr.nr_memsize >> 10); 1430227614Sluigi // continue, fail later 1431227614Sluigi } 1432227614Sluigi 1433227614Sluigi /* 1434227614Sluigi * Register the interface on the netmap device: from now on, 1435227614Sluigi * we can operate on the network interface without any 1436227614Sluigi * interference from the legacy network stack. 1437227614Sluigi * 1438227614Sluigi * We decide to put the first interface registration here to 1439227614Sluigi * give time to cards that take a long time to reset the PHY. 1440227614Sluigi */ 1441232238Sluigi nmr.nr_version = NETMAP_API; 1442246896Sluigi if (ioctl(g.main_fd, NIOCREGIF, &nmr) == -1) { 1443246896Sluigi D("Unable to register interface %s", g.ifname); 1444227614Sluigi //continue, fail later 1445227614Sluigi } 1446227614Sluigi 1447227614Sluigi 1448227614Sluigi /* Print some debug information. */ 1449227614Sluigi fprintf(stdout, 1450227614Sluigi "%s %s: %d queues, %d threads and %d cpus.\n", 1451246896Sluigi (g.td_body == sender_body) ? "Sending on" : "Receiving from", 1452246896Sluigi g.ifname, 1453227614Sluigi devqueues, 1454227614Sluigi g.nthreads, 1455227614Sluigi g.cpus); 1456246896Sluigi if (g.td_body == sender_body) { 1457227614Sluigi fprintf(stdout, "%s -> %s (%s -> %s)\n", 1458246896Sluigi g.src_ip.name, g.dst_ip.name, 1459246896Sluigi g.src_mac.name, g.dst_mac.name); 1460227614Sluigi } 1461227614Sluigi 1462227614Sluigi /* Exit if something went wrong. */ 1463246896Sluigi if (g.main_fd < 0) { 1464227614Sluigi D("aborting"); 1465227614Sluigi usage(); 1466227614Sluigi } 1467234956Sluigi } 1468227614Sluigi 1469234956Sluigi if (g.options) { 1470234956Sluigi D("special options:%s%s%s%s\n", 1471234956Sluigi g.options & OPT_PREFETCH ? " prefetch" : "", 1472234956Sluigi g.options & OPT_ACCESS ? " access" : "", 1473234956Sluigi g.options & OPT_MEMCPY ? " memcpy" : "", 1474234956Sluigi g.options & OPT_COPY ? " copy" : ""); 1475234956Sluigi } 1476227614Sluigi /* Wait for PHY reset. */ 1477227614Sluigi D("Wait %d secs for phy reset", wait_link); 1478227614Sluigi sleep(wait_link); 1479227614Sluigi D("Ready..."); 1480227614Sluigi 1481227614Sluigi /* Install ^C handler. */ 1482227614Sluigi global_nthreads = g.nthreads; 1483227614Sluigi signal(SIGINT, sigint_h); 1484227614Sluigi 1485246896Sluigi#if 0 // XXX this is not needed, i believe 1486246896Sluigi if (g.dev_type > DEV_NETMAP) { 1487246896Sluigi g.p = pcap_open_live(g.ifname, 0, 1, 100, NULL); 1488234956Sluigi if (g.p == NULL) { 1489246896Sluigi D("cannot open pcap on %s", g.ifname); 1490234956Sluigi usage(); 1491234956Sluigi } else 1492246896Sluigi D("using pcap %p on %s", g.p, g.ifname); 1493227614Sluigi } 1494246896Sluigi#endif // XXX 1495246896Sluigi start_threads(&g); 1496246896Sluigi main_thread(&g); 1497246896Sluigi return 0; 1498246896Sluigi} 1499227614Sluigi 1500227614Sluigi/* end of file */ 1501