| /* pg3.c: Packet Generator for packet performance testing. |
| * |
| * Copyright 2001 by Robert Olsson <robert.olsson@its.uu.se> |
| * Uppsala University, Sweden |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * |
| * |
| */ |
| |
| /* |
| |
| A tool for loading a network with a preconfigurated packets. The tool is |
| implemented as a linux module. Parameters as output device IPG interpacket |
| packet, number of packets can be configured. pg uses already intalled |
| device driver output routine. |
| |
| |
| Additional hacking by: |
| |
| Jens.Laas@data.slu.se |
| Improved by ANK. 010120. |
| Improved by ANK even more. 010212. |
| MAC address typo fixed. 010417 --ro |
| |
| |
| TODO: |
| * could release kernel lock yet. |
| |
| |
| HOWTO: |
| |
| 1. Compile module pg3.o and install it in the place where modprobe may find it. |
| 2. Cut script "ipg" (see below). |
| 3. Edit script to set preferred device and destination IP address. |
| 4. . ipg |
| 5. After this two commands are defined: |
| A. "pg" to start generator and to get results. |
| B. "pgset" to change generator parameters. F.e. |
| pgset "pkt_size 9014" sets packet size to 9014 |
| pgset "frags 5" packet will consist of 5 fragments |
| pgset "count 200000" sets number of packets to send |
| pgset "ipg 5000" sets artificial gap inserted between packets |
| to 5000 nanoseconds |
| pgset "dst 10.0.0.1" sets IP destination address |
| (BEWARE! This generator is very aggressive!) |
| pgset "dstmac 00:00:00:00:00:00" sets MAC destination address |
| pgset stop aborts injection |
| |
| Also, ^C aborts generator. |
| |
| ---- cut here |
| |
| #! /bin/sh |
| |
| modprobe pg3.o |
| |
| function pgset() { |
| local result |
| |
| echo $1 > /proc/net/pg |
| |
| result=`cat /proc/net/pg | fgrep "Result: OK:"` |
| if [ "$result" = "" ]; then |
| cat /proc/net/pg | fgrep Result: |
| fi |
| } |
| |
| function pg() { |
| echo inject > /proc/net/pg |
| cat /proc/net/pg |
| } |
| |
| pgset "odev eth0" |
| pgset "dst 0.0.0.0" |
| |
| ---- cut here |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/string.h> |
| #include <linux/ptrace.h> |
| #include <linux/errno.h> |
| #include <linux/ioport.h> |
| #include <linux/malloc.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/delay.h> |
| #include <linux/init.h> |
| #include <linux/inet.h> |
| #include <asm/byteorder.h> |
| #include <asm/bitops.h> |
| #include <asm/io.h> |
| #include <asm/dma.h> |
| |
| #include <linux/in.h> |
| #include <linux/ip.h> |
| #include <linux/udp.h> |
| #include <linux/skbuff.h> |
| #include <linux/netdevice.h> |
| #include <linux/inetdevice.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/proc_fs.h> |
| #include <linux/if_arp.h> |
| #include <net/checksum.h> |
| |
| static char version[] __initdata = |
| "pg3.c: v1.0 010812: Packet Generator for packet performance testing.\n"; |
| |
| |
| |
| /* Parameters */ |
| |
| char pg_outdev[32], pg_dst[32]; |
| int pkt_size=ETH_ZLEN; |
| int nfrags=0; |
| __u32 pg_count = 100000; /* Default No packets to send */ |
| __u32 pg_ipg = 0; /* Default Interpacket gap in nsec */ |
| |
| /* Globar vars */ |
| |
| int debug; |
| int forced_stop; |
| int pg_cpu_speed; |
| int pg_busy; |
| |
| static __u8 hh[14] = { |
| 0x00, 0x80, 0xC8, 0x79, 0xB3, 0xCB, |
| |
| /* We fill in SRC address later */ |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x08, 0x00 |
| }; |
| |
| unsigned char *pg_dstmac = hh; |
| char pg_result[512]; |
| |
| |
| static struct net_device *pg_setup_inject(u32 *saddrp) |
| { |
| int p1, p2; |
| struct net_device *odev; |
| u32 saddr; |
| |
| rtnl_lock(); |
| odev = __dev_get_by_name(pg_outdev); |
| if (!odev) { |
| sprintf(pg_result, "No such netdevice: \"%s\"", pg_outdev); |
| goto out_unlock; |
| } |
| |
| if (odev->type != ARPHRD_ETHER) { |
| sprintf(pg_result, "Not ethernet device: \"%s\"", pg_outdev); |
| goto out_unlock; |
| } |
| |
| if (!netif_running(odev)) { |
| sprintf(pg_result, "Device is down: \"%s\"", pg_outdev); |
| goto out_unlock; |
| } |
| |
| for(p1=6,p2=0; p1 < odev->addr_len+6;p1++) |
| hh[p1]=odev->dev_addr[p2++]; |
| |
| saddr = 0; |
| if (odev->ip_ptr) { |
| struct in_device *in_dev = odev->ip_ptr; |
| |
| if (in_dev->ifa_list) |
| saddr = in_dev->ifa_list->ifa_address; |
| } |
| atomic_inc(&odev->refcnt); |
| rtnl_unlock(); |
| |
| *saddrp = saddr; |
| return odev; |
| |
| out_unlock: |
| rtnl_unlock(); |
| return NULL; |
| } |
| |
| |
| u32 idle_acc_lo, idle_acc_hi; |
| |
| void nanospin(int pg_ipg) |
| { |
| u32 idle_start, idle; |
| |
| idle_start = get_cycles(); |
| |
| for (;;) { |
| barrier(); |
| idle = get_cycles() - idle_start; |
| if (idle*1000 >= pg_ipg*pg_cpu_speed) |
| break; |
| } |
| idle_acc_lo += idle; |
| if (idle_acc_lo < idle) |
| idle_acc_hi++; |
| } |
| |
| int calc_mhz(void) |
| { |
| struct timeval start, stop; |
| u32 start_s, elapsed; |
| |
| do_gettimeofday(&start); |
| start_s = get_cycles(); |
| do { |
| barrier(); |
| elapsed = get_cycles() - start_s; |
| if (elapsed == 0) |
| return 0; |
| } while (elapsed < 1000*50000); |
| do_gettimeofday(&stop); |
| return elapsed/(stop.tv_usec-start.tv_usec+1000000*(stop.tv_sec-start.tv_sec)); |
| } |
| |
| static void cycles_calibrate(void) |
| { |
| int i; |
| |
| for (i=0; i<3; i++) { |
| int res = calc_mhz(); |
| if (res > pg_cpu_speed) |
| pg_cpu_speed = res; |
| } |
| } |
| |
| struct sk_buff * |
| fill_packet(struct net_device *odev, __u32 saddr) |
| { |
| struct sk_buff *skb; |
| __u8 *eth; |
| struct udphdr *udph; |
| int datalen, iplen; |
| struct iphdr *iph; |
| |
| skb = alloc_skb(pkt_size+64+16, GFP_ATOMIC); |
| if (!skb) { |
| sprintf(pg_result, "No memory"); |
| return NULL; |
| } |
| |
| skb_reserve(skb, 16); |
| |
| /* Reserve for ethernet and IP header */ |
| eth = (__u8 *) skb_push(skb, 14); |
| iph = (struct iphdr*)skb_put(skb, sizeof( struct iphdr)); |
| udph = (struct udphdr*)skb_put(skb, sizeof( struct udphdr)); |
| |
| /* Copy the ethernet header */ |
| memcpy(eth, hh, 14); |
| |
| datalen = pkt_size-14-20-8; /* Eth + IPh + UDPh */ |
| if (datalen < 0) |
| datalen = 0; |
| |
| udph->source= htons(9); |
| udph->dest= htons(9); |
| udph->len= htons(datalen+8); /* DATA + udphdr */ |
| udph->check=0; /* No checksum */ |
| |
| iph->ihl=5; |
| iph->version=4; |
| iph->ttl=3; |
| iph->tos=0; |
| iph->protocol = IPPROTO_UDP; /* UDP */ |
| iph->saddr = saddr; |
| iph->daddr = in_aton(pg_dst); |
| iph->frag_off = 0; |
| iplen = 20 + 8 + datalen; |
| iph->tot_len = htons(iplen); |
| iph->check = 0; |
| iph->check = ip_fast_csum((void *)iph, iph->ihl); |
| skb->protocol = __constant_htons(ETH_P_IP); |
| skb->mac.raw = ((u8*)iph) - 14; |
| skb->dev = odev; |
| skb->pkt_type = PACKET_HOST; |
| |
| if (nfrags<=0) { |
| skb_put(skb, datalen); |
| } else { |
| int frags = nfrags; |
| int i; |
| |
| if (frags > MAX_SKB_FRAGS) |
| frags = MAX_SKB_FRAGS; |
| if (datalen > frags*PAGE_SIZE) { |
| skb_put(skb, datalen-frags*PAGE_SIZE); |
| datalen = frags*PAGE_SIZE; |
| } |
| |
| i = 0; |
| while (datalen > 0) { |
| struct page *page = alloc_pages(GFP_KERNEL, 0); |
| skb_shinfo(skb)->frags[i].page = page; |
| skb_shinfo(skb)->frags[i].page_offset = 0; |
| skb_shinfo(skb)->frags[i].size = (datalen < PAGE_SIZE ? datalen : PAGE_SIZE); |
| datalen -= skb_shinfo(skb)->frags[i].size; |
| skb->len += skb_shinfo(skb)->frags[i].size; |
| skb->data_len += skb_shinfo(skb)->frags[i].size; |
| i++; |
| skb_shinfo(skb)->nr_frags = i; |
| } |
| |
| while (i < frags) { |
| int rem; |
| |
| if (i == 0) |
| break; |
| |
| rem = skb_shinfo(skb)->frags[i-1].size/2; |
| if (rem == 0) |
| break; |
| |
| skb_shinfo(skb)->frags[i-1].size -= rem; |
| |
| skb_shinfo(skb)->frags[i] = skb_shinfo(skb)->frags[i-1]; |
| get_page(skb_shinfo(skb)->frags[i].page); |
| skb_shinfo(skb)->frags[i].page = skb_shinfo(skb)->frags[i-1].page; |
| skb_shinfo(skb)->frags[i].page_offset += skb_shinfo(skb)->frags[i-1].size; |
| skb_shinfo(skb)->frags[i].size = rem; |
| i++; |
| skb_shinfo(skb)->nr_frags = i; |
| } |
| } |
| |
| return skb; |
| } |
| |
| |
| static void pg_inject(void) |
| { |
| u32 saddr; |
| struct net_device *odev; |
| struct sk_buff *skb; |
| struct timeval start, stop; |
| u32 total, idle; |
| int pc, lcount; |
| |
| odev = pg_setup_inject(&saddr); |
| if (!odev) |
| return; |
| |
| skb = fill_packet(odev, saddr); |
| if (skb == NULL) |
| goto out_reldev; |
| |
| forced_stop = 0; |
| idle_acc_hi = 0; |
| idle_acc_lo = 0; |
| pc = 0; |
| lcount = pg_count; |
| do_gettimeofday(&start); |
| |
| for(;;) { |
| spin_lock_bh(&odev->xmit_lock); |
| atomic_inc(&skb->users); |
| if (!netif_queue_stopped(odev)) { |
| if (odev->hard_start_xmit(skb, odev)) { |
| kfree_skb(skb); |
| if (net_ratelimit()) |
| printk(KERN_INFO "Hard xmit error\n"); |
| } |
| pc++; |
| } else { |
| kfree_skb(skb); |
| } |
| spin_unlock_bh(&odev->xmit_lock); |
| |
| if (pg_ipg) |
| nanospin(pg_ipg); |
| if (forced_stop) |
| goto out_intr; |
| if (signal_pending(current)) |
| goto out_intr; |
| |
| if (--lcount == 0) { |
| if (atomic_read(&skb->users) != 1) { |
| u32 idle_start, idle; |
| |
| idle_start = get_cycles(); |
| while (atomic_read(&skb->users) != 1) { |
| if (signal_pending(current)) |
| goto out_intr; |
| schedule(); |
| } |
| idle = get_cycles() - idle_start; |
| idle_acc_lo += idle; |
| if (idle_acc_lo < idle) |
| idle_acc_hi++; |
| } |
| break; |
| } |
| |
| if (netif_queue_stopped(odev) || current->need_resched) { |
| u32 idle_start, idle; |
| |
| idle_start = get_cycles(); |
| do { |
| if (signal_pending(current)) |
| goto out_intr; |
| if (!netif_running(odev)) |
| goto out_intr; |
| if (current->need_resched) |
| schedule(); |
| else |
| do_softirq(); |
| } while (netif_queue_stopped(odev)); |
| idle = get_cycles() - idle_start; |
| idle_acc_lo += idle; |
| if (idle_acc_lo < idle) |
| idle_acc_hi++; |
| } |
| } |
| |
| do_gettimeofday(&stop); |
| |
| total = (stop.tv_sec - start.tv_sec)*1000000 + |
| stop.tv_usec - start.tv_usec; |
| |
| idle = (((idle_acc_hi<<20)/pg_cpu_speed)<<12)+idle_acc_lo/pg_cpu_speed; |
| |
| if (1) { |
| char *p = pg_result; |
| |
| p += sprintf(p, "OK: %u(c%u+d%u) usec, %u (%dbyte,%dfrags) %upps %uMB/sec", |
| total, total-idle, idle, |
| pc, skb->len, skb_shinfo(skb)->nr_frags, |
| ((pc*1000)/(total/1000)), |
| (((pc*1000)/(total/1000))*pkt_size)/1024/1024 |
| ); |
| } |
| |
| out_relskb: |
| kfree_skb(skb); |
| out_reldev: |
| dev_put(odev); |
| return; |
| |
| out_intr: |
| sprintf(pg_result, "Interrupted"); |
| goto out_relskb; |
| } |
| |
| /* proc/net/pg */ |
| |
| static struct proc_dir_entry *pg_proc_ent = 0; |
| static struct proc_dir_entry *pg_busy_proc_ent = 0; |
| |
| int proc_pg_busy_read(char *buf , char **start, off_t offset, |
| int len, int *eof, void *data) |
| { |
| char *p; |
| |
| p = buf; |
| p += sprintf(p, "%d\n", pg_busy); |
| *eof = 1; |
| |
| return p-buf; |
| } |
| |
| int proc_pg_read(char *buf , char **start, off_t offset, |
| int len, int *eof, void *data) |
| { |
| char *p; |
| int i; |
| |
| p = buf; |
| p += sprintf(p, "Params: count=%u pkt_size=%u frags %d ipg %u odev \"%s\" dst %s dstmac ", |
| pg_count, pkt_size, nfrags, pg_ipg, |
| pg_outdev, pg_dst); |
| for(i=0;i<6;i++) |
| p += sprintf(p, "%02X%s", pg_dstmac[i], i == 5 ? "\n" : ":"); |
| |
| if(pg_result[0]) |
| p += sprintf(p, "Result: %s\n", pg_result); |
| else |
| p += sprintf(p, "Result: Idle\n"); |
| *eof = 1; |
| return p-buf; |
| } |
| |
| int count_trail_chars(const char *buffer, unsigned int maxlen) |
| { |
| int i; |
| |
| for(i=0; i<maxlen;i++) { |
| switch(buffer[i]) { |
| case '\"': |
| case '\n': |
| case '\r': |
| case '\t': |
| case ' ': |
| case '=': |
| break; |
| default: |
| goto done; |
| } |
| } |
| done: |
| return i; |
| } |
| |
| unsigned long num_arg(const char *buffer, unsigned long maxlen, |
| unsigned long *num) |
| { |
| int i=0; |
| *num = 0; |
| |
| for(; i<maxlen;i++) { |
| if( (buffer[i] >= '0') && (buffer[i] <= '9')) { |
| *num *= 10; |
| *num += buffer[i] -'0'; |
| } |
| else |
| break; |
| } |
| return i; |
| } |
| |
| int strn_len(const char *buffer, unsigned int maxlen) |
| { |
| int i=0; |
| |
| for(; i<maxlen;i++) |
| switch(buffer[i]) { |
| case '\"': |
| case '\n': |
| case '\r': |
| case '\t': |
| case ' ': |
| goto done_str; |
| default: |
| } |
| done_str: |
| return i; |
| } |
| |
| int proc_pg_write(struct file *file, const char *buffer, |
| unsigned long count, void *data) |
| { |
| int i=0, max, len; |
| char name[16], valstr[32]; |
| unsigned long value = 0; |
| |
| if (count < 1) { |
| sprintf(pg_result, "Wrong command format"); |
| return -EINVAL; |
| } |
| |
| max = count -i; |
| i += count_trail_chars(&buffer[i], max); |
| |
| /* Read variable name */ |
| |
| len = strn_len(&buffer[i], sizeof(name)-1); |
| memset(name, 0, sizeof(name)); |
| strncpy(name, &buffer[i], len); |
| i += len; |
| |
| max = count -i; |
| len = count_trail_chars(&buffer[i], max); |
| i += len; |
| |
| if (debug) |
| printk("pg: %s,%lu\n", name, count); |
| |
| /* Only stop is allowed when we are running */ |
| |
| if(!strcmp(name, "stop")) { |
| forced_stop=1; |
| if (pg_busy) |
| strcpy(pg_result, "Stopping"); |
| return count; |
| } |
| |
| if (pg_busy) { |
| strcpy(pg_result, "Busy"); |
| return -EINVAL; |
| } |
| |
| if(!strcmp(name, "pkt_size")) { |
| len = num_arg(&buffer[i], 10, &value); |
| i += len; |
| if (value < 14+20+8) |
| value = 14+20+8; |
| pkt_size = value; |
| sprintf(pg_result, "OK: pkt_size=%u", pkt_size); |
| return count; |
| } |
| if(!strcmp(name, "frags")) { |
| len = num_arg(&buffer[i], 10, &value); |
| i += len; |
| nfrags = value; |
| sprintf(pg_result, "OK: frags=%u", nfrags); |
| return count; |
| } |
| if(!strcmp(name, "ipg")) { |
| len = num_arg(&buffer[i], 10, &value); |
| i += len; |
| pg_ipg = value; |
| sprintf(pg_result, "OK: ipg=%u", pg_ipg); |
| return count; |
| } |
| if(!strcmp(name, "count")) { |
| len = num_arg(&buffer[i], 10, &value); |
| i += len; |
| pg_count = value; |
| sprintf(pg_result, "OK: count=%u", pg_count); |
| return count; |
| } |
| if(!strcmp(name, "odev")) { |
| len = strn_len(&buffer[i], sizeof(pg_outdev)-1); |
| memset(pg_outdev, 0, sizeof(pg_outdev)); |
| strncpy(pg_outdev, &buffer[i], len); |
| i += len; |
| sprintf(pg_result, "OK: odev=%s", pg_outdev); |
| return count; |
| } |
| if(!strcmp(name, "dst")) { |
| len = strn_len(&buffer[i], sizeof(pg_dst)-1); |
| memset(pg_dst, 0, sizeof(pg_dst)); |
| strncpy(pg_dst, &buffer[i], len); |
| if(debug) |
| printk("pg: dst set to: %s\n", pg_dst); |
| i += len; |
| sprintf(pg_result, "OK: dst=%s", pg_dst); |
| return count; |
| } |
| if(!strcmp(name, "dstmac")) { |
| char *v = valstr; |
| unsigned char *m = pg_dstmac; |
| |
| len = strn_len(&buffer[i], sizeof(valstr)-1); |
| memset(valstr, 0, sizeof(valstr)); |
| strncpy(valstr, &buffer[i], len); |
| i += len; |
| |
| for(*m = 0;*v && m < pg_dstmac+6;v++) { |
| if(*v >= '0' && *v <= '9') { |
| *m *= 16; |
| *m += *v - '0'; |
| } |
| if(*v >= 'A' && *v <= 'F') { |
| *m *= 16; |
| *m += *v - 'A' + 10; |
| } |
| if(*v >= 'a' && *v <= 'f') { |
| *m *= 16; |
| *m += *v - 'a' + 10; |
| } |
| if(*v == ':') { |
| m++; |
| *m = 0; |
| } |
| } |
| sprintf(pg_result, "OK: dstmac"); |
| return count; |
| } |
| |
| if (!strcmp(name, "inject") || !strcmp(name, "start") ) { |
| MOD_INC_USE_COUNT; |
| pg_busy = 1; |
| strcpy(pg_result, "Starting"); |
| pg_inject(); |
| pg_busy = 0; |
| MOD_DEC_USE_COUNT; |
| return count; |
| } |
| |
| sprintf(pg_result, "No such parameter \"%s\"", name); |
| return -EINVAL; |
| } |
| |
| static int pg_init(void) |
| { |
| printk(version); |
| cycles_calibrate(); |
| if (pg_cpu_speed == 0) { |
| printk("pg3: Error: your machine does not have working cycle counter.\n"); |
| return -EINVAL; |
| } |
| if(!pg_proc_ent) { |
| pg_proc_ent = create_proc_entry("net/pg", 0600, 0); |
| if (pg_proc_ent) { |
| pg_proc_ent->read_proc = proc_pg_read; |
| pg_proc_ent->write_proc = proc_pg_write; |
| pg_proc_ent->data = 0; |
| } |
| pg_busy_proc_ent = create_proc_entry("net/pg_busy", 0, 0); |
| if (pg_busy_proc_ent) { |
| pg_busy_proc_ent->read_proc = proc_pg_busy_read; |
| pg_busy_proc_ent->data = 0; |
| } |
| } |
| return 0; |
| } |
| |
| void pg_cleanup(void) |
| { |
| if (pg_proc_ent) { |
| remove_proc_entry("net/pg", NULL); |
| pg_proc_ent = 0; |
| remove_proc_entry("net/pg_busy", NULL); |
| pg_busy_proc_ent = 0; |
| } |
| } |
| |
| module_init(pg_init); |
| module_exit(pg_cleanup); |
| |
| |
| #if LINUX_VERSION_CODE > 0x20118 |
| MODULE_AUTHOR("Robert Olsson <robert.olsson@its.uu.se"); |
| MODULE_DESCRIPTION("Packet Generator tool"); |
| MODULE_PARM(pg_count, "i"); |
| MODULE_PARM(pg_ipg, "i"); |
| MODULE_PARM(pg_cpu_speed, "i"); |
| #endif |
| |
| /* |
| * Local variables: |
| * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2 -c pg3.c" |
| * End: |
| */ |