diff options
author | Daniel Borkmann <dborkman@redhat.com> | 2013-03-15 10:41:48 +0100 |
---|---|---|
committer | Daniel Borkmann <dborkman@redhat.com> | 2013-03-15 10:41:48 +0100 |
commit | 1a9fbac03c684f29cff9ac44875bd9504a89f54e (patch) | |
tree | 1b2e40dbe5dc1899ef5b62c4325c9b94c9c450fc /trafgen.c |
all: import netsniff-ng 0.5.8-rc0 source
We decided to get rid of the old Git history and start a new one for
several reasons:
*) Allow / enforce only high-quality commits (which was not the case
for many commits in the history), have a policy that is more close
to the one from the Linux kernel. With high quality commits, we
mean code that is logically split into commits and commit messages
that are signed-off and have a proper subject and message body.
We do not allow automatic Github merges anymore, since they are
total bullshit. However, we will either cherry-pick your patches
or pull them manually.
*) The old archive was about ~27MB for no particular good reason.
This basically derived from the bad decision that also some PDF
files where stored there. From this moment onwards, no binary
objects are allowed to be stored in this repository anymore.
The old archive is not wiped away from the Internet. You will still
be able to find it, e.g. on git.cryptoism.org etc.
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Diffstat (limited to 'trafgen.c')
-rw-r--r-- | trafgen.c | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/trafgen.c b/trafgen.c new file mode 100644 index 0000000..03c12c5 --- /dev/null +++ b/trafgen.c @@ -0,0 +1,1064 @@ +/* + * netsniff-ng - the packet sniffing beast + * Copyright 2011 - 2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>, + * Swiss federal institute of technology (ETH Zurich) + * Subject to the GPL, version 2. + */ + +#include <stdio.h> +#include <string.h> +#include <getopt.h> +#include <ctype.h> +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/fsuid.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/mman.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <linux/icmp.h> +#include <arpa/inet.h> +#include <signal.h> +#include <stdint.h> +#include <stdlib.h> +#include <fcntl.h> +#include <time.h> +#include <poll.h> +#include <netdb.h> +#include <math.h> +#include <unistd.h> + +#include "xmalloc.h" +#include "die.h" +#include "mac80211.h" +#include "xutils.h" +#include "xio.h" +#include "built_in.h" +#include "trafgen_conf.h" +#include "tprintf.h" +#include "ring_tx.h" +#include "csum.h" + +struct ctx { + bool rand, rfraw, jumbo_support, verbose, smoke_test, enforce; + unsigned long kpull, num, gap, reserve_size, cpus; + uid_t uid; gid_t gid; char *device, *device_trans, *rhost; + struct sockaddr_in dest; +}; + +struct cpu_stats { + unsigned long tv_sec, tv_usec; + unsigned long long tx_packets, tx_bytes; + unsigned long long cf_packets, cf_bytes; + unsigned long long cd_packets; + sig_atomic_t state; +}; + +sig_atomic_t sigint = 0; + +struct packet *packets = NULL; +size_t plen = 0; + +struct packet_dyn *packet_dyn = NULL; +size_t dlen = 0; + +static const char *short_options = "d:c:n:t:vJhS:rk:i:o:VRs:P:eE:pu:g:"; +static const struct option long_options[] = { + {"dev", required_argument, NULL, 'd'}, + {"out", required_argument, NULL, 'o'}, + {"in", required_argument, NULL, 'i'}, + {"conf", required_argument, NULL, 'c'}, + {"num", required_argument, NULL, 'n'}, + {"gap", required_argument, NULL, 't'}, + {"cpus", required_argument, NULL, 'P'}, + {"ring-size", required_argument, NULL, 'S'}, + {"kernel-pull", required_argument, NULL, 'k'}, + {"smoke-test", required_argument, NULL, 's'}, + {"seed", required_argument, NULL, 'E'}, + {"user", required_argument, NULL, 'u'}, + {"group", required_argument, NULL, 'g'}, + {"jumbo-support", no_argument, NULL, 'J'}, + {"cpp", no_argument, NULL, 'p'}, + {"rfraw", no_argument, NULL, 'R'}, + {"rand", no_argument, NULL, 'r'}, + {"verbose", no_argument, NULL, 'V'}, + {"version", no_argument, NULL, 'v'}, + {"example", no_argument, NULL, 'e'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} +}; + +static int sock; + +static struct itimerval itimer; + +static unsigned long interval = TX_KERNEL_PULL_INT; + +static struct cpu_stats *stats; + +unsigned int seed; + +#define CPU_STATS_STATE_CFG 1 +#define CPU_STATS_STATE_CHK 2 +#define CPU_STATS_STATE_RES 4 + +#ifndef ICMP_FILTER +# define ICMP_FILTER 1 + +struct icmp_filter { + __u32 data; +}; +#endif + +static void signal_handler(int number) +{ + switch (number) { + case SIGINT: + sigint = 1; + case SIGHUP: + default: + break; + } +} + +static void timer_elapsed(int number) +{ + set_itimer_interval_value(&itimer, 0, interval); + pull_and_flush_tx_ring(sock); + setitimer(ITIMER_REAL, &itimer, NULL); +} + +static void help(void) +{ + printf("\ntrafgen %s, multithreaded zero-copy network packet generator\n", VERSION_STRING); + puts("http://www.netsniff-ng.org\n\n" + "Usage: trafgen [options]\n" + "Options:\n" + " -i|-c|--in|--conf <cfg/-> Packet configuration file/stdin\n" + " -o|-d|--out|--dev <netdev> Networking device i.e., eth0\n" + " -p|--cpp Run packet config through C preprocessor\n" + " -J|--jumbo-support Support 64KB super jumbo frames (def: 2048B)\n" + " -R|--rfraw Inject raw 802.11 frames\n" + " -s|--smoke-test <ipv4> Probe if machine survived fuzz-tested packet\n" + " -n|--num <uint> Number of packets until exit (def: 0)\n" + " -r|--rand Randomize packet selection (def: round robin)\n" + " -P|--cpus <uint> Specify number of forks(<= CPUs) (def: #CPUs)\n" + " -t|--gap <uint> Interpacket gap in us (approx)\n" + " -S|--ring-size <size> Manually set mmap size (KiB/MiB/GiB)\n" + " -k|--kernel-pull <uint> Kernel batch interval in us (def: 10us)\n" + " -E|--seed <uint> Manually set srand(3) seed\n" + " -u|--user <userid> Drop privileges and change to userid\n" + " -g|--group <groupid> Drop privileges and change to groupid\n" + " -V|--verbose Be more verbose\n" + " -v|--version Show version\n" + " -e|--example Show built-in packet config example\n" + " -h|--help Guess what?!\n\n" + "Examples:\n" + " See trafgen.txf for configuration file examples.\n" + " trafgen --dev eth0 --conf trafgen.cfg\n" + " trafgen -e | trafgen -i - -o eth0 --cpp -n 1\n" + " trafgen --dev eth0 --conf fuzzing.cfg --smoke-test 10.0.0.1\n" + " trafgen --dev wlan0 --rfraw --conf beacon-test.txf -V --cpus 2\n" + " trafgen --dev eth0 --conf frag_dos.cfg --rand --gap 1000\n" + " trafgen --dev eth0 --conf icmp.cfg --rand --num 1400000 -k1000\n" + " trafgen --dev eth0 --conf tcp_syn.cfg -u `id -u bob` -g `id -g bob`\n\n" + "Arbitrary packet config examples (e.g. trafgen -e > trafgen.cfg):\n" + " Run packet on all CPUs: { fill(0xff, 64) csum16(0, 64) }\n" + " Run packet only on CPU1: cpu(1): { rnd(64), 0b11001100, 0xaa }\n" + " Run packet only on CPU1-2: cpu(1:2): { drnd(64),'a',csum16(1, 8),'b',42 }\n\n" + "Note:\n" + " Smoke/fuzz test example: machine A, 10.0.0.2 (trafgen) is directly\n" + " connected to machine B (test kernel), 10.0.0.1. If ICMP reply fails\n" + " we assume the kernel crashed, thus we print the packet and quit.\n" + " In case you find a ping-of-death, please mention trafgen in your\n" + " commit message of the fix!\n\n" + " For introducing bit errors, delays with random variation and more,\n" + " make use of tc(8) with its different disciplines, i.e. netem.\n\n" + " For generating different package distributions, you can use scripting\n" + " to generate a trafgen config file with packet ratios as:\n\n" + " IMIX 64:7, 570:4, 1518:1\n" + " Tolly 64:55, 78:5, 576:17, 1518:23\n" + " Cisco 64:7, 594:4, 1518:1\n" + " RPR Trimodal 64:60, 512:20, 1518:20\n" + " RPR Quadrimodal 64:50, 512:15, 1518:15, 9218:20\n\n" + "Please report bugs to <bugs@netsniff-ng.org>\n" + "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>,\n" + "Swiss federal institute of technology (ETH Zurich)\n" + "License: GNU GPL version 2.0\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); + die(); +} + +static void example(void) +{ + const char *e = + "/* Note: dynamic elements make trafgen slower! */\n" + "#include <stddef.h>\n\n" + "{\n" + " /* MAC Destination */\n" + " fill(0xff, ETH_ALEN),\n" + " /* MAC Source */\n" + " 0x00, 0x02, 0xb3, drnd(3),\n" + " /* IPv4 Protocol */\n" + " c16(ETH_P_IP),\n" + " /* IPv4 Version, IHL, TOS */\n" + " 0b01000101, 0,\n" + " /* IPv4 Total Len */\n" + " c16(58),\n" + " /* IPv4 Ident */\n" + " drnd(2),\n" + " /* IPv4 Flags, Frag Off */\n" + " 0b01000000, 0,\n" + " /* IPv4 TTL */\n" + " 64,\n" + " /* Proto TCP */\n" + " 0x06,\n" + " /* IPv4 Checksum (IP header from, to) */\n" + " csumip(14, 33),\n" + " /* Source IP */\n" + " drnd(4),\n" + " /* Dest IP */\n" + " drnd(4),\n" + " /* TCP Source Port */\n" + " drnd(2),\n" + " /* TCP Dest Port */\n" + " c16(80),\n" + " /* TCP Sequence Number */\n" + " drnd(4),\n" + " /* TCP Ackn. Number */\n" + " c32(0),\n" + " /* TCP Header length + TCP SYN/ECN Flag */\n" + " c16((8 << 12) | TCP_FLAG_SYN | TCP_FLAG_ECE)\n" + " /* Window Size */\n" + " c16(16),\n" + " /* TCP Checksum (offset IP, offset TCP) */\n" + " csumtcp(14, 34),\n" + " /* TCP Options */\n" + " 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x06,\n" + " 0x91, 0x68, 0x7d, 0x06, 0x91, 0x68, 0x6f,\n" + " /* Data blob */\n" + " \"gotcha!\",\n" + "}"; + puts(e); + die(); +} + +static void version(void) +{ + printf("\ntrafgen %s, multithreaded zero-copy network packet generator\n", VERSION_STRING); + puts("http://www.netsniff-ng.org\n\n" + "Please report bugs to <bugs@netsniff-ng.org>\n" + "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>,\n" + "Swiss federal institute of technology (ETH Zurich)\n" + "License: GNU GPL version 2.0\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); + die(); +} + +static void apply_counter(int counter_id) +{ + int j, i = counter_id; + size_t counter_max = packet_dyn[i].clen; + + for (j = 0; j < counter_max; ++j) { + uint8_t val; + struct counter *counter = &packet_dyn[i].cnt[j]; + + val = counter->val - counter->min; + + switch (counter->type) { + case TYPE_INC: + val = (val + counter->inc) % (counter->max - counter->min + 1); + break; + case TYPE_DEC: + val = (val - counter->inc) % (counter->min - counter->max + 1); + break; + default: + bug(); + } + + counter->val = val + counter->min; + packets[i].payload[counter->off] = val; + } +} + +static void apply_randomizer(int rand_id) +{ + int j, i = rand_id; + size_t rand_max = packet_dyn[i].rlen; + + for (j = 0; j < rand_max; ++j) { + uint8_t val = (uint8_t) rand(); + struct randomizer *randomizer = &packet_dyn[i].rnd[j]; + + packets[i].payload[randomizer->off] = val; + } +} + +static void apply_csum16(int csum_id) +{ + int j, i = csum_id; + size_t csum_max = packet_dyn[i].slen; + + for (j = 0; j < csum_max; ++j) { + uint16_t sum = 0; + struct csum16 *csum = &packet_dyn[i].csum[j]; + + fmemset(&packets[i].payload[csum->off], 0, sizeof(sum)); + + switch (csum->which) { + case CSUM_IP: + if (csum->to >= packets[i].len) + csum->to = packets[i].len - 1; + sum = calc_csum(packets[i].payload + csum->from, + csum->to - csum->from + 1, 0); + break; + case CSUM_UDP: + sum = p4_csum((void *) packets[i].payload + csum->from, + packets[i].payload + csum->to, + (packets[i].len - csum->to), + IPPROTO_UDP); + break; + case CSUM_TCP: + sum = p4_csum((void *) packets[i].payload + csum->from, + packets[i].payload + csum->to, + (packets[i].len - csum->to), + IPPROTO_TCP); + break; + } + + fmemcpy(&packets[i].payload[csum->off], &sum, sizeof(sum)); + } +} + +static struct cpu_stats *setup_shared_var(unsigned long cpus) +{ + int fd; + char zbuff[cpus * sizeof(struct cpu_stats)], file[256]; + struct cpu_stats *buff; + + fmemset(zbuff, 0, sizeof(zbuff)); + slprintf(file, sizeof(file), ".tmp_mmap.%u", (unsigned int) rand()); + + fd = creat(file, S_IRUSR | S_IWUSR); + bug_on(fd < 0); + close(fd); + + fd = open_or_die_m(file, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + write_or_die(fd, zbuff, sizeof(zbuff)); + + buff = (void *) mmap(0, sizeof(zbuff), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buff == (void *) -1) + panic("Cannot setup shared variable!\n"); + + close(fd); + unlink(file); + + memset(buff, 0, sizeof(zbuff)); + + return buff; +} + +static void destroy_shared_var(void *buff, unsigned long cpus) +{ + munmap(buff, cpus * sizeof(struct cpu_stats)); +} + +static void dump_trafgen_snippet(uint8_t *payload, size_t len) +{ + int i; + + printf("{"); + for (i = 0; i < len; ++i) { + if (i % 15 == 0) + printf("\n "); + printf("0x%02x, ", payload[i]); + } + printf("\n}\n"); + fflush(stdout); +} + +static int xmit_smoke_setup(struct ctx *ctx) +{ + int icmp_sock, ret, ttl = 64; + struct icmp_filter filter; + + icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (icmp_sock < 0) + panic("Cannot get a ICMP socket: %s!\n", strerror(errno)); + + filter.data = ~(1 << ICMP_ECHOREPLY); + + ret = setsockopt(icmp_sock, SOL_RAW, ICMP_FILTER, &filter, sizeof(filter)); + if (ret < 0) + panic("Cannot install filter!\n"); + + ret = setsockopt(icmp_sock, SOL_IP, IP_TTL, &ttl, sizeof(ttl)); + if (ret < 0) + panic("Cannot set TTL!\n"); + + memset(&ctx->dest, 0, sizeof(ctx->dest)); + ctx->dest.sin_family = AF_INET; + ctx->dest.sin_port = 0; + + ret = inet_aton(ctx->rhost, &ctx->dest.sin_addr); + if (ret < 0) + panic("Cannot resolv address!\n"); + + return icmp_sock; +} + +static int xmit_smoke_probe(int icmp_sock, struct ctx *ctx) +{ + int ret, i, j = 0, probes = 100; + short ident, cnt = 1, idstore[probes]; + uint8_t outpack[512], *data; + struct icmphdr *icmp; + struct iphdr *ip; + size_t len = sizeof(*icmp) + 56; + struct sockaddr_in from; + socklen_t from_len; + struct pollfd fds = { + .fd = icmp_sock, + .events = POLLIN, + }; + + fmemset(idstore, 0, sizeof(idstore)); + while (probes-- > 0) { + while ((ident = htons((short) rand())) == 0) + sleep(0); + idstore[j++] = ident; + + memset(outpack, 0, sizeof(outpack)); + icmp = (void *) outpack; + icmp->type = ICMP_ECHO; + icmp->code = 0; + icmp->checksum = 0; + icmp->un.echo.id = ident; + icmp->un.echo.sequence = htons(cnt++); + + data = ((uint8_t *) outpack + sizeof(*icmp)); + for (i = 0; i < 56; ++i) + data[i] = (uint8_t) rand(); + + icmp->checksum = csum((unsigned short *) outpack, + len / sizeof(unsigned short)); + + ret = sendto(icmp_sock, outpack, len, MSG_DONTWAIT, + (struct sockaddr *) &ctx->dest, sizeof(ctx->dest)); + if (unlikely(ret != len)) + panic("Cannot send out probe: %s!\n", strerror(errno)); + + ret = poll(&fds, 1, 50); + if (ret < 0) + panic("Poll failed!\n"); + + if (fds.revents & POLLIN) { + ret = recvfrom(icmp_sock, outpack, sizeof(outpack), 0, + (struct sockaddr *) &from, &from_len); + if (unlikely(ret <= 0)) + panic("Probe receive failed!\n"); + if (unlikely(from_len != sizeof(ctx->dest))) + continue; + if (unlikely(memcmp(&from, &ctx->dest, sizeof(ctx->dest)))) + continue; + if (unlikely(ret < sizeof(*ip) + sizeof(*icmp))) + continue; + ip = (void *) outpack; + if (unlikely(ip->ihl * 4 + sizeof(*icmp) > ret)) + continue; + icmp = (void *) outpack + ip->ihl * 4; + for (i = 0; i < array_size(idstore); ++i) { + if (unlikely(icmp->un.echo.id != idstore[i])) + continue; + return 0; + } + } + } + + return -1; +} + +static void xmit_slowpath_or_die(struct ctx *ctx, int cpu) +{ + int ret, icmp_sock = -1; + unsigned long num = 1, i = 0; + struct timeval start, end, diff; + unsigned long long tx_bytes = 0, tx_packets = 0; + struct packet_dyn *pktd; + struct sockaddr_ll saddr = { + .sll_family = PF_PACKET, + .sll_halen = ETH_ALEN, + .sll_ifindex = device_ifindex(ctx->device), + }; + + if (ctx->num > 0) + num = ctx->num; + + if (ctx->smoke_test) + icmp_sock = xmit_smoke_setup(ctx); + + drop_privileges(ctx->enforce, ctx->uid, ctx->gid); + + bug_on(gettimeofday(&start, NULL)); + + while (likely(sigint == 0) && likely(num > 0)) { + pktd = &packet_dyn[i]; + if (pktd->clen + pktd->rlen + pktd->slen) { + apply_counter(i); + apply_randomizer(i); + apply_csum16(i); + } +retry: + ret = sendto(sock, packets[i].payload, packets[i].len, 0, + (struct sockaddr *) &saddr, sizeof(saddr)); + if (unlikely(ret < 0)) { + if (errno == ENOBUFS) { + sched_yield(); + goto retry; + } + + panic("Sendto error: %s!\n", strerror(errno)); + } + + tx_bytes += packets[i].len; + tx_packets++; + + if (ctx->smoke_test) { + ret = xmit_smoke_probe(icmp_sock, ctx); + if (unlikely(ret < 0)) { + printf("%sSmoke test alert:%s\n", colorize_start(bold), colorize_end()); + printf(" Remote host seems to be unresponsive to ICMP probes!\n"); + printf(" Last instance was packet%lu, seed:%u, trafgen snippet:\n\n", + i, seed); + + dump_trafgen_snippet(packets[i].payload, packets[i].len); + break; + } + } + + if (!ctx->rand) { + i++; + if (i >= plen) + i = 0; + } else + i = rand() % plen; + + if (ctx->num > 0) + num--; + + if (ctx->gap > 0) + usleep(ctx->gap); + } + + bug_on(gettimeofday(&end, NULL)); + timersub(&end, &start, &diff); + + if (ctx->smoke_test) + close(icmp_sock); + + stats[cpu].tx_packets = tx_packets; + stats[cpu].tx_bytes = tx_bytes; + stats[cpu].tv_sec = diff.tv_sec; + stats[cpu].tv_usec = diff.tv_usec; + + stats[cpu].state |= CPU_STATS_STATE_RES; +} + +static void xmit_fastpath_or_die(struct ctx *ctx, int cpu) +{ + int ifindex = device_ifindex(ctx->device); + uint8_t *out = NULL; + unsigned int it = 0; + unsigned long num = 1, i = 0, size; + struct ring tx_ring; + struct frame_map *hdr; + struct timeval start, end, diff; + struct packet_dyn *pktd; + unsigned long long tx_bytes = 0, tx_packets = 0; + + fmemset(&tx_ring, 0, sizeof(tx_ring)); + + size = ring_size(ctx->device, ctx->reserve_size); + + set_sock_prio(sock, 512); + set_packet_loss_discard(sock); + + setup_tx_ring_layout(sock, &tx_ring, size, ctx->jumbo_support); + create_tx_ring(sock, &tx_ring, ctx->verbose); + mmap_tx_ring(sock, &tx_ring); + alloc_tx_ring_frames(&tx_ring); + bind_tx_ring(sock, &tx_ring, ifindex); + + drop_privileges(ctx->enforce, ctx->uid, ctx->gid); + + if (ctx->kpull) + interval = ctx->kpull; + if (ctx->num > 0) + num = ctx->num; + + set_itimer_interval_value(&itimer, 0, interval); + setitimer(ITIMER_REAL, &itimer, NULL); + + bug_on(gettimeofday(&start, NULL)); + + while (likely(sigint == 0) && likely(num > 0)) { + while (user_may_pull_from_tx(tx_ring.frames[it].iov_base) && likely(num > 0)) { + hdr = tx_ring.frames[it].iov_base; + out = ((uint8_t *) hdr) + TPACKET2_HDRLEN - sizeof(struct sockaddr_ll); + + hdr->tp_h.tp_snaplen = packets[i].len; + hdr->tp_h.tp_len = packets[i].len; + + pktd = &packet_dyn[i]; + if (pktd->clen + pktd->rlen + pktd->slen) { + apply_counter(i); + apply_randomizer(i); + apply_csum16(i); + } + + fmemcpy(out, packets[i].payload, packets[i].len); + + tx_bytes += packets[i].len; + tx_packets++; + + if (!ctx->rand) { + i++; + if (i >= plen) + i = 0; + } else + i = rand() % plen; + + kernel_may_pull_from_tx(&hdr->tp_h); + + it++; + if (it >= tx_ring.layout.tp_frame_nr) + it = 0; + + if (ctx->num > 0) + num--; + + if (unlikely(sigint == 1)) + break; + } + } + + bug_on(gettimeofday(&end, NULL)); + timersub(&end, &start, &diff); + + destroy_tx_ring(sock, &tx_ring); + + stats[cpu].tx_packets = tx_packets; + stats[cpu].tx_bytes = tx_bytes; + stats[cpu].tv_sec = diff.tv_sec; + stats[cpu].tv_usec = diff.tv_usec; + + stats[cpu].state |= CPU_STATS_STATE_RES; +} + +static inline void __set_state(int cpu, sig_atomic_t s) +{ + stats[cpu].state = s; +} + +static inline sig_atomic_t __get_state(int cpu) +{ + return stats[cpu].state; +} + +static unsigned long __wait_and_sum_others(struct ctx *ctx, int cpu) +{ + int i; + unsigned long total; + + for (i = 0, total = plen; i < ctx->cpus; i++) { + if (i == cpu) + continue; + + while ((__get_state(i) & CPU_STATS_STATE_CFG) == 0 && + sigint == 0) + sched_yield(); + + total += stats[i].cf_packets; + } + + return total; +} + +static void __correct_global_delta(struct ctx *ctx, int cpu, unsigned long orig) +{ + int i, cpu_sel; + unsigned long total; + long long delta_correction = 0; + + for (i = 0, total = ctx->num; i < ctx->cpus; i++) { + if (i == cpu) + continue; + + while ((__get_state(i) & CPU_STATS_STATE_CHK) == 0 && + sigint == 0) + sched_yield(); + + total += stats[i].cd_packets; + } + + if (total > orig) + delta_correction = -1 * ((long long) total - orig); + if (total < orig) + delta_correction = +1 * ((long long) orig - total); + + for (cpu_sel = -1, i = 0; i < ctx->cpus; i++) { + if (stats[i].cd_packets > 0) { + if ((long long) stats[i].cd_packets + + delta_correction > 0) { + cpu_sel = i; + break; + } + } + } + + if (cpu == cpu_sel) + ctx->num += delta_correction; +} + +static void __set_state_cf(int cpu, unsigned long p, unsigned long b, + sig_atomic_t s) +{ + stats[cpu].cf_packets = p; + stats[cpu].cf_bytes = b; + stats[cpu].state = s; +} + +static void __set_state_cd(int cpu, unsigned long p, sig_atomic_t s) +{ + stats[cpu].cd_packets = p; + stats[cpu].state = s; +} + +static int xmit_packet_precheck(struct ctx *ctx, int cpu) +{ + int i; + unsigned long plen_total, orig = ctx->num; + size_t mtu, total_len = 0; + + bug_on(plen != dlen); + + for (i = 0; i < plen; ++i) + total_len += packets[i].len; + + __set_state_cf(cpu, plen, total_len, CPU_STATS_STATE_CFG); + plen_total = __wait_and_sum_others(ctx, cpu); + + if (orig > 0) { + ctx->num = (unsigned long) nearbyint((1.0 * plen / plen_total) * orig); + + __set_state_cd(cpu, ctx->num, CPU_STATS_STATE_CHK | + CPU_STATS_STATE_CFG); + __correct_global_delta(ctx, cpu, orig); + } + + if (plen == 0) { + __set_state(cpu, CPU_STATS_STATE_RES); + return -1; + } + + for (mtu = device_mtu(ctx->device), i = 0; i < plen; ++i) { + if (packets[i].len > mtu + 14) + panic("Device MTU < than packet%d's size!\n", i); + if (packets[i].len <= 14) + panic("Packet%d's size too short!\n", i); + } + + return 0; +} + +static void main_loop(struct ctx *ctx, char *confname, bool slow, + int cpu, bool invoke_cpp) +{ + compile_packets(confname, ctx->verbose, cpu, invoke_cpp); + if (xmit_packet_precheck(ctx, cpu) < 0) + return; + + if (cpu == 0) { + int i; + size_t total_len = 0, total_pkts = 0; + + for (i = 0; i < ctx->cpus; ++i) { + total_len += stats[i].cf_bytes; + total_pkts += stats[i].cf_packets; + } + + printf("%6zu packets to schedule\n", total_pkts); + printf("%6zu bytes in total\n", total_len); + printf("Running! Hang up with ^C!\n\n"); + fflush(stdout); + } + + sock = pf_socket(); + + if (slow) + xmit_slowpath_or_die(ctx, cpu); + else + xmit_fastpath_or_die(ctx, cpu); + + close(sock); + + cleanup_packets(); +} + +static unsigned int generate_srand_seed(void) +{ + int fd; + unsigned int seed; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return time(0); + + read_or_die(fd, &seed, sizeof(seed)); + + close(fd); + return seed; +} + +int main(int argc, char **argv) +{ + bool slow = false, invoke_cpp = false, reseed = true; + int c, opt_index, i, j, vals[4] = {0}, irq; + char *confname = NULL, *ptr; + unsigned long cpus_tmp; + unsigned long long tx_packets, tx_bytes; + struct ctx ctx; + + fmemset(&ctx, 0, sizeof(ctx)); + ctx.cpus = get_number_cpus_online(); + ctx.uid = getuid(); + ctx.gid = getgid(); + + while ((c = getopt_long(argc, argv, short_options, long_options, + &opt_index)) != EOF) { + switch (c) { + case 'h': + help(); + break; + case 'v': + version(); + break; + case 'e': + example(); + break; + case 'p': + invoke_cpp = true; + break; + case 'V': + ctx.verbose = true; + break; + case 'P': + cpus_tmp = strtoul(optarg, NULL, 0); + if (cpus_tmp > 0 && cpus_tmp < ctx.cpus) + ctx.cpus = cpus_tmp; + break; + case 'd': + case 'o': + ctx.device = xstrndup(optarg, IFNAMSIZ); + break; + case 'r': + ctx.rand = true; + break; + case 's': + slow = true; + ctx.cpus = 1; + ctx.smoke_test = true; + ctx.rhost = xstrdup(optarg); + break; + case 'R': + ctx.rfraw = true; + break; + case 'J': + ctx.jumbo_support = true; + break; + case 'c': + case 'i': + confname = xstrdup(optarg); + if (!strncmp("-", confname, strlen("-"))) + ctx.cpus = 1; + break; + case 'u': + ctx.uid = strtoul(optarg, NULL, 0); + ctx.enforce = true; + break; + case 'g': + ctx.gid = strtoul(optarg, NULL, 0); + ctx.enforce = true; + break; + case 'k': + ctx.kpull = strtoul(optarg, NULL, 0); + break; + case 'E': + seed = strtoul(optarg, NULL, 0); + reseed = false; + break; + case 'n': + ctx.num = strtoul(optarg, NULL, 0); + break; + case 't': + slow = true; + ctx.gap = strtoul(optarg, NULL, 0); + if (ctx.gap > 0) + /* Fall back to single core to not + * mess up correct timing. We are slow + * anyway! + */ + ctx.cpus = 1; + break; + case 'S': + ptr = optarg; + ctx.reserve_size = 0; + + for (j = i = strlen(optarg); i > 0; --i) { + if (!isdigit(optarg[j - i])) + break; + ptr++; + } + + if (!strncmp(ptr, "KiB", strlen("KiB"))) + ctx.reserve_size = 1 << 10; + else if (!strncmp(ptr, "MiB", strlen("MiB"))) + ctx.reserve_size = 1 << 20; + else if (!strncmp(ptr, "GiB", strlen("GiB"))) + ctx.reserve_size = 1 << 30; + else + panic("Syntax error in ring size param!\n"); + *ptr = 0; + + ctx.reserve_size *= strtol(optarg, NULL, 0); + break; + case '?': + switch (optopt) { + case 'd': + case 'c': + case 'n': + case 'S': + case 's': + case 'P': + case 'o': + case 'E': + case 'i': + case 'k': + case 'u': + case 'g': + case 't': + panic("Option -%c requires an argument!\n", + optopt); + default: + if (isprint(optopt)) + printf("Unknown option character `0x%X\'!\n", optopt); + die(); + } + default: + break; + } + } + + if (argc < 5) + help(); + if (ctx.device == NULL) + panic("No networking device given!\n"); + if (confname == NULL) + panic("No configuration file given!\n"); + if (device_mtu(ctx.device) == 0) + panic("This is no networking device!\n"); + if (!ctx.rfraw && device_up_and_running(ctx.device) == 0) + panic("Networking device not running!\n"); + + register_signal(SIGINT, signal_handler); + register_signal(SIGHUP, signal_handler); + register_signal_f(SIGALRM, timer_elapsed, SA_SIGINFO); + + set_system_socket_memory(vals, array_size(vals)); + xlockme(); + + if (ctx.rfraw) { + ctx.device_trans = xstrdup(ctx.device); + xfree(ctx.device); + + enter_rfmon_mac80211(ctx.device_trans, &ctx.device); + sleep(0); + } + + irq = device_irq_number(ctx.device); + device_set_irq_affinity_list(irq, 0, ctx.cpus - 1); + + if (ctx.num > 0 && ctx.num <= ctx.cpus) + ctx.cpus = 1; + + stats = setup_shared_var(ctx.cpus); + + for (i = 0; i < ctx.cpus; i++) { + pid_t pid = fork(); + + switch (pid) { + case 0: + if (reseed) + seed = generate_srand_seed(); + srand(seed); + + cpu_affinity(i); + main_loop(&ctx, confname, slow, i, invoke_cpp); + + goto thread_out; + case -1: + panic("Cannot fork processes!\n"); + } + } + + for (i = 0; i < ctx.cpus; i++) { + int status; + + wait(&status); + if (WEXITSTATUS(status) == EXIT_FAILURE) + die(); + } + + if (ctx.rfraw) + leave_rfmon_mac80211(ctx.device_trans, ctx.device); + + reset_system_socket_memory(vals, array_size(vals)); + + for (i = 0, tx_packets = tx_bytes = 0; i < ctx.cpus; i++) { + while ((__get_state(i) & CPU_STATS_STATE_RES) == 0) + sched_yield(); + + tx_packets += stats[i].tx_packets; + tx_bytes += stats[i].tx_bytes; + } + + fflush(stdout); + printf("\n"); + printf("\r%12llu packets outgoing\n", tx_packets); + printf("\r%12llu bytes outgoing\n", tx_bytes); + for (i = 0; i < ctx.cpus; i++) { + printf("\r%12lu sec, %lu usec on CPU%d (%llu packets)\n", + stats[i].tv_sec, stats[i].tv_usec, i, + stats[i].tx_packets); + } + +thread_out: + xunlockme(); + destroy_shared_var(stats, ctx.cpus); + + free(ctx.device); + free(ctx.device_trans); + free(ctx.rhost); + free(confname); + + return 0; +} |