From: Ben Greear <greearb@candelatech.com>
To: linux-kernel <linux-kernel@vger.kernel.org>,
"David S. Miller" <davem@redhat.com>
Subject: PATCH: pktgen.c update
Date: Sat, 20 Apr 2002 17:42:21 -0700 [thread overview]
Message-ID: <3CC20AED.4000007@candelatech.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 582 bytes --]
I got all excited about the pktgen.c module, and went in and
changed it significantly to suite my needs better. I would
be happy to see these changes integrated into the mainstream kernel,
but even if that is not appropriate, I will appreciate any
feedback, especially about how to make it sleep more efficiently
when there is an inter-packet-gap set.
Thanks,
Ben
--
Ben Greear <greearb@candelatech.com> <Ben_Greear AT excite.com>
President of Candela Technologies Inc http://www.candelatech.com
ScryMUD: http://scry.wanfear.com http://scry.wanfear.com/~greear
[-- Attachment #2: patch2.txt --]
[-- Type: text/plain, Size: 39588 bytes --]
--- linux/net/core/pktgen.c Sat Apr 20 17:28:52 2002
+++ linux.dev/net/core/pktgen.c Sat Apr 20 17:27:55 2002
@@ -1,4 +1,5 @@
-/* $Id: pktgen.c,v 1.1.2.1 2002/03/01 12:15:05 davem Exp $
+/* -*-linux-c-*-
+ * $Id: pktgen.c,v 1.1.2.1 2002/03/01 12:15:05 davem Exp $
* pktgen.c: Packet Generator for performance evaluation.
*
* Copyright 2001, 2002 by Robert Olsson <robert.olsson@its.uu.se>
@@ -18,6 +19,20 @@
* MAC address typo fixed. 010417 --ro
* Integrated. 020301 --DaveM
* Added multiskb option 020301 --DaveM
+ * Significant re-work of the module:
+ * * Updated to support generation over multiple interfaces at once
+ * by creating 32 /proc/net/pg* files. Each file can be manipulated
+ * individually.
+ * * Converted many counters to __u64 to allow longer runs.
+ * * Allow configuration of ranges, like min/max IP address, MACs,
+ * and UDP-ports, for both source and destination, and can
+ * set to use a random distribution or sequentially walk the range.
+ * * Can now change some values after starting.
+ * * Place 12-byte packet in UDP payload with magic number,
+ * sequence number, and timestamp. Will write receiver next.
+ * * The new changes seem to have a performance inpact of around 1%,
+ * as far as I can tell.
+ * --Ben Greear <greearb@candelatech.com>
*
* See Documentation/networking/pktgen.txt for how to use this.
*/
@@ -56,69 +71,189 @@
#define cycles() ((u32)get_cycles())
static char version[] __initdata =
- "pktgen.c: v1.0 010812: Packet Generator for packet performance testing.\n";
+ "pktgen.c: v1.1: Packet Generator for packet performance testing.\n";
-/* Parameters */
-static char pg_outdev[32], pg_dst[32];
-static int pkt_size = ETH_ZLEN;
-static int nfrags = 0;
-static __u32 pg_count = 100000; /* Default No packets to send */
-static __u32 pg_ipg = 0; /* Default Interpacket gap in nsec */
-static int pg_multiskb = 0; /* Use multiple SKBs during packet gen. */
+/* Used to help with determining the pkts on receive */
+#define PKTGEN_MAGIC 0xbe9be955
-static int debug;
-static int forced_stop;
-static int pg_cpu_speed;
-static int pg_busy;
+/* Keep information per interface */
+struct pktgen_info {
+ /* Parameters */
+
+ /* If min != max, then we will either do a linear iteration, or
+ * we will do a random selection from within the range.
+ */
+ __u32 flags; /* 1<<0 IP-Src Random, 1<<1 IP-Dst Random,
+ * 1<<2 UDP-Src Random, 1<<3 UDP-Dst Random
+ * 1<<4 MAC-Src Random, 1<<5 MAC-Dst Random
+ * 1<<6 Specify-Src-Mac (default is to use Interface's MAC Addr)
+ * 1<<7 Specify-Src-IP (default is to use Interface's IP Addr)
+ */
+
+ int pkt_size; /* = ETH_ZLEN; */
+ int nfrags;
+ __u32 pg_ipg; /* Default Interpacket gap in nsec */
+ __u64 pg_count; /* Default No packets to send */
+ __u64 pg_sofar; /* How many pkts we've sent so far */
+ __u64 errors; /* Errors when trying to transmit, pkts will be re-sent */
+ struct timeval started_at;
+ struct timeval stopped_at;
+ __u64 idle_acc;
+ __u32 seq_num;
+
+ int pg_multiskb; /* Use multiple SKBs during packet gen. */
+ int forced_stop;
+ int pg_busy;
+ int do_run_run; /* if this changes to false, the test will stop */
+
+ char pg_outdev[32];
+ char dst_min[32];
+ char dst_max[32];
+ char src_min[32];
+ char src_max[32];
+
+ /* If we're doing ranges, random or incremental, then this
+ * defines the min/max for those ranges.
+ */
+ __u32 saddr_min; /* inclusive, source IP address */
+ __u32 saddr_max; /* exclusive, source IP address */
+ __u32 daddr_min; /* inclusive, dest IP address */
+ __u32 daddr_max; /* exclusive, dest IP address */
+
+ __u16 udp_src_min; /* inclusive, source UDP port */
+ __u16 udp_src_max; /* exclusive, source UDP port */
+ __u16 udp_dst_min; /* inclusive, dest UDP port */
+ __u16 udp_dst_max; /* exclusive, dest UDP port */
+
+ __u32 src_mac_count; /* How many MACs to iterate through */
+ __u32 dst_mac_count; /* How many MACs to iterate through */
+
+ unsigned char dst_mac[6];
+ unsigned char src_mac[6];
+
+ __u32 cur_dst_mac_offset;
+ __u32 cur_src_mac_offset;
+ __u32 cur_saddr;
+ __u32 cur_daddr;
+ __u16 cur_udp_dst;
+ __u16 cur_udp_src;
+
+ __u8 hh[14];
+ /* = {
+ 0x00, 0x80, 0xC8, 0x79, 0xB3, 0xCB,
+
+ We fill in SRC address later
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00
+ };
+ */
+ __u16 pad; /* pad out the hh struct to an even 16 bytes */
+ char pg_result[512];
+
+ /* proc file names */
+ char fname[80];
+ char busy_fname[80];
-static __u8 hh[14] = {
- 0x00, 0x80, 0xC8, 0x79, 0xB3, 0xCB,
+ struct proc_dir_entry *pg_proc_ent;
+ struct proc_dir_entry *pg_busy_proc_ent;
+};
- /* We fill in SRC address later */
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00
+struct pktgen_hdr {
+ __u32 pgh_magic;
+ __u32 seq_num;
+ struct timeval timestamp;
};
-static unsigned char *pg_dstmac = hh;
-static char pg_result[512];
+static int pg_cpu_speed;
+static int debug;
+
+/* Module parameters, defaults. */
+static int pg_count_d = 100000;
+static int pg_ipg_d = 0;
+static int pg_multiskb_d = 0;
+
+
+#define MAX_PKTGEN 32
+static struct pktgen_info pginfos[MAX_PKTGEN];
+
+
+/** Convert to miliseconds */
+inline __u64 tv_to_ms(const struct timeval* tv) {
+ __u64 ms = tv->tv_usec / 1000;
+ ms += (__u64)tv->tv_sec * (__u64)1000;
+ return ms;
+}
+
+inline __u64 getCurMs(void) {
+ struct timeval tv;
+ do_gettimeofday(&tv);
+ return tv_to_ms(&tv);
+}
+
-static struct net_device *pg_setup_inject(u32 *saddrp)
+static struct net_device *pg_setup_inject(struct pktgen_info* info)
{
struct net_device *odev;
- int p1, p2;
- u32 saddr;
rtnl_lock();
- odev = __dev_get_by_name(pg_outdev);
+ odev = __dev_get_by_name(info->pg_outdev);
if (!odev) {
- sprintf(pg_result, "No such netdevice: \"%s\"", pg_outdev);
+ sprintf(info->pg_result, "No such netdevice: \"%s\"", info->pg_outdev);
goto out_unlock;
}
if (odev->type != ARPHRD_ETHER) {
- sprintf(pg_result, "Not ethernet device: \"%s\"", pg_outdev);
+ sprintf(info->pg_result, "Not ethernet device: \"%s\"", info->pg_outdev);
goto out_unlock;
}
if (!netif_running(odev)) {
- sprintf(pg_result, "Device is down: \"%s\"", pg_outdev);
+ sprintf(info->pg_result, "Device is down: \"%s\"", info->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;
- }
+ /* Default to the interface's mac if not explicitly set. */
+ if (!(info->flags & (1<<6))) {
+ memcpy(&(info->hh[6]), odev->dev_addr, 6);
+ }
+ else {
+ memcpy(&(info->hh[6]), info->src_mac, 6);
+ }
+
+ /* Set up Dest MAC */
+ memcpy(&(info->hh[0]), info->dst_mac, 6);
+
+ info->saddr_min = 0;
+ info->saddr_max = 0;
+ if (strlen(info->src_min) == 0) {
+ if (odev->ip_ptr) {
+ struct in_device *in_dev = odev->ip_ptr;
+
+ if (in_dev->ifa_list) {
+ info->saddr_min = in_dev->ifa_list->ifa_address;
+ info->saddr_max = info->saddr_min;
+ }
+ }
+ }
+ else {
+ info->saddr_min = in_aton(info->src_min);
+ info->saddr_max = in_aton(info->src_max);
+ }
+
+ info->daddr_min = in_aton(info->dst_min);
+ info->daddr_max = in_aton(info->dst_max);
+
+ /* Initialize current values. */
+ info->cur_dst_mac_offset = 0;
+ info->cur_src_mac_offset = 0;
+ info->cur_saddr = info->saddr_min;
+ info->cur_daddr = info->daddr_min;
+ info->cur_udp_dst = info->udp_dst_min;
+ info->cur_udp_src = info->udp_src_min;
+
atomic_inc(&odev->refcnt);
rtnl_unlock();
- *saddrp = saddr;
return odev;
out_unlock:
@@ -126,9 +261,7 @@
return NULL;
}
-static u32 idle_acc_lo, idle_acc_hi;
-
-static void nanospin(int pg_ipg)
+static void nanospin(int pg_ipg, struct pktgen_info* info)
{
u32 idle_start, idle;
@@ -140,9 +273,7 @@
if (idle * 1000 >= pg_ipg * pg_cpu_speed)
break;
}
- idle_acc_lo += idle;
- if (idle_acc_lo < idle)
- idle_acc_hi++;
+ info->idle_acc += idle;
}
static int calc_mhz(void)
@@ -173,17 +304,136 @@
}
}
-static struct sk_buff *fill_packet(struct net_device *odev, __u32 saddr)
+
+/* Increment/randomize headers according to flags and current values
+ * for IP src/dest, UDP src/dst port, MAC-Addr src/dst
+ */
+static void mod_cur_headers(struct pktgen_info* info) {
+ __u32 imn;
+ __u32 imx;
+
+ /* Deal with source MAC */
+ if (info->src_mac_count > 1) {
+ __u32 mc;
+ __u32 tmp;
+ if (info->flags & (1<<4)) {
+ mc = net_random() % (info->src_mac_count);
+ }
+ else {
+ mc = info->cur_src_mac_offset++;
+ if (info->cur_src_mac_offset > info->src_mac_count) {
+ info->cur_src_mac_offset = 0;
+ }
+ }
+
+ tmp = info->src_mac[5] + (mc & 0xFF);
+ info->hh[11] = tmp;
+ tmp = (info->src_mac[4] + ((mc >> 8) & 0xFF) + (tmp >> 8));
+ info->hh[10] = tmp;
+ tmp = (info->src_mac[3] + ((mc >> 16) & 0xFF) + (tmp >> 8));
+ info->hh[9] = tmp;
+ tmp = (info->src_mac[2] + ((mc >> 24) & 0xFF) + (tmp >> 8));
+ info->hh[8] = tmp;
+ tmp = (info->src_mac[1] + (tmp >> 8));
+ info->hh[7] = tmp;
+ }
+
+ /* Deal with Destination MAC */
+ if (info->dst_mac_count > 1) {
+ __u32 mc;
+ __u32 tmp;
+ if (info->flags & (1<<5)) {
+ mc = net_random() % (info->dst_mac_count);
+ }
+ else {
+ mc = info->cur_dst_mac_offset++;
+ if (info->cur_dst_mac_offset > info->dst_mac_count) {
+ info->cur_dst_mac_offset = 0;
+ }
+ }
+
+ tmp = info->dst_mac[5] + (mc & 0xFF);
+ info->hh[5] = tmp;
+ tmp = (info->dst_mac[4] + ((mc >> 8) & 0xFF) + (tmp >> 8));
+ info->hh[4] = tmp;
+ tmp = (info->dst_mac[3] + ((mc >> 16) & 0xFF) + (tmp >> 8));
+ info->hh[3] = tmp;
+ tmp = (info->dst_mac[2] + ((mc >> 24) & 0xFF) + (tmp >> 8));
+ info->hh[2] = tmp;
+ tmp = (info->dst_mac[1] + (tmp >> 8));
+ info->hh[1] = tmp;
+ }
+
+ if (info->udp_src_min < info->udp_src_max) {
+ if (info->flags & (1<<2)) {
+ info->cur_udp_src = ((net_random() % (info->udp_src_max - info->udp_src_min))
+ + info->udp_src_min);
+ }
+ else {
+ info->cur_udp_src++;
+ if (info->cur_udp_src >= info->udp_src_max) {
+ info->cur_udp_src = info->udp_src_min;
+ }
+ }
+ }
+
+ if (info->udp_dst_min < info->udp_dst_max) {
+ if (info->flags & (1<<3)) {
+ info->cur_udp_dst = ((net_random() % (info->udp_dst_max - info->udp_dst_min))
+ + info->udp_dst_min);
+ }
+ else {
+ info->cur_udp_dst++;
+ if (info->cur_udp_dst >= info->udp_dst_max) {
+ info->cur_udp_dst = info->udp_dst_min;
+ }
+ }
+ }
+
+ if ((imn = ntohl(info->saddr_min)) < (imx = ntohl(info->saddr_max))) {
+ __u32 t;
+ if (info->flags & (1<<0)) {
+ t = ((net_random() % (imx - imn)) + imn);
+ }
+ else {
+ t = ntohl(info->cur_saddr);
+ t++;
+ if (t >= imx) {
+ t = imn;
+ }
+ }
+ info->cur_saddr = htonl(t);
+ }
+
+ if ((imn = ntohl(info->daddr_min)) < (imx = ntohl(info->daddr_max))) {
+ __u32 t;
+ if (info->flags & (1<<1)) {
+ t = ((net_random() % (imx - imn)) + imn);
+ }
+ else {
+ t = ntohl(info->cur_daddr);
+ t++;
+ if (t >= imx) {
+ t = imn;
+ }
+ }
+ info->cur_daddr = htonl(t);
+ }
+}/* mod_cur_headers */
+
+
+static struct sk_buff *fill_packet(struct net_device *odev, struct pktgen_info* info)
{
- struct sk_buff *skb;
+ struct sk_buff *skb = NULL;
__u8 *eth;
struct udphdr *udph;
int datalen, iplen;
struct iphdr *iph;
-
- skb = alloc_skb(pkt_size + 64 + 16, GFP_ATOMIC);
+ struct pktgen_hdr *pgh = NULL;
+
+ skb = alloc_skb(info->pkt_size + 64 + 16, GFP_ATOMIC);
if (!skb) {
- sprintf(pg_result, "No memory");
+ sprintf(info->pg_result, "No memory");
return NULL;
}
@@ -194,15 +444,20 @@
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);
+ /* Update any of the values, used when we're incrementing various
+ * fields.
+ */
+ mod_cur_headers(info);
+
+ memcpy(eth, info->hh, 14);
+
+ datalen = info->pkt_size - 14 - 20 - 8; /* Eth + IPh + UDPh */
+ if (datalen < sizeof(struct pktgen_hdr)) {
+ datalen = sizeof(struct pktgen_hdr);
+ }
+
+ udph->source = htons(info->cur_udp_src);
+ udph->dest = htons(info->cur_udp_dst);
udph->len = htons(datalen + 8); /* DATA + udphdr */
udph->check = 0; /* No checksum */
@@ -211,8 +466,8 @@
iph->ttl = 3;
iph->tos = 0;
iph->protocol = IPPROTO_UDP; /* UDP */
- iph->saddr = saddr;
- iph->daddr = in_aton(pg_dst);
+ iph->saddr = info->cur_saddr;
+ iph->daddr = info->cur_daddr;
iph->frag_off = 0;
iplen = 20 + 8 + datalen;
iph->tot_len = htons(iplen);
@@ -223,12 +478,15 @@
skb->dev = odev;
skb->pkt_type = PACKET_HOST;
- if (nfrags <= 0) {
- skb_put(skb, datalen);
+ if (info->nfrags <= 0) {
+ pgh = (struct pktgen_hdr *)skb_put(skb, datalen);
} else {
- int frags = nfrags;
+ int frags = info->nfrags;
int i;
+ /* TODO: Verify this is OK...it sure is ugly. --Ben */
+ pgh = (struct pktgen_hdr*)(((char*)(udph)) + 8);
+
if (frags > MAX_SKB_FRAGS)
frags = MAX_SKB_FRAGS;
if (datalen > frags*PAGE_SIZE) {
@@ -272,63 +530,95 @@
}
}
+ /* Stamp the time, and sequence number, convert them to network byte order */
+ if (pgh) {
+ pgh->pgh_magic = __constant_htonl(PKTGEN_MAGIC);
+ do_gettimeofday(&(pgh->timestamp));
+ pgh->timestamp.tv_usec = htonl(pgh->timestamp.tv_usec);
+ pgh->timestamp.tv_sec = htonl(pgh->timestamp.tv_sec);
+ pgh->seq_num = htonl(info->seq_num);
+ }
+
return skb;
}
-static void pg_inject(void)
+static void pg_inject(struct pktgen_info* info)
{
- 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);
+ struct sk_buff *skb = NULL;
+ __u64 total = 0;
+ __u64 idle = 0;
+ __u64 lcount = 0;
+ int nr_frags = 0;
+
+ odev = pg_setup_inject(info);
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(;;) {
+ info->do_run_run = 1; /* Cranke yeself! */
+ info->forced_stop = 0;
+ info->idle_acc = 0;
+ info->pg_sofar = 0;
+ lcount = info->pg_count;
+ do_gettimeofday(&(info->started_at));
+
+ while(info->do_run_run) {
+ /* Want to set a time-stamp, so build a new pkt each time */
+ if (skb) {
+ kfree_skb(skb);
+ }
+ skb = fill_packet(odev, info);
+ if (skb == NULL)
+ goto out_reldev;
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
spin_lock_bh(&odev->xmit_lock);
if (!netif_queue_stopped(odev)) {
struct sk_buff *skb2 = skb;
- if (pg_multiskb)
+ if (info->pg_multiskb)
skb2 = skb_copy(skb, GFP_ATOMIC);
else
atomic_inc(&skb->users);
if (!skb2)
goto skip;
if (odev->hard_start_xmit(skb2, odev)) {
- kfree_skb(skb2);
- if (net_ratelimit())
- printk(KERN_INFO "Hard xmit error\n");
+ if (net_ratelimit()) {
+ printk(KERN_INFO "Hard xmit error\n");
+ }
+ info->errors++;
}
- pc++;
+ else {
+ info->pg_sofar++;
+ info->seq_num++;
+ }
}
skip:
spin_unlock_bh(&odev->xmit_lock);
- if (pg_ipg)
- nanospin(pg_ipg);
- if (forced_stop)
+ if (info->pg_ipg) {
+ /* Try not to busy-spin if we have larger sleep times.
+ * TODO: Investigate better ways to do this.
+ */
+ if (info->pg_ipg < 10000) { /* 10 usecs or less */
+ nanospin(info->pg_ipg, info);
+ }
+ else if (info->pg_ipg < 10000000) { /* 10ms or less */
+ udelay(info->pg_ipg / 1000);
+ }
+ else {
+ mdelay(info->pg_ipg / 1000000);
+ }
+ }
+
+ if (info->forced_stop)
goto out_intr;
if (signal_pending(current))
- goto out_intr;
+ break;
- if (--lcount == 0) {
+ /* If lcount is zero, then run forever */
+ if ((lcount != 0) && (--lcount == 0)) {
if (atomic_read(&skb->users) != 1) {
u32 idle_start, idle;
@@ -339,9 +629,7 @@
schedule();
}
idle = cycles() - idle_start;
- idle_acc_lo += idle;
- if (idle_acc_lo < idle)
- idle_acc_hi++;
+ info->idle_acc += idle;
}
break;
}
@@ -361,53 +649,63 @@
do_softirq();
} while (netif_queue_stopped(odev));
idle = cycles() - idle_start;
- idle_acc_lo += idle;
- if (idle_acc_lo < idle)
- idle_acc_hi++;
+ info->idle_acc += idle;
}
- }
+ }/* while we should be running */
- do_gettimeofday(&stop);
+ do_gettimeofday(&(info->stopped_at));
- total = (stop.tv_sec - start.tv_sec) * 1000000 +
- stop.tv_usec - start.tv_usec;
+ total = (info->stopped_at.tv_sec - info->started_at.tv_sec) * 1000000 +
+ info->stopped_at.tv_usec - info->started_at.tv_usec;
- idle = (((idle_acc_hi<<20)/pg_cpu_speed)<<12)+idle_acc_lo/pg_cpu_speed;
+ idle = (__u32)(info->idle_acc)/(__u32)(pg_cpu_speed);
{
- char *p = pg_result;
-
- p += sprintf(p, "OK: %u(c%u+d%u) usec, %u (%dbyte,%dfrags) %upps %uMB/sec",
+ char *p = info->pg_result;
+ __u64 pps = (__u32)(info->pg_sofar * 1000) / ((__u32)(total) / 1000);
+ __u64 bps = pps * 8 * (info->pkt_size + 4); /* take 32bit ethernet CRC into account */
+ p += sprintf(p, "OK: %llu(c%llu+d%llu) usec, %llu (%dbyte,%dfrags) %llupps %lluMb/sec (%llubps) errors: %llu",
total, total-idle, idle,
- pc, skb->len, skb_shinfo(skb)->nr_frags,
- ((pc*1000)/(total/1000)),
- (((pc*1000)/(total/1000))*pkt_size)/1024/1024
+ info->pg_sofar,
+ skb->len + 4, /* Add 4 to account for the ethernet checksum */
+ nr_frags,
+ pps,
+ bps/1024/1024, bps, info->errors
);
}
-
+
out_relskb:
- kfree_skb(skb);
+ if (skb) {
+ kfree_skb(skb);
+ skb = NULL;
+ }
+
out_reldev:
dev_put(odev);
return;
out_intr:
- sprintf(pg_result, "Interrupted");
+ sprintf(info->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;
-
static int proc_pg_busy_read(char *buf , char **start, off_t offset,
int len, int *eof, void *data)
{
char *p;
+ int idx = (int)(data);
+ struct pktgen_info* info = NULL;
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_pg_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
p = buf;
- p += sprintf(p, "%d\n", pg_busy);
+ p += sprintf(p, "%d\n", info->pg_busy);
*eof = 1;
return p-buf;
@@ -418,16 +716,72 @@
{
char *p;
int i;
+ int idx = (int)(data);
+ struct pktgen_info* info = NULL;
+ __u64 sa;
+ __u64 stopped;
+ __u64 now = getCurMs();
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_pg_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
p = buf;
- p += sprintf(p, "Params: count=%u pkt_size=%u frags %d ipg %u multiskb %d odev \"%s\" dst %s dstmac ",
- pg_count, pkt_size, nfrags, pg_ipg, pg_multiskb,
- 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);
+ p += sprintf(p, "VERSION-1\n"); /* Help with parsing compatibility */
+ p += sprintf(p, "Params: count %llu pkt_size: %u frags: %d ipg: %u multiskb: %d odev \"%s\"\n",
+ info->pg_count, info->pkt_size, info->nfrags, info->pg_ipg,
+ info->pg_multiskb, info->pg_outdev);
+ p += sprintf(p, " dst_min: %s dst_max: %s src_min: %s src_max: %s\n",
+ info->dst_min, info->dst_max, info->src_min, info->src_max);
+ p += sprintf(p, " src_mac: ");
+ for (i = 0; i < 6; i++) {
+ p += sprintf(p, "%02X%s", info->src_mac[i], i == 5 ? " " : ":");
+ }
+ p += sprintf(p, "dst_mac: ");
+ for (i = 0; i < 6; i++) {
+ p += sprintf(p, "%02X%s", info->dst_mac[i], i == 5 ? "\n" : ":");
+ }
+ p += sprintf(p, " udp_src_min: %d udp_src_max: %d udp_dst_min: %d udp_dst_max: %d\n",
+ info->udp_src_min, info->udp_src_max, info->udp_dst_min,
+ info->udp_dst_max);
+ p += sprintf(p, " src_mac_count: %d dst_mac_count: %d\n Flags: ",
+ info->src_mac_count, info->dst_mac_count);
+ if (info->flags & (1<<0)) {
+ p += sprintf(p, "IPSRC_RND ");
+ }
+ if (info->flags & (1<<1)) {
+ p += sprintf(p, "IPDST_RND ");
+ }
+ if (info->flags & (1<<2)) {
+ p += sprintf(p, "UDPSRC_RND ");
+ }
+ if (info->flags & (1<<3)) {
+ p += sprintf(p, "UDPDST_RND ");
+ }
+ if (info->flags & (1<<4)) {
+ p += sprintf(p, "MACSRC_RND ");
+ }
+ if (info->flags & (1<<5)) {
+ p += sprintf(p, "MACDST_RND ");
+ }
+ p += sprintf(p, "\n");
+
+ sa = tv_to_ms(&(info->started_at));
+ stopped = tv_to_ms(&(info->stopped_at));
+ if (info->do_run_run) {
+ stopped = now; /* not really stopped, more like last-running-at */
+ }
+ p += sprintf(p, "Current:\n pkts-sofar: %llu errors: %llu\n started: %llums stopped: %llums now: %llums idle: %lluns\n",
+ info->pg_sofar, info->errors, sa, stopped, now, info->idle_acc);
+ p += sprintf(p, " seq_num: %d cur_dst_mac_offset: %d cur_src_mac_offset: %d\n",
+ info->seq_num, info->cur_dst_mac_offset, info->cur_src_mac_offset);
+ p += sprintf(p, " cur_saddr: 0x%x cur_daddr: 0x%x cur_udp_dst: %d cur_udp_src: %d\n",
+ info->cur_saddr, info->cur_daddr, info->cur_udp_dst, info->cur_udp_src);
+
+ if (info->pg_result[0])
+ p += sprintf(p, "Result: %s\n", info->pg_result);
else
p += sprintf(p, "Result: Idle\n");
*eof = 1;
@@ -498,7 +852,17 @@
int i = 0, max, len;
char name[16], valstr[32];
unsigned long value = 0;
-
+ int idx = (int)(data);
+ struct pktgen_info* info = NULL;
+ char* pg_result = NULL;
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_pg_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
+ pg_result = &(info->pg_result[0]);
+
if (count < 1) {
sprintf(pg_result, "Wrong command format");
return -EINVAL;
@@ -521,85 +885,206 @@
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)
+ info->forced_stop = 1;
+ if (info->do_run_run) {
strcpy(pg_result, "Stopping");
+ }
+ else {
+ strcpy(pg_result, "Already stopped...\n");
+ }
+ info->do_run_run = 0;
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);
+ info->pkt_size = value;
+ sprintf(pg_result, "OK: pkt_size=%u", info->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);
+ info->nfrags = value;
+ sprintf(pg_result, "OK: frags=%u", info->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);
+ info->pg_ipg = value;
+ sprintf(pg_result, "OK: ipg=%u", info->pg_ipg);
+ return count;
+ }
+ if (!strcmp(name, "udp_src_min")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->udp_src_min = value;
+ sprintf(pg_result, "OK: udp_src_min=%u", info->udp_src_min);
+ return count;
+ }
+ if (!strcmp(name, "udp_dst_min")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->udp_dst_min = value;
+ sprintf(pg_result, "OK: udp_dst_min=%u", info->udp_dst_min);
+ return count;
+ }
+ if (!strcmp(name, "udp_src_max")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->udp_src_max = value;
+ sprintf(pg_result, "OK: udp_src_max=%u", info->udp_src_max);
+ return count;
+ }
+ if (!strcmp(name, "udp_dst_max")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->udp_dst_max = value;
+ sprintf(pg_result, "OK: udp_dst_max=%u", info->udp_dst_max);
return count;
}
if (!strcmp(name, "multiskb")) {
len = num_arg(&buffer[i], 10, &value);
i += len;
- pg_multiskb = (value ? 1 : 0);
- sprintf(pg_result, "OK: multiskb=%d", pg_multiskb);
+ info->pg_multiskb = (value ? 1 : 0);
+ sprintf(pg_result, "OK: multiskb=%d", info->pg_multiskb);
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);
+ info->pg_count = value;
+ sprintf(pg_result, "OK: count=%llu", info->pg_count);
+ return count;
+ }
+ if (!strcmp(name, "src_mac_count")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->src_mac_count = value;
+ sprintf(pg_result, "OK: src_mac_count=%d", info->src_mac_count);
+ return count;
+ }
+ if (!strcmp(name, "dst_mac_count")) {
+ len = num_arg(&buffer[i], 10, &value);
+ i += len;
+ info->dst_mac_count = value;
+ sprintf(pg_result, "OK: dst_mac_count=%d", info->dst_mac_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);
+ len = strn_len(&buffer[i], sizeof(info->pg_outdev) - 1);
+ memset(info->pg_outdev, 0, sizeof(info->pg_outdev));
+ strncpy(info->pg_outdev, &buffer[i], len);
+ i += len;
+ sprintf(pg_result, "OK: odev=%s", info->pg_outdev);
+ return count;
+ }
+ if (!strcmp(name, "flag")) {
+ char f[32];
+ memset(f, 0, 32);
+ len = strn_len(&buffer[i], sizeof(f) - 1);
+ strncpy(f, &buffer[i], len);
i += len;
- sprintf(pg_result, "OK: odev=%s", pg_outdev);
+ if (strcmp(f, "IPSRC_RND") == 0) {
+ info->flags |= (1<<0);
+ }
+ else if (strcmp(f, "!IPSRC_RND") == 0) {
+ info->flags &= ~(1<<0);
+ }
+ else if (strcmp(f, "IPDST_RND") == 0) {
+ info->flags |= (1<<1);
+ }
+ else if (strcmp(f, "!IPDST_RND") == 0) {
+ info->flags &= ~(1<<1);
+ }
+ else if (strcmp(f, "UDPSRC_RND") == 0) {
+ info->flags |= (1<<2);
+ }
+ else if (strcmp(f, "!UDPSRC_RND") == 0) {
+ info->flags &= ~(1<<2);
+ }
+ else if (strcmp(f, "UDPDST_RND") == 0) {
+ info->flags |= (1<<3);
+ }
+ else if (strcmp(f, "!UDPDST_RND") == 0) {
+ info->flags &= ~(1<<3);
+ }
+ else if (strcmp(f, "MACSRC_RND") == 0) {
+ info->flags |= (1<<4);
+ }
+ else if (strcmp(f, "!MACSRC_RND") == 0) {
+ info->flags &= ~(1<<4);
+ }
+ else if (strcmp(f, "MACDST_RND") == 0) {
+ info->flags |= (1<<5);
+ }
+ else if (strcmp(f, "!MACDST_RND") == 0) {
+ info->flags &= ~(1<<5);
+ }
+ else {
+ sprintf(pg_result, "Flag -:%s:- unknown\nAvailable flags, (prepend ! to un-set flag):\n%s",
+ f,
+ "IPSRC_RND, IPDST_RND, UDPSRC_RND, UDPDST_RND, MACSRC_RND, MACDST_RND\n");
+ return count;
+ }
+ sprintf(pg_result, "OK: flags=0x%x", info->flags);
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 (!strcmp(name, "dst_min") || !strcmp(name, "dst")) {
+ len = strn_len(&buffer[i], sizeof(info->dst_min) - 1);
+ memset(info->dst_min, 0, sizeof(info->dst_min));
+ strncpy(info->dst_min, &buffer[i], len);
if(debug)
- printk("pg: dst set to: %s\n", pg_dst);
+ printk("pg: dst_min set to: %s\n", info->dst_min);
i += len;
- sprintf(pg_result, "OK: dst=%s", pg_dst);
+ sprintf(pg_result, "OK: dst_min=%s", info->dst_min);
+ return count;
+ }
+ if (!strcmp(name, "dst_max")) {
+ len = strn_len(&buffer[i], sizeof(info->dst_max) - 1);
+ memset(info->dst_max, 0, sizeof(info->dst_max));
+ strncpy(info->dst_max, &buffer[i], len);
+ if(debug)
+ printk("pg: dst_max set to: %s\n", info->dst_max);
+ i += len;
+ sprintf(pg_result, "OK: dst_max=%s", info->dst_max);
+ return count;
+ }
+ if (!strcmp(name, "src_min")) {
+ len = strn_len(&buffer[i], sizeof(info->src_min) - 1);
+ memset(info->src_min, 0, sizeof(info->src_min));
+ strncpy(info->src_min, &buffer[i], len);
+ if(debug)
+ printk("pg: src_min set to: %s\n", info->src_min);
+ i += len;
+ sprintf(pg_result, "OK: src_min=%s", info->src_min);
+ return count;
+ }
+ if (!strcmp(name, "src_max")) {
+ len = strn_len(&buffer[i], sizeof(info->src_max) - 1);
+ memset(info->src_max, 0, sizeof(info->src_max));
+ strncpy(info->src_max, &buffer[i], len);
+ if(debug)
+ printk("pg: src_max set to: %s\n", info->src_max);
+ i += len;
+ sprintf(pg_result, "OK: src_max=%s", info->src_max);
return count;
}
if (!strcmp(name, "dstmac")) {
char *v = valstr;
- unsigned char *m = pg_dstmac;
+ unsigned char *m = info->dst_mac;
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++) {
+ for(*m = 0;*v && m < info->dst_mac + 6; v++) {
if (*v >= '0' && *v <= '9') {
*m *= 16;
*m += *v - '0';
@@ -620,54 +1105,125 @@
sprintf(pg_result, "OK: dstmac");
return count;
}
+ if (!strcmp(name, "srcmac")) {
+ char *v = valstr;
+ unsigned char *m = info->src_mac;
+
+ 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 < info->src_mac + 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: srcmac");
+ 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;
+ if (info->pg_busy) {
+ strcpy(info->pg_result, "Already running...\n");
+ }
+ else {
+ info->pg_busy = 1;
+ strcpy(info->pg_result, "Starting");
+ pg_inject(info);
+ info->pg_busy = 0;
+ }
MOD_DEC_USE_COUNT;
return count;
}
- sprintf(pg_result, "No such parameter \"%s\"", name);
+ sprintf(info->pg_result, "No such parameter \"%s\"", name);
return -EINVAL;
}
static int __init pg_init(void)
{
+ int i;
printk(version);
cycles_calibrate();
if (pg_cpu_speed == 0) {
printk("pktgen: Error: your machine does not have working cycle counter.\n");
return -EINVAL;
}
- pg_proc_ent = create_proc_entry("net/pg", 0600, 0);
- if (!pg_proc_ent) {
- printk("pktgen: Error: cannot create net/pg procfs entry.\n");
- return -ENOMEM;
- }
- 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) {
- printk("pktgen: Error: cannot create net/pg_busy procfs entry.\n");
- remove_proc_entry("net/pg", NULL);
- return -ENOMEM;
- }
- pg_busy_proc_ent->read_proc = proc_pg_busy_read;
- pg_busy_proc_ent->data = 0;
- return 0;
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ memset(&(pginfos[i]), 0, sizeof(pginfos[i]));
+ pginfos[i].pkt_size = ETH_ZLEN;
+ pginfos[i].nfrags = 0;
+ pginfos[i].pg_multiskb = pg_multiskb_d;
+ pginfos[i].pg_ipg = pg_ipg_d;
+ pginfos[i].pg_count = pg_count_d;
+ pginfos[i].pg_sofar = 0;
+ pginfos[i].hh[12] = 0x08; /* fill in protocol. Rest is filled in later. */
+ pginfos[i].hh[13] = 0x00;
+ pginfos[i].udp_src_min = 9; /* sink NULL */
+ pginfos[i].udp_src_max = 9;
+ pginfos[i].udp_dst_min = 9;
+ pginfos[i].udp_dst_max = 9;
+
+ sprintf(pginfos[i].fname, "net/pg%i", i);
+ pginfos[i].pg_proc_ent = create_proc_entry(pginfos[i].fname, 0600, 0);
+ if (!pginfos[i].pg_proc_ent) {
+ printk("pktgen: Error: cannot create net/pg procfs entry.\n");
+ goto cleanup_mem;
+ }
+ pginfos[i].pg_proc_ent->read_proc = proc_pg_read;
+ pginfos[i].pg_proc_ent->write_proc = proc_pg_write;
+ pginfos[i].pg_proc_ent->data = (void*)(i);
+
+ sprintf(pginfos[i].busy_fname, "net/pg_busy%i", i);
+ pginfos[i].pg_busy_proc_ent = create_proc_entry(pginfos[i].busy_fname, 0, 0);
+ if (!pginfos[i].pg_busy_proc_ent) {
+ printk("pktgen: Error: cannot create net/pg_busy procfs entry.\n");
+ goto cleanup_mem;
+ }
+ pginfos[i].pg_busy_proc_ent->read_proc = proc_pg_busy_read;
+ pginfos[i].pg_busy_proc_ent->data = (void*)(i);
+ }
+ return 0;
+
+cleanup_mem:
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ if (strlen(pginfos[i].fname)) {
+ remove_proc_entry(pginfos[i].fname, NULL);
+ }
+ if (strlen(pginfos[i].busy_fname)) {
+ remove_proc_entry(pginfos[i].busy_fname, NULL);
+ }
+ }
+ return -ENOMEM;
}
static void __exit pg_cleanup(void)
{
- remove_proc_entry("net/pg", NULL);
- remove_proc_entry("net/pg_busy", NULL);
+ int i;
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ if (strlen(pginfos[i].fname)) {
+ remove_proc_entry(pginfos[i].fname, NULL);
+ }
+ if (strlen(pginfos[i].busy_fname)) {
+ remove_proc_entry(pginfos[i].busy_fname, NULL);
+ }
+ }
}
module_init(pg_init);
@@ -676,7 +1232,7 @@
MODULE_AUTHOR("Robert Olsson <robert.olsson@its.uu.se");
MODULE_DESCRIPTION("Packet Generator tool");
MODULE_LICENSE("GPL");
-MODULE_PARM(pg_count, "i");
-MODULE_PARM(pg_ipg, "i");
+MODULE_PARM(pg_count_d, "i");
+MODULE_PARM(pg_ipg_d, "i");
MODULE_PARM(pg_cpu_speed, "i");
-MODULE_PARM(pg_multiskb, "i");
+MODULE_PARM(pg_multiskb_d, "i");
[-- Attachment #3: patch1.txt --]
[-- Type: text/plain, Size: 3374 bytes --]
--- linux/Documentation/networking/pktgen.txt Sat Apr 20 17:28:11 2002
+++ linux.dev/Documentation/networking/pktgen.txt Sat Apr 20 17:39:55 2002
@@ -4,6 +4,9 @@
in the place where insmod may find it.
2. Cut script "ipg" (see below).
3. Edit script to set preferred device and destination IP address.
+3a. Create more scripts for different interfaces. Up to thirty-two
+ pktgen processes can be configured and run at once by using the
+ 32 /proc/net/pg* files.
4. Run in shell: ". ipg"
5. After this two commands are defined:
A. "pg" to start generator and to get results.
@@ -12,12 +15,33 @@
pgset "multiskb 0" use single SKB for all transmits
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 "count 200000" sets number of packets to send, set to zero
+ for continious sends untill explicitly
+ stopped.
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 "dst_min 10.0.0.1" Same as dst
+ pgset "dst_max 10.0.0.254" Set the maximum destination IP.
+ pgset "src_min 10.0.0.1" Set the minimum (or only) source IP.
+ pgset "src_max 10.0.0.254" Set the maximum source IP.
pgset "dstmac 00:00:00:00:00:00" sets MAC destination address
+ pgset "srcmac 00:00:00:00:00:00" sets MAC source address
+ pgset "src_mac_count 1" Sets the number of MACs we'll range through. The
+ 'minimum' MAC is what you set with srcmac.
+ pgset "dst_mac_count 1" Sets the number of MACs we'll range through. The
+ 'minimum' MAC is what you set with dstmac.
+ pgset "flag [name]" Set a flag to determine behaviour. Current flags
+ are: IPSRC_RND #IP Source is random (between min/max),
+ IPDST_RND, UDPSRC_RND,
+ UDPDST_RND, MACSRC_RND, MACDST_RND
+ pgset "udp_src_min 9" set UDP source port min, If < udp_src_max, then
+ cycle through the port range.
+ pgset "udp_src_max 9" set UDP source port max.
+ pgset "udp_dst_min 9" set UDP destination port min, If < udp_dst_max, then
+ cycle through the port range.
+ pgset "udp_dst_max 9" set UDP destination port max.
pgset stop aborts injection
Also, ^C aborts generator.
@@ -26,22 +50,24 @@
#! /bin/sh
-modprobe pktgen.o
+modprobe pktgen
+
+PGDEV=/proc/net/pg0
function pgset() {
local result
- echo $1 > /proc/net/pg
+ echo $1 > $PGDEV
- result=`cat /proc/net/pg | fgrep "Result: OK:"`
+ result=`cat $PGDEV | fgrep "Result: OK:"`
if [ "$result" = "" ]; then
- cat /proc/net/pg | fgrep Result:
+ cat $PGDEV | fgrep Result:
fi
}
function pg() {
- echo inject > /proc/net/pg
- cat /proc/net/pg
+ echo inject > $PGDEV
+ cat $PGDEV
}
pgset "odev eth0"
next reply other threads:[~2002-04-21 0:42 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2002-04-21 0:42 Ben Greear [this message]
2002-04-21 1:12 ` PATCH: pktgen.c update David S. Miller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=3CC20AED.4000007@candelatech.com \
--to=greearb@candelatech.com \
--cc=davem@redhat.com \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.