diff --git a/delay/linux/iptables_mods/iptables-1.3.6-imq.diff b/delay/linux/iptables_mods/iptables-1.3.6-imq.diff
new file mode 100644
index 0000000000000000000000000000000000000000..262fef1a1ec0664505fa9b7f73d02b9a912cde64
--- /dev/null
+++ b/delay/linux/iptables_mods/iptables-1.3.6-imq.diff
@@ -0,0 +1,221 @@
+--- iptables-1.3.6.orig/extensions.orig/.IMQ-test6	Thu Jan  1 01:00:00 1970
++++ iptables-1.3.6/extensions/.IMQ-test6	Mon Jun 16 10:12:47 2003
+@@ -0,0 +1,3 @@
++#!/bin/sh
++# True if IMQ target patch is applied.
++[ -f $KERNEL_DIR/net/ipv6/netfilter/ip6t_IMQ.c ] && echo IMQ
+--- iptables-1.3.6.orig/extensions.orig/libip6t_IMQ.c	Thu Jan  1 01:00:00 1970
++++ iptables-1.3.6/extensions/libip6t_IMQ.c	Mon Jun 16 10:12:47 2003
+@@ -0,0 +1,101 @@
++/* Shared library add-on to iptables to add IMQ target support. */
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include <getopt.h>
++
++#include <ip6tables.h>
++#include <linux/netfilter_ipv6/ip6_tables.h>
++#include <linux/netfilter_ipv6/ip6t_IMQ.h>
++
++/* Function which prints out usage message. */
++static void
++help(void)
++{
++	printf(
++"IMQ target v%s options:\n"
++"  --todev <N>		enqueue to imq<N>, defaults to 0\n", 
++IPTABLES_VERSION);
++}
++
++static struct option opts[] = {
++	{ "todev", 1, 0, '1' },
++	{ 0 }
++};
++
++/* Initialize the target. */
++static void
++init(struct ip6t_entry_target *t, unsigned int *nfcache)
++{
++	struct ip6t_imq_info *mr = (struct ip6t_imq_info*)t->data;
++
++	mr->todev = 0;
++	*nfcache |= NFC_UNKNOWN;
++}
++
++/* Function which parses command options; returns true if it
++   ate an option */
++static int
++parse(int c, char **argv, int invert, unsigned int *flags,
++      const struct ip6t_entry *entry,
++      struct ip6t_entry_target **target)
++{
++	struct ip6t_imq_info *mr = (struct ip6t_imq_info*)(*target)->data;
++	
++	switch(c) {
++	case '1':
++		if (check_inverse(optarg, &invert, NULL, 0))
++			exit_error(PARAMETER_PROBLEM,
++				   "Unexpected `!' after --todev");
++		mr->todev=atoi(optarg);
++		break;
++	default:
++		return 0;
++	}
++	return 1;
++}
++
++static void
++final_check(unsigned int flags)
++{
++}
++
++/* Prints out the targinfo. */
++static void
++print(const struct ip6t_ip6 *ip,
++      const struct ip6t_entry_target *target,
++      int numeric)
++{
++	struct ip6t_imq_info *mr = (struct ip6t_imq_info*)target->data;
++
++	printf("IMQ: todev %u ", mr->todev);
++}
++
++/* Saves the union ipt_targinfo in parsable form to stdout. */
++static void
++save(const struct ip6t_ip6 *ip, const struct ip6t_entry_target *target)
++{
++	struct ip6t_imq_info *mr = (struct ip6t_imq_info*)target->data;
++
++	printf("--todev %u", mr->todev);
++}
++
++static struct ip6tables_target imq = {
++	.next		= NULL,
++	.name		= "IMQ",
++	.version	= IPTABLES_VERSION,
++	.size		= IP6T_ALIGN(sizeof(struct ip6t_imq_info)),
++	.userspacesize	= IP6T_ALIGN(sizeof(struct ip6t_imq_info)),
++	.help		= &help,
++	.init		= &init,
++	.parse		= &parse,
++	.final_check	= &final_check,
++	.print		= &print,
++	.save		= &save,
++	.extra_opts	= opts
++};
++
++static __attribute__((constructor)) void _init(void)
++{
++	register_target6(&imq);
++}
+--- iptables-1.3.6.orig/extensions.orig/.IMQ-test	Thu Jan  1 01:00:00 1970
++++ iptables-1.3.6/extensions/.IMQ-test	Mon Jun 16 10:12:47 2003
+@@ -0,0 +1,3 @@
++#!/bin/sh
++# True if IMQ target patch is applied.
++[ -f $KERNEL_DIR/net/ipv4/netfilter/ipt_IMQ.c ] && echo IMQ
+--- iptables-1.3.6.orig/extensions.orig/libipt_IMQ.c	Thu Jan  1 01:00:00 1970
++++ iptables-1.3.6/extensions/libipt_IMQ.c	Mon Jun 16 10:12:47 2003
+@@ -0,0 +1,101 @@
++/* Shared library add-on to iptables to add IMQ target support. */
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include <getopt.h>
++
++#include <iptables.h>
++#include <linux/netfilter_ipv4/ip_tables.h>
++#include <linux/netfilter_ipv4/ipt_IMQ.h>
++
++/* Function which prints out usage message. */
++static void
++help(void)
++{
++	printf(
++"IMQ target v%s options:\n"
++"  --todev <N>		enqueue to imq<N>, defaults to 0\n", 
++IPTABLES_VERSION);
++}
++
++static struct option opts[] = {
++	{ "todev", 1, 0, '1' },
++	{ 0 }
++};
++
++/* Initialize the target. */
++static void
++init(struct ipt_entry_target *t, unsigned int *nfcache)
++{
++	struct ipt_imq_info *mr = (struct ipt_imq_info*)t->data;
++
++	mr->todev = 0;
++	*nfcache |= NFC_UNKNOWN;
++}
++
++/* Function which parses command options; returns true if it
++   ate an option */
++static int
++parse(int c, char **argv, int invert, unsigned int *flags,
++      const struct ipt_entry *entry,
++      struct ipt_entry_target **target)
++{
++	struct ipt_imq_info *mr = (struct ipt_imq_info*)(*target)->data;
++	
++	switch(c) {
++	case '1':
++		if (check_inverse(optarg, &invert, NULL, 0))
++			exit_error(PARAMETER_PROBLEM,
++				   "Unexpected `!' after --todev");
++		mr->todev=atoi(optarg);
++		break;
++	default:
++		return 0;
++	}
++	return 1;
++}
++
++static void
++final_check(unsigned int flags)
++{
++}
++
++/* Prints out the targinfo. */
++static void
++print(const struct ipt_ip *ip,
++      const struct ipt_entry_target *target,
++      int numeric)
++{
++	struct ipt_imq_info *mr = (struct ipt_imq_info*)target->data;
++
++	printf("IMQ: todev %u ", mr->todev);
++}
++
++/* Saves the union ipt_targinfo in parsable form to stdout. */
++static void
++save(const struct ipt_ip *ip, const struct ipt_entry_target *target)
++{
++	struct ipt_imq_info *mr = (struct ipt_imq_info*)target->data;
++
++	printf("--todev %u", mr->todev);
++}
++
++static struct iptables_target imq = {
++	.next		= NULL,
++	.name		= "IMQ",
++	.version	= IPTABLES_VERSION,
++	.size		= IPT_ALIGN(sizeof(struct ipt_imq_info)),
++	.userspacesize	= IPT_ALIGN(sizeof(struct ipt_imq_info)),
++	.help		= &help,
++	.init		= &init,
++	.parse		= &parse,
++	.final_check	= &final_check,
++	.print		= &print,
++	.save		= &save,
++	.extra_opts	= opts
++};
++
++static __attribute__((constructor)) void _init(void)
++{
++	register_target(&imq);
++}
+
diff --git a/delay/linux/kmods/linux-2.6.20-1.2944.fc6.emulab-1.linkdelay.patch b/delay/linux/kmods/linux-2.6.20-1.2944.fc6.emulab-1.linkdelay.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9e79126ecd219c38b62d0741f659627b00e9877b
--- /dev/null
+++ b/delay/linux/kmods/linux-2.6.20-1.2944.fc6.emulab-1.linkdelay.patch
@@ -0,0 +1,1254 @@
+diff -urN linux-2.6.20-1.2944.fc6.emulab-1/include/linux/pkt_sched.h linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/include/linux/pkt_sched.h
+--- linux-2.6.20-1.2944.fc6.emulab-1/include/linux/pkt_sched.h	2007-04-17 12:04:26.000000000 -0600
++++ linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/include/linux/pkt_sched.h	2007-09-27 23:03:33.000000000 -0600
+@@ -90,6 +90,41 @@
+ 	__u32	limit;	/* Queue length: bytes for bfifo, packets for pfifo */
+ };
+ 
++/* PLR section */
++
++struct tc_plr_qopt
++{
++        __u32 plr; /* % drop rate (0-100) */
++};
++
++enum
++{
++  TCA_PLR_UNSPEC,
++  TCA_PLR_PARMS,
++  TCA_PLR_INIT,
++  __TCA_PLR_MAX,
++};
++
++#define TCA_PLR_MAX (__TCA_PLR_MAX - 1)
++
++/* DELAY section */
++
++struct tc_delay_qopt
++{
++        __u32 delay_usec; /* # of usecs to delay */
++        __u8  reset_time;       /* flag: reset time on dequeue, or not */
++};
++
++enum
++{
++  TCA_DELAY_UNSPEC,
++  TCA_DELAY_PARMS,
++  TCA_DELAY_INIT,
++  __TCA_DELAY_MAX,
++};
++
++#define TCA_DELAY_MAX (__TCA_DELAY_MAX - 1)
++
+ /* PRIO section */
+ 
+ #define TCQ_PRIO_BANDS	16
+diff -urN linux-2.6.20-1.2944.fc6.emulab-1/net/sched/Kconfig linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/Kconfig
+--- linux-2.6.20-1.2944.fc6.emulab-1/net/sched/Kconfig	2007-04-17 12:04:29.000000000 -0600
++++ linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/Kconfig	2007-08-09 11:14:07.000000000 -0600
+@@ -104,6 +104,34 @@
+ 
+ comment "Queueing/Scheduling"
+ 
++config NET_SCH_PLR
++       tristate "PLR packet scheduler"
++       ---help---
++         Provides the facility to drop packets on a given interface
++	 at a specified packet loss rate (percentage 0-100). In general,
++	 there's no need for this option unless you want to perform
++	 network testing with artificially produced packet loss.
++
++	 If you say Y or M now, your networking will not be affected
++	 unless you enable the packet scheduler via the iproute2+tc
++	 package.
++
++	 If unsure, say N now.
++
++config NET_SCH_DELAY
++       tristate "Delay packet scheduler"
++       ---help---
++         Provides the facility to delay the delivery of IP packets on
++	 a given interface for a specified number of usecs. In general,
++	 there's no need for this option unless you want to perform
++	 network testing with artificially produced latencies.
++
++	 If you say Y or M now, your networking will not be affected
++	 unless you enable the packet scheduler via the iproute2tc
++	 package.
++
++	 If unsure, say N now.
++
+ config NET_SCH_CBQ
+ 	tristate "Class Based Queueing (CBQ)"
+ 	---help---
+diff -urN linux-2.6.20-1.2944.fc6.emulab-1/net/sched/Makefile linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/Makefile
+--- linux-2.6.20-1.2944.fc6.emulab-1/net/sched/Makefile	2007-04-17 12:04:29.000000000 -0600
++++ linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/Makefile	2007-08-09 11:14:07.000000000 -0600
+@@ -14,7 +14,9 @@
+ obj-$(CONFIG_NET_ACT_IPT)	+= act_ipt.o
+ obj-$(CONFIG_NET_ACT_PEDIT)	+= act_pedit.o
+ obj-$(CONFIG_NET_ACT_SIMP)	+= act_simple.o
+-obj-$(CONFIG_NET_SCH_FIFO)	+= sch_fifo.o
++obj-$(CONFIG_NET_SCH_FIFO)	+= sch_fifo.o 
++obj-$(CONFIG_NET_SCH_DELAY)     += sch_delay.o
++obj-$(CONFIG_NET_SCH_PLR)       += sch_plr.o
+ obj-$(CONFIG_NET_SCH_CBQ)	+= sch_cbq.o
+ obj-$(CONFIG_NET_SCH_HTB)	+= sch_htb.o
+ obj-$(CONFIG_NET_SCH_HPFQ)	+= sch_hpfq.o
+diff -urN linux-2.6.20-1.2944.fc6.emulab-1/net/sched/sch_delay.c linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/sch_delay.c
+--- linux-2.6.20-1.2944.fc6.emulab-1/net/sched/sch_delay.c	1969-12-31 17:00:00.000000000 -0700
++++ linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/sch_delay.c	2007-09-27 23:36:59.000000000 -0600
+@@ -0,0 +1,732 @@
++/*
++ * net/sched/sch_delay.c       Add a delay to anything going out...
++ *
++ *             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.
++ *
++ * Authors:
++ *         David T McWherter, <dtm@vramp.net>
++ *         Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
++ */
++
++#include <linux/module.h>
++#include <asm/uaccess.h>
++#include <asm/system.h>
++#include <asm/bitops.h>
++#include <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/string.h>
++#include <linux/mm.h>
++#include <linux/socket.h>
++#include <linux/sockios.h>
++#include <linux/in.h>
++#include <linux/errno.h>
++#include <linux/interrupt.h>
++#include <linux/if_ether.h>
++#include <linux/inet.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/notifier.h>
++#include <net/ip.h>
++#include <net/route.h>
++#include <linux/skbuff.h>
++#include <linux/rtnetlink.h>
++#include <net/sock.h>
++#include <net/pkt_sched.h>
++
++/*  Delay Scheduler
++    =======================================
++       Provides the facility to delay the delivery of IP packets on
++       a given interface for a specified number of usecs. In general,
++       there's no need for this option unless you want to perform
++       network testing with artificially produced latencies.
++ */
++
++#define DQDISC (1<<16)
++#define DCLASS (1<<17)
++#define DSTATS (1<<18)
++
++/* debugging level & bitmask: upper 16 bits are debug mask, lower 16
++   are level */
++/* bits for mask are defined above, and level is 0 - 1000 */
++#define DBG DSTATS | 1000
++
++#if 0
++#define DMSK(lvl) ((lvl) & 0xffff0000)
++#define DLVL(lvl)  ((lvl) & 0x0000ffff)
++#define DPRINTK(lvl, fmt, args...)   if ((DMSK(lvl) & DMSK(DBG)) && \
++                                         (DLVL(lvl) < DLVL(DBG))) \
++                                       { printk(KERN_DEBUG fmt, ##args); }
++#else
++#define DMSK(lvl)
++#define DLVL(lvl)
++#define DPRINTK(lvl, fmt, args...)
++#endif
++
++#define EPRINTK(fmt, args...)    printk(KERN_ERR fmt, ##args);
++
++#define TN_MAGIC 94343939L
++
++/* delay packet queue lock */
++static rwlock_t delay_mod_lock = RW_LOCK_UNLOCKED;
++
++/* XXX: temp for debugging */
++static __u32 oldbklog;
++
++struct time_node {
++  struct list_head      link;
++  long                  magic;
++  psched_time_t                time;
++};
++
++#define LIST_ENTRY(x)           list_entry((x), struct time_node, link)
++
++struct delay_sched_data
++{
++  struct Qdisc      *qd;         /* Child qdisc pointer */
++  struct tcf_proto  *filter_list; /* tc filter chain - unused. */
++  struct time_node  times_queue; /* The queue of socket input times */
++  struct timer_list wd_timer;    /* watchdog timer */
++  psched_tdiff_t    delaytime;   /* The amount of time to delay sending packets */
++  __u8              reset_time;  /* Should sk_buff time get reset?: flag */
++};
++
++
++/**
++ * time_node utility functions.
++ */
++static struct time_node *
++time_node_alloc (void)
++{
++  struct time_node * out;
++  DPRINTK(DQDISC | 50, "{ time_node_alloc\n");
++  out = (struct time_node*)kmalloc( sizeof(struct time_node), GFP_ATOMIC );
++  if ( !out )
++    {
++      EPRINTK("!!! - NULL time_node_alloc\n");
++    } else {
++      out->magic = TN_MAGIC;
++    }
++  DPRINTK(DQDISC | 50, "} time_node_alloc\n");
++  return out;
++}
++
++
++static void
++time_node_free( struct time_node * nd )
++{
++  if ( nd )
++    {
++      if ( nd->magic != TN_MAGIC )
++       {
++         EPRINTK("!!! - time_node_free of %p with bad magic! (%ld)\n",
++                       nd, (long)nd->magic);
++       } else {
++          kfree(nd);
++       }
++    }   
++}
++
++
++static struct time_node *
++time_node_stamp ( struct time_node * nd )
++{
++  if ( nd )
++    PSCHED_GET_TIME( nd->time );
++  return nd;
++}
++
++
++static struct time_node *
++time_node_queue_enqueue_head ( struct time_node * head, struct time_node * nd )
++{
++  if ( head && nd )
++    {
++      list_add(&nd->link, &head->link);
++    }
++  return nd;
++}
++
++
++static struct time_node *
++time_node_queue_enqueue_tail ( struct time_node * head, struct time_node * nd )
++{
++  DPRINTK(DQDISC | 50, "{ time_node_queue_enqueue_tail\n");
++  if ( head && nd )
++    {
++      list_add_tail(&nd->link, &head->link);
++    }
++  DPRINTK(DQDISC | 50, "} time_node_queue_enqueue_tail\n");
++  return nd;
++}
++ 
++
++static struct time_node *
++time_node_queue_dequeue_head ( struct time_node * head )
++{
++  struct time_node * first = NULL;
++  DPRINTK(DQDISC | 50, "{ time_node_queue_dequeue_head\n");
++  if ( head )
++    {
++      if ( !list_empty(&head->link) )
++       {
++          first = LIST_ENTRY(head->link.next);
++          list_del(head->link.next);
++       }
++    }
++  DPRINTK(DQDISC | 50, "} time_node_queue_dequeue_head\n");
++  return first;
++}
++
++
++static struct time_node *
++time_node_queue_peek_head ( struct time_node * head )
++{
++  struct time_node * first = NULL;
++  DPRINTK(DQDISC | 50, "{ time_node_queue_peek_head\n");
++  if ( head )
++    {
++      if ( !list_empty(&head->link) )
++       {
++          first = LIST_ENTRY(head->link.next);
++       }
++    }   
++  DPRINTK(DQDISC | 50, "} time_node_queue_peek_head\n");
++  return first;
++}
++
++
++static struct time_node *
++time_node_queue_purge ( struct time_node * head )
++{
++  DPRINTK(DQDISC | 50, "{ time_node_queue_purge(%p)\n", head);
++  if ( head )
++    {
++      for ( ; !list_empty(&head->link) ; )
++       {
++         struct time_node * nd;
++         nd = time_node_queue_dequeue_head ( head );
++         time_node_free( nd );
++       }
++    }
++  DPRINTK(DQDISC | 50, "} time_node_queue_purge\n");
++  return head;
++}
++
++/**
++ * Watchdog timer
++ */
++static void delay_watchdog ( unsigned long arg )
++{
++  struct Qdisc * sch = (struct Qdisc*)arg;
++  psched_time_t foo;
++
++  PSCHED_GET_TIME(foo);
++  DPRINTK(DQDISC | 200, "Bang! WD fired at: %llu\n", foo);
++  netif_schedule( sch->dev );
++}
++
++/* ------------------------------------------------------------------------- */
++/* Class functions --------------------------------------------------------- */
++/* ------------------------------------------------------------------------- */
++
++static int
++delay_graft(struct Qdisc *sch,unsigned long arg,
++          struct Qdisc *new1,struct Qdisc **old)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DCLASS | 100, "delay_graft(sch %p,[qdisc %p],new %p,old %p)\n",
++          sch,p,new1,old);
++  if (!new1)
++    new1 = &noop_qdisc;
++  sch_tree_lock(sch);
++  *old = xchg(&p->qd,new1);
++  /* XXX: decrease qlen? */
++  qdisc_reset(*old);
++  sch_tree_unlock(sch); /* @@@ move up ? */
++  return 0;
++}
++
++
++static struct Qdisc*
++delay_leaf(struct Qdisc *sch, unsigned long arg)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++
++  return p->qd;
++}
++
++
++static unsigned long
++delay_get(struct Qdisc *sch,u32 classid)
++{
++  struct delay_sched_data *p __attribute__((unused)) = qdisc_priv(sch);
++
++  DPRINTK(DCLASS | 100, "delay_get(sch %p,[qdisc %p],classid %x)\n",
++          sch,p,classid);
++  return 1; /* we currently only support one class so.. */
++}
++
++static int
++delay_class_change(struct Qdisc *sch, u32 classid, u32 parent,
++                 struct rtattr **tca, unsigned long *arg)
++{
++  return 0;
++}
++
++static unsigned long
++delay_bind_filter(struct Qdisc *sch, unsigned long parent, u32 classid)
++{
++  return delay_get(sch,classid);
++}
++
++
++static void
++delay_put(struct Qdisc *sch, unsigned long cl)
++{
++}
++
++static int
++delay_delete(struct Qdisc *sch,unsigned long arg)
++{
++  struct delay_sched_data *p __attribute__((unused)) = qdisc_priv(sch);
++
++  if (!arg)
++    return -EINVAL;
++
++  return 0;
++}
++ 
++
++static void
++delay_walk(struct Qdisc *sch,struct qdisc_walker *walker)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DCLASS | 100, "delay_walk(sch %p,[qdisc %p],walker %p)\n",
++          sch,p,walker);
++
++  if (!walker->stop) {
++    if (walker->count >= walker->skip) {
++      if (walker->fn(sch, 1, walker) < 0) {
++	walker->stop = 1;
++	return;
++      }
++    }
++    walker->count++;
++  }
++}
++
++
++static struct tcf_proto**
++delay_find_tcf(struct Qdisc *sch,unsigned long cl)
++{
++       struct delay_sched_data *p = qdisc_priv(sch);
++
++       return &p->filter_list;
++}
++
++
++static int
++delay_dump_class(struct Qdisc *sch, unsigned long cl,
++               struct sk_buff *skb, struct tcmsg *tcm)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  unsigned char *b = skb->tail;
++
++  DPRINTK(DCLASS | 100, "delay_dump_class(sch %p,[qdisc %p],class %ld\n",
++          sch,p,cl);
++  tcm->tcm_handle = TC_H_MAKE(TC_H_MAJ(sch->handle),1);
++  RTA_PUT(skb,TCA_OPTIONS,0,NULL);
++
++  return skb->len;
++
++ rtattr_failure:
++  skb_trim(skb,b-skb->data);
++  return -1;
++}
++
++
++/* ------------------------------------------------------------------------- */
++/* Qdisc functions --------------------------------------------------------- */
++/* ------------------------------------------------------------------------- */
++
++static int
++delay_enqueue(struct sk_buff *skb, struct Qdisc* sch)
++{
++  struct delay_sched_data *q = qdisc_priv(sch);
++  int out = NET_XMIT_DROP;
++
++  DPRINTK(DQDISC, "{ delay_enqueue\n");
++
++  {
++    struct time_node * nd;
++    psched_time_t currtime;
++
++    PSCHED_GET_TIME(currtime);
++    DPRINTK(DQDISC | 200, "Enqueueing at: %llu\n", currtime);
++    nd = time_node_stamp(time_node_alloc());
++    if ( nd )
++      {
++        write_lock(&delay_mod_lock);
++       (void) time_node_queue_enqueue_tail( &q->times_queue, nd );
++       __skb_queue_tail(&sch->q, skb);
++        write_unlock(&delay_mod_lock);
++       sch->qstats.backlog += skb->len;
++        if (sch->qstats.backlog > oldbklog) {
++          oldbklog = sch->qstats.backlog;
++          DPRINTK(DSTATS | 100, "Backlog increased to: %u\n", oldbklog);
++          DPRINTK(DSTATS | 100, "Queue size is: %u\n", sch->q.qlen);
++        }
++       sch->bstats.bytes += skb->len;
++       sch->bstats.packets++;
++       sch->q.qlen++;
++       out = 0; /* We don't drop packets - XXX: REVISE!*/
++      }
++  }
++
++  DPRINTK(DQDISC, "} delay_enqueue\n");
++
++  return out;
++}
++
++static int
++delay_requeue(struct sk_buff *skb, struct Qdisc* sch)
++{
++  /* XXX: ret 0 ? */
++  int out = NET_XMIT_SUCCESS;
++  struct delay_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DQDISC, "{ delay_requeue\n");
++
++  /* We don't delay it a second time, just pass to inner queue. */
++  if ((out = p->qd->ops->requeue(skb, p->qd)) == 0) {
++    sch->q.qlen++;
++    sch->qstats.requeues++;
++  }
++  else {
++    sch->qstats.drops++;
++    /* XXX: ret 0? */
++    out = NET_XMIT_DROP;
++  }
++
++  DPRINTK(DQDISC, "} delay_requeue\n");
++  return out;
++}
++
++static struct sk_buff *
++delay_dequeue(struct Qdisc* sch)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  struct sk_buff *skb = 0;
++  struct time_node * nd;
++  psched_time_t currtime;
++  psched_time_t tmp;
++
++  DPRINTK(DQDISC, "{ delay_dequeue\n");
++
++  PSCHED_GET_TIME(currtime);
++
++  write_lock(&delay_mod_lock);
++  nd = time_node_queue_dequeue_head ( &p->times_queue );
++  if ( nd )
++    {
++      PSCHED_TADD2(nd->time, p->delaytime, tmp);
++
++      if (PSCHED_TLESS(tmp, currtime))
++       {
++          // dequeue the skb
++         DPRINTK(DQDISC | 200, "time ((%llu+%ld)=%llu) < %llu, dequeuing\n",
++                  nd->time,p->delaytime,tmp,currtime);
++         skb = __skb_dequeue( &sch->q );
++          /* XXX: should always be an sk_buff to accompany the time_node */
++         if ( skb )
++           {
++              if (p->reset_time) {
++		skb_set_timestamp(skb,&currtime);
++              }
++	      sch->qstats.backlog -= skb->len;
++              time_node_free(nd);
++           }
++          if (p->qd->ops->enqueue(skb, p->qd) != 0)
++            {
++              DPRINTK(DQDISC | DCLASS, "Child qdisc dropped delayed packet!\n");
++              sch->qstats.drops++;
++              sch->q.qlen--;
++            }
++       } else {
++         /* psched_time_t diff; */
++
++         DPRINTK(DQDISC | 200, "time ((%llu+%ld)=%llu) >= %llu, "
++                  "NOT dequeueing\n",
++                  nd->time,p->delaytime,tmp,currtime);
++          /* requeue the time */
++         time_node_queue_enqueue_head ( &p->times_queue, nd );
++        }
++
++      /* update timer if any packets still exist in the queue */
++      nd = time_node_queue_peek_head( &p->times_queue );
++      if (nd && !netif_queue_stopped(sch->dev))
++        {
++          psched_tdiff_t diff;
++
++          /* Calculate the wait time before we should be awakened */
++          PSCHED_TADD2(nd->time, p->delaytime, tmp);
++          diff = PSCHED_TDIFF(tmp, currtime);
++
++          if (diff < 1)
++            diff = 1;
++
++          DPRINTK(DQDISC | 500, "Setting timer to expire at %lu jiffies.  "
++                  "Diff is %lu  Jiffies is %lu  HZ is %u\n",
++                  jiffies + PSCHED_US2JIFFIE(diff),
++                  PSCHED_US2JIFFIE(diff),
++                  jiffies,
++                  HZ);
++
++          mod_timer( &p->wd_timer, jiffies + PSCHED_US2JIFFIE(diff)); /* removed +100 */
++       }
++    }
++  write_unlock(&delay_mod_lock);
++
++  skb = p->qd->ops->dequeue(p->qd);
++  if ( skb )
++    {
++      DPRINTK(DQDISC | 100, "delay_dequeue: really dequeueing packet\n");
++      sch->q.qlen--;
++    }
++
++  DPRINTK(DQDISC, "} delay_dequeue\n");
++  return skb;
++}
++
++
++static unsigned int
++delay_drop(struct Qdisc* sch)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  struct sk_buff *skb;
++  int out = 0;
++
++  DPRINTK(DQDISC, "{ delay_drop\n");
++
++  write_lock(&delay_mod_lock);
++  skb = __skb_dequeue_tail(&sch->q);
++  if ( skb )
++    {
++      struct time_node * nd =
++        time_node_queue_dequeue_head ( &p->times_queue );
++      time_node_free( nd );
++
++      sch->qstats.backlog -= skb->len;
++      sch->qstats.drops++;
++      sch->q.qlen--;
++      kfree_skb(skb);
++      out = 1; /* We succeeded */
++    }
++  else if (p->qd->ops->drop(p->qd))
++    {
++      sch->q.qlen--;
++      out = 1;
++    }
++  write_unlock(&delay_mod_lock);
++
++  DPRINTK(DQDISC, "} delay_drop\n");
++  return out;
++}
++
++static void
++delay_reset(struct Qdisc* sch)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DQDISC, "{ delay_reset\n");
++
++  del_timer( &p->wd_timer );
++  skb_queue_purge(&sch->q);
++  time_node_queue_purge ( &p->times_queue );
++  sch->qstats.backlog = 0;
++  qdisc_reset(p->qd);
++  sch->q.qlen = 0;
++
++  DPRINTK(DQDISC, "} delay_reset\n");
++}
++
++static int
++delay_change(struct Qdisc *sch, struct rtattr *opt)
++{
++  int err = -EINVAL;
++  struct delay_sched_data *p = qdisc_priv(sch);
++  struct tc_delay_qopt * qopt;
++
++  DPRINTK(DQDISC, "{ delay_change\n");
++
++  if (opt == NULL)
++    goto done;
++
++  qopt = RTA_DATA(opt);
++
++  if ( RTA_PAYLOAD(opt) < sizeof(*qopt) )
++    {
++      EPRINTK("delay_change: opt is too small\n");
++      goto done;
++    }
++
++  DPRINTK(DSTATS, "About to lock tree in change\n");
++  sch_tree_lock(sch);
++  DPRINTK(DSTATS, "Tree locked\n");
++  p->delaytime = qopt->delay_usec;
++  p->reset_time = qopt->reset_time;
++  DPRINTK(DSTATS, "Reseting timer\n");
++  init_timer( &p->wd_timer );
++  p->wd_timer.function = delay_watchdog;
++  p->wd_timer.data = (unsigned long)sch;
++  DPRINTK(DSTATS, "Timer reset\n");
++  sch_tree_unlock(sch);
++  DPRINTK(DSTATS, "Tree unlocked\n");
++
++  DPRINTK(DQDISC, "delay_change: usec: %u\n", qopt->delay_usec);
++
++  err = 0;
++ done:
++  DPRINTK(DQDISC, "} delay_change\n");
++  return err;
++}
++
++static int
++delay_init(struct Qdisc *sch, struct rtattr *opt)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  int err = -EINVAL;
++
++  DPRINTK(DQDISC, "{ delay_init\n");
++
++  DPRINTK(DQDISC, ">> initializing queue\n");
++  INIT_LIST_HEAD(&p->times_queue.link);
++  oldbklog = 0; /* XXX: temp */
++
++  if ( delay_change(sch, opt) != 0 )
++    {
++      DPRINTK(DQDISC, ">> delay_change failed\n");
++    }
++  else
++    { 
++      p->filter_list = NULL;
++      if (!(p->qd = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops,
++				      TC_H_MAKE(sch->handle, 1))))
++        p->qd = &noop_qdisc;
++
++      err = 0;
++    }
++
++  DPRINTK(DQDISC, "} delay_init\n");
++  return err;
++}
++ 
++static
++void delay_destroy(struct Qdisc *sch)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  struct tcf_proto *tp;
++
++  DPRINTK(DQDISC, "{ delay_destroy\n");
++
++  DPRINTK(DQDISC | 100, ">> del_timer\n");
++
++  del_timer( &p->wd_timer );
++  skb_queue_purge(&sch->q);
++  time_node_queue_purge ( &p->times_queue );
++  sch->qstats.backlog = 0;
++
++  while (p->filter_list) {
++    tp = p->filter_list;
++    p->filter_list = tp->next;
++    tp->ops->destroy(tp);
++  }
++   
++  qdisc_destroy(p->qd);
++  p->qd = &noop_qdisc;
++
++  sch->q.qlen = 0;
++
++  DPRINTK(DQDISC, "} delay_destroy\n");
++}
++
++
++static int
++delay_dump(struct Qdisc *sch, struct sk_buff *skb)
++{
++  struct delay_sched_data *p = qdisc_priv(sch);
++  unsigned char           *b = skb->tail;
++  struct rtattr           *rta;
++  struct tc_delay_qopt    opt;
++
++  DPRINTK(DQDISC, "{ delay_dump\n");
++
++  rta = (struct rtattr *)b;
++  RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
++
++  /* XXX: fix locking */
++  //sch_tree_lock(sch);
++  opt.delay_usec = p->delaytime;
++  opt.reset_time = p->reset_time;
++  //sch_tree_unlock(sch);
++
++  RTA_PUT(skb, TCA_DELAY_PARMS, sizeof(opt), &opt);
++
++  DPRINTK(DQDISC, "} delay_dump\n");
++  rta->rta_len = skb->tail - b;
++  return skb->len;
++
++ rtattr_failure:
++  skb_trim(skb, b - skb->data);
++  DPRINTK(DQDISC, "} delay_dump - failed!\n");
++  return -1;
++}
++
++static struct Qdisc_class_ops delay_class_ops = {
++    .graft      = delay_graft,                 /* graft */
++    .leaf       = delay_leaf,                  /* leaf */
++    .get        = delay_get,                   /* get */
++    .put        = delay_put,                   /* put */
++    .change     = delay_class_change,          /* change */
++    .delete     = delay_delete,                /* delete */
++    .walk       = delay_walk,                  /* walk */
++    .tcf_chain  = delay_find_tcf,              /* tcf_chain */
++    .bind_tcf   = delay_bind_filter,           /* bind_tcf */
++    .unbind_tcf = delay_put,                   /* unbind_tcf */
++    .dump       = delay_dump_class,            /* dump */
++};
++  
++struct Qdisc_ops delay_qdisc_ops = {
++    .next      = NULL,
++    .cl_ops    = &delay_class_ops,
++    .id        = "delay",
++    .priv_size = sizeof(struct delay_sched_data),
++    .enqueue   = delay_enqueue,
++    .dequeue   = delay_dequeue,
++    .requeue   = delay_requeue,
++    .drop      = delay_drop,
++    .init      = delay_init,
++    .reset     = delay_reset,
++    .destroy   = delay_destroy,
++    .change    = delay_change,
++    .dump      = delay_dump,
++    .owner     = THIS_MODULE,
++};
++
++static int __init delay_module_init(void)
++{  
++  return register_qdisc(&delay_qdisc_ops);
++}
++static void __exit delay_module_exit(void)
++{
++  unregister_qdisc(&delay_qdisc_ops);
++}
++module_init(delay_module_init)
++module_exit(delay_module_exit)
++MODULE_LICENSE("GPL");
+diff -urN linux-2.6.20-1.2944.fc6.emulab-1/net/sched/sch_plr.c linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/sch_plr.c
+--- linux-2.6.20-1.2944.fc6.emulab-1/net/sched/sch_plr.c	1969-12-31 17:00:00.000000000 -0700
++++ linux-2.6.20-1.2944.fc6.emulab-1-linkdelay/net/sched/sch_plr.c	2007-09-27 22:58:45.000000000 -0600
+@@ -0,0 +1,417 @@
++/*
++ * net/sched/sch_plr.c probibalistically drop packets
++ *
++ *             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.
++ *
++ * Authors:
++ *         David Johnson <johnsond@cs.utah.edu> (2.4 to 2.6)
++ *         Kirk Webb <kwebb@cs.utah.edu>
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/skbuff.h>
++#include <linux/rtnetlink.h>
++#include <net/sock.h>
++#include <net/pkt_sched.h>
++
++/*
++    Simple PLR "scheduler" We drop packets based on a uniform
++    probability dist, set within the range of an u32.  The tc tool
++    expects a floating pt. number between 0 and 1, and subsequently
++    scales it to the appropriate closest u32 value (btw 0 and 2^32)
++    before handing it off to this module via rtnetlink.
++ */
++
++#define DQDISC (1<<16)
++#define DCLASS (1<<17)
++
++/* debugging level & bitmask: upper 16 bits are debug mask, lower 16
++   are level */
++/* bits for mask are defined above, and level is 0 - 1000 */
++#define DBG DQDISC | DCLASS | 1000
++
++#if 0
++#define DMSK(lvl) ((lvl) & 0xffff0000)
++#define DLVL(lvl)  ((lvl) & 0x0000ffff)
++#define DPRINTK(lvl, fmt, args...)   if ((DMSK(lvl) & DMSK(DBG)) && \
++                                         (DLVL(lvl) < DLVL(DBG))) \
++                                       { printk(KERN_DEBUG fmt, ##args); }
++#else
++#define DMSK(lvl)
++#define DLVL(lvl)
++#define DPRINTK(lvl, fmt, args...)
++#endif
++
++#define EPRINTK(fmt, args...)    printk(KERN_ERR fmt, ##args);
++
++struct plr_sched_data
++{
++  struct Qdisc      *qd;            /* child qdisc - we only support 1 */
++  u32               plr;            /* packet loss rate */
++};
++
++/* ------------------------------------------------------------------------- */
++/* Class functions --------------------------------------------------------- */
++/* ------------------------------------------------------------------------- */
++
++static int plr_graft(struct Qdisc *sch,unsigned long arg,
++		     struct Qdisc *new1,struct Qdisc **old)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DCLASS | 100, "plr_graft(sch %p,[qdisc %p],new %p,old %p)\n",
++          sch,p,new1,old);
++  if (!new1)
++    new1 = &noop_qdisc;
++  sch_tree_lock(sch);
++  *old = xchg(&p->qd,new1);
++  qdisc_tree_decrease_qlen(*old, (*old)->q.qlen);
++  qdisc_reset(*old);
++  sch_tree_unlock(sch); /* @@@ move up ? */
++
++  return 0;
++}
++
++
++static struct Qdisc* plr_leaf(struct Qdisc *sch, unsigned long arg)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  return p->qd;
++}
++
++static unsigned long plr_get(struct Qdisc *sch,u32 classid)
++{
++  return 1; /* we currently only support one class so.. */
++}
++
++static int plr_class_change(struct Qdisc *sch, u32 classid, u32 parent,
++			    struct rtattr **tca, unsigned long *arg)
++{
++  return -ENOSYS;
++}
++
++static void plr_put(struct Qdisc *sch, unsigned long cl)
++{
++}
++
++static int plr_delete(struct Qdisc *sch,unsigned long arg)
++{
++  return -ENOSYS;
++}
++
++
++static void plr_walk(struct Qdisc *sch,struct qdisc_walker *walker)
++{
++  if (!walker->stop) {
++    if (walker->count >= walker->skip) {
++      if (walker->fn(sch, 1, walker) < 0) {
++	walker->stop = 1;
++	return;
++      }
++    }
++    walker->count++;
++  }
++}
++
++static struct tcf_proto** plr_find_tcf(struct Qdisc *sch,unsigned long cl)
++{
++  return NULL;
++}
++
++static int plr_dump_class(struct Qdisc *sch, unsigned long cl,
++			  struct sk_buff *skb, struct tcmsg *tcm)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  unsigned char *b = skb->tail;
++
++  if (cl != 1) 
++    return -ENOENT;
++
++  DPRINTK(DCLASS | 100, "plr_dump_class(sch %p,[qdisc %p],class %ld\n",
++          sch,p,cl);
++
++  /*
++   * tcm->tcm_handle = TC_H_MAKE(sch->handle,1);
++   * RTA_PUT(skb,TCA_OPTIONS,0,NULL);
++
++   * return skb->len;
++
++   * rtattr_failure:
++   * skb_trim(skb,b-skb->data);
++   * return -1;
++   */
++
++  tcm->tcm_handle |= TC_H_MIN(1);
++  tcm->tcm_info = p->qd->handle;
++
++  return 0;
++}
++
++
++/* ------------------------------------------------------------------------- */
++/* Qdisc functions --------------------------------------------------------- */
++/* ------------------------------------------------------------------------- */
++
++static int plr_enqueue(struct sk_buff *skb, struct Qdisc* sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  int ret;
++
++  DPRINTK(DQDISC, "{ plr_enqueue\n");
++
++  {
++    u32 rnd = net_random();
++    psched_time_t currtime;
++
++    PSCHED_GET_TIME(currtime);
++    DPRINTK(DQDISC | 100, "Enqueueing at: %llu\n", currtime);
++
++    if ( rnd < p->plr)
++      {
++        /*
++           Drop, but don't tell the network device we did.
++           Keep track though in stats.
++        */
++	DPRINTK(DQDISC | 200, "Dropped incoming packet, rand val: %u\n", rnd);
++	kfree_skb(skb);
++	sch->qstats.drops++;
++	return NET_XMIT_DROP;
++      }
++
++    if ((ret = p->qd->ops->enqueue(skb,p->qd)) != 0) 
++      {
++	DPRINTK(DQDISC | 200, "Didn't enqueue incoming packet, rand val: %u\n", rnd);
++	sch->qstats.drops++;
++	return ret;
++      }
++
++    sch->q.qlen++;
++    sch->bstats.bytes += skb->len;
++    sch->bstats.packets++;
++  }
++
++  DPRINTK(DQDISC, "} plr_enqueue\n");
++
++  return ret;
++}
++ 
++static int plr_requeue(struct sk_buff *skb, struct Qdisc* sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  int out = 0;
++
++  DPRINTK(DQDISC, "{ plr_requeue\n");
++
++  if ((out = p->qd->ops->requeue(skb, p->qd)) == 0) {
++    sch->q.qlen++;
++    sch->qstats.requeues++;
++  }
++  else {
++    sch->qstats.drops++;
++  }
++
++  DPRINTK(DQDISC, "} plr_requeue\n");
++
++  return out;
++}
++
++static struct sk_buff *plr_dequeue(struct Qdisc* sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  struct sk_buff *skb = NULL;
++  psched_time_t currtime;
++
++  DPRINTK(DQDISC, "{ plr_dequeue\n");
++
++  PSCHED_GET_TIME(currtime);
++
++  // dequeue the skb
++  DPRINTK(DQDISC | 200, "time %llu, dequeuing\n", currtime);
++  skb = p->qd->ops->dequeue(p->qd);
++  if ( skb )
++    {
++      sch->q.qlen--;
++    }
++
++  DPRINTK(DQDISC, "} plr_dequeue\n");
++  return skb;
++}
++
++
++static unsigned int plr_drop(struct Qdisc* sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  int out = 0;
++
++  DPRINTK(DQDISC, "{ plr_drop\n");
++
++  if ((out = p->qd->ops->drop(p->qd)) == 1) {
++    sch->q.qlen--;
++    sch->qstats.drops++; /* XXX: should we report a drop too? */  /* Yes. */
++  }
++
++  DPRINTK(DQDISC, "} plr_drop\n");
++  return out;
++}
++
++static void plr_reset(struct Qdisc* sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DQDISC, "{ plr_reset\n");
++
++  qdisc_reset(p->qd);
++  sch->q.qlen = 0;
++
++  DPRINTK(DQDISC, "} plr_reset\n");
++}
++ 
++static int plr_change(struct Qdisc *sch, struct rtattr *opt)
++{
++  int err = -EINVAL;
++  struct plr_sched_data *p = qdisc_priv(sch);
++  struct tc_plr_qopt *qopt;
++
++  DPRINTK(DQDISC, "{ plr_change\n");
++
++  if (opt == NULL) 
++    goto done;
++
++  qopt = RTA_DATA(opt);
++
++  if ( RTA_PAYLOAD(opt) < sizeof(u32) )
++    {
++      EPRINTK("plr_change: opt is too small\n");
++      goto done;
++    }
++
++  sch_tree_lock(sch);
++  p->plr = qopt->plr;
++  sch_tree_unlock(sch);
++
++  DPRINTK(DQDISC | 100, "plr_change: plr: %u\n", qopt->plr );
++
++  err = 0;
++ done:
++  DPRINTK(DQDISC, "} plr_change\n");
++  return err;
++}
++
++static int plr_init(struct Qdisc *sch, struct rtattr *opt)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  int err = -EINVAL;
++
++  DPRINTK(DQDISC, "{ plr_init\n");
++
++  if ( plr_change(sch, opt) != 0 )
++    {
++      DPRINTK(DQDISC, ">> plr_change failed\n");
++    }
++  else
++    { 
++      if (!(p->qd = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops,
++				      TC_H_MAKE(sch->handle, 1))))
++        p->qd = &noop_qdisc;
++
++      err = 0;
++    }
++
++  DPRINTK(DQDISC | 100, "} plr_init\n");
++  return err;
++}
++ 
++static void plr_destroy(struct Qdisc *sch)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++
++  DPRINTK(DQDISC, "{ plr_destroy\n");
++
++  qdisc_destroy(p->qd);
++
++  DPRINTK(DQDISC, "} plr_destroy\n");
++}
++
++
++static int plr_dump(struct Qdisc *sch, struct sk_buff *skb)
++{
++  struct plr_sched_data *p = qdisc_priv(sch);
++  unsigned char         *b = skb->tail;
++  struct rtattr *rta;
++  struct tc_plr_qopt opt;
++
++  DPRINTK(DQDISC, "{ plr_dump\n");
++
++  rta = (struct rtattr *)b;
++  RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
++
++  DPRINTK(DQDISC, "  plr_dump a\n");
++
++  /* XXX: fix locking */
++  //sch_tree_lock(sch);
++  opt.plr = p->plr;
++  //sch_tree_unlock(sch);
++
++  DPRINTK(DQDISC, "  plr_dump b\n");
++
++  RTA_PUT(skb, TCA_PLR_PARMS, sizeof(opt), &opt);
++
++  DPRINTK(DQDISC, "} plr_dump\n");
++  rta->rta_len = skb->tail - b;
++  return skb->len;
++
++ rtattr_failure:
++  skb_trim(skb, b - skb->data);
++  DPRINTK(DQDISC, "} plr_dump (failed!)\n");
++  return -1;
++}
++
++static struct Qdisc_class_ops plr_class_ops = {
++    .graft     = plr_graft,                   /* graft */
++    .leaf      = plr_leaf,                    /* leaf */
++    .get       = plr_get,                     /* get */
++    .put       = plr_put,                     /* put */
++    .change    = plr_class_change,            /* change */
++    .delete    = plr_delete,                  /* delete */
++    .walk      = plr_walk,                    /* walk */
++    .tcf_chain = plr_find_tcf,                /* tcf_chain */
++    .dump      = plr_dump_class,              /* dump */
++};
++
++static struct Qdisc_ops plr_qdisc_ops = {
++    .next      = NULL,
++    .cl_ops    = &plr_class_ops,
++    .id        = "plr",
++    .priv_size = sizeof(struct plr_sched_data),
++    .enqueue   = plr_enqueue,
++    .dequeue   = plr_dequeue,
++    .requeue   = plr_requeue,
++    .drop      = plr_drop,
++    .init      = plr_init,
++    .reset     = plr_reset,
++    .destroy   = plr_destroy,
++    .change    = plr_change,
++    .dump      = plr_dump,
++    .owner     = THIS_MODULE,
++};
++
++static int __init plr_module_init(void)
++{
++    return register_qdisc(&plr_qdisc_ops);
++}
++static void __exit plr_module_exit(void)
++{
++    unregister_qdisc(&plr_qdisc_ops);
++}
++module_init(plr_module_init)
++module_exit(plr_module_exit)
++MODULE_LICENSE("GPL");
diff --git a/delay/linux/tc_mods/iproute-2.6.20-070313-emulab.patch b/delay/linux/tc_mods/iproute-2.6.20-070313-emulab.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1dc7bd5fc2101c9f7e73ffddd2a0297768761315
--- /dev/null
+++ b/delay/linux/tc_mods/iproute-2.6.20-070313-emulab.patch
@@ -0,0 +1,324 @@
+diff -urN iproute-2.6.20-070313/Config iproute-2.6.20-070313-emulab/Config
+--- iproute-2.6.20-070313/Config	2007-09-24 20:43:43.000000000 -0600
++++ iproute-2.6.20-070313-emulab/Config	2007-09-24 21:30:11.000000000 -0600
+@@ -1 +1 @@
+-# Generated config based on /usr/src/iproute-2.6.20-070313/include
++# Generated config based on /usr/src/linux-2.6.20-1.2944.fc6.emulab-1/include
+diff -urN iproute-2.6.20-070313/Makefile iproute-2.6.20-070313-emulab/Makefile
+--- iproute-2.6.20-070313/Makefile	2007-03-13 15:50:56.000000000 -0600
++++ iproute-2.6.20-070313-emulab/Makefile	2007-08-09 14:55:52.000000000 -0600
+@@ -1,7 +1,8 @@
++KERNEL_INCLUDE=/usr/src/linux-2.6.20-1.2944.fc6.emulab-1/include
+ DESTDIR=
+-SBINDIR=/usr/sbin
+-CONFDIR=/etc/iproute2
+-DOCDIR=/usr/share/doc/iproute2
++SBINDIR=/usr/local/sbin
++CONFDIR=/usr/local/etc/iproute2
++DOCDIR=/usr/local/share/doc/iproute2
+ MANDIR=/usr/share/man
+ 
+ # Path to db_185.h include
+diff -urN iproute-2.6.20-070313/include/linux/pkt_sched.h iproute-2.6.20-070313-emulab/include/linux/pkt_sched.h
+--- iproute-2.6.20-070313/include/linux/pkt_sched.h	2007-03-13 15:50:56.000000000 -0600
++++ iproute-2.6.20-070313-emulab/include/linux/pkt_sched.h	2007-08-09 15:50:25.000000000 -0600
+@@ -90,6 +90,21 @@
+ 	__u32	limit;	/* Queue length: bytes for bfifo, packets for pfifo */
+ };
+ 
++/* PLR section */
++
++struct tc_plr_qopt
++{
++  __u32 plr; /* % drop rate (0-100) */
++};
++
++/* DELAY section */
++
++struct tc_delay_qopt
++{
++  __u32 delay_usec; /* # of usecs to delay */
++  __u8  reset_time;       /* flag: reset time on dequeue, or not */
++};
++
+ /* PRIO section */
+ 
+ #define TCQ_PRIO_BANDS	16
+diff -urN iproute-2.6.20-070313/tc/Makefile iproute-2.6.20-070313-emulab/tc/Makefile
+--- iproute-2.6.20-070313/tc/Makefile	2007-03-13 15:50:56.000000000 -0600
++++ iproute-2.6.20-070313-emulab/tc/Makefile	2007-08-09 14:37:43.000000000 -0600
+@@ -16,6 +16,8 @@
+ TCMODULES += f_route.o
+ TCMODULES += f_fw.o
+ TCMODULES += f_basic.o
++TCMODULES += q_delay.o
++TCMODULES += q_plr.o
+ TCMODULES += q_dsmark.o
+ TCMODULES += q_gred.o
+ TCMODULES += f_tcindex.o
+diff -urN iproute-2.6.20-070313/tc/q_delay.c iproute-2.6.20-070313-emulab/tc/q_delay.c
+--- iproute-2.6.20-070313/tc/q_delay.c	1969-12-31 17:00:00.000000000 -0700
++++ iproute-2.6.20-070313-emulab/tc/q_delay.c	2007-09-24 21:27:29.000000000 -0600
+@@ -0,0 +1,131 @@
++/*
++ * q_delay.c		Delay.
++ *
++ *		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.
++ *
++ * Authors:	David T. McWherter, <dtm@vramp.net>
++ *			Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
++ */
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <syslog.h>
++#include <fcntl.h>
++#include <sys/socket.h>
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <string.h>
++
++#include "utils.h"
++#include "tc_util.h"
++
++static void explain(void)
++{
++	fprintf(stderr, "Usage: ... delay <microseconds> [reset_time (0|1)]\n");
++}
++
++static void explain1(char *arg)
++{
++	fprintf(stderr, "Illegal \"%s\"\n", arg);
++}
++
++
++#define usage() return(-1)
++
++static int 
++delay_parse_opt
++	(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
++{
++	struct tc_delay_qopt opt;
++        char *end;
++
++	memset(&opt, 0, sizeof(opt));
++
++	if ( argc > 4 ) { 
++          fprintf(stderr, "Too many arguments (seen: %d, expected: 2 or 4)\n",
++                  argc);
++          return -1;
++	} else if ( argc < 2 ) { 
++          fprintf(stderr, "Too few arguments (seen: %d, expected: 2 or 4)\n",
++                  argc);
++          return -1;
++	} else {
++
++          while ( argc > 0 ) { 
++            if (!strcmp(*argv, "usecs")) {
++              NEXT_ARG();
++              opt.delay_usec = strtoul(*argv,&end,0);
++              if (*end) {
++                explain1("microseconds");
++                return -1;
++              }
++              fprintf( stdout, "Usecs: %u\n", opt.delay_usec );
++            }
++            else if (!strcmp(*argv, "reset_time")) {
++              NEXT_ARG();
++              opt.reset_time = strtoul(*argv,&end,0);
++              if (*end) {
++                explain1("reset_time");
++                return -1;
++              }
++              fprintf( stdout, "reset_time: %u\n", opt.reset_time );
++            }
++            argc--;
++            argv++;
++          }
++        }
++
++	addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
++
++	return 0;
++}
++
++static int 
++delay_print_opt
++	(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
++{
++	struct tc_delay_qopt *qopt;
++
++	if (opt == NULL)
++		return 0;
++
++	if ( RTA_PAYLOAD(opt) < sizeof(*qopt))
++		return -1;
++	
++	qopt = RTA_DATA(opt);
++	fprintf( f, "delay { %u } reset_time { %u }", 
++                 qopt->delay_usec,
++                 qopt->reset_time
++		);
++
++	return 0;
++}
++
++
++static int delay_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
++{
++	return 0;
++}
++
++
++static int 
++delay_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
++   struct nlmsghdr *n)
++{
++  return 0;
++}
++
++
++struct qdisc_util delay_qdisc_util = {
++    .id = "delay",
++    .parse_qopt = delay_parse_opt,
++    .print_qopt = delay_print_opt,
++    .print_xstats = delay_print_xstats,
++
++    .parse_copt = delay_parse_class_opt,
++    .print_copt = delay_print_opt,
++};
+diff -urN iproute-2.6.20-070313/tc/q_plr.c iproute-2.6.20-070313-emulab/tc/q_plr.c
+--- iproute-2.6.20-070313/tc/q_plr.c	1969-12-31 17:00:00.000000000 -0700
++++ iproute-2.6.20-070313-emulab/tc/q_plr.c	2007-09-24 21:29:18.000000000 -0600
+@@ -0,0 +1,115 @@
++/*
++ * q_plr.c      packet loss qdisc
++ *
++ *		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.
++ *
++ * Authors:	Kirk Webb, <kwebb@cs.utah.edu>
++ *			Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
++ */
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <syslog.h>
++#include <fcntl.h>
++#include <sys/socket.h>
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <string.h>
++
++#include "utils.h"
++#include "tc_util.h"
++
++static void explain(void)
++{
++	fprintf(stderr, "Usage: ... plr <rate (%% loss: 0-100)>\n");
++}
++
++static void explain1(char *arg)
++{
++	fprintf(stderr, "Illegal \"%s\"\n", arg);
++}
++
++
++#define usage() return(-1)
++
++static int 
++plr_parse_opt
++	(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
++{
++	struct tc_plr_qopt opt;
++	double plr_rate = 0;
++	char *p;
++
++	memset(&opt, 0, sizeof(opt));
++
++	if ( argc > 1 ) { 
++		fprintf(stderr, "Too many arguments (seen: %d, expected: %d)\n",
++			argc, 1);
++		return -1;
++	} else if ( argc < 1 ) { 
++		fprintf(stderr, "Too few arguments (seen: %d, expected: %d)\n",
++			argc, 1);
++		return -1;
++	} else if ( argc == 1 ) {
++	  plr_rate = strtod(*argv, &p);
++            if (p == *argv || plr_rate < 0 || plr_rate > 1) {
++	      explain1("fraction (range 0-1)");
++	      return -1;
++	    }
++	}
++
++	opt.plr = (unsigned int)(plr_rate*0xffffffffUL);
++	fprintf( stdout, "PLR: %u\n", opt.plr );
++
++	addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
++
++	return 0;
++}
++
++static int 
++plr_print_opt
++	(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
++{
++	struct tc_plr_qopt *qopt;
++
++	if (opt == NULL)
++		return 0;
++
++	if ( RTA_PAYLOAD(opt) < sizeof(*qopt))
++		return -1;
++	
++	qopt = RTA_DATA(opt);
++	fprintf( f, "PLR: %f", 
++		 qopt->plr / (double) 0xffffffff
++		);
++
++	return 0;
++}
++
++static int plr_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
++{
++	return 0;
++}
++
++static int 
++plr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
++   struct nlmsghdr *n)
++{
++  return 0;
++}
++
++
++
++struct qdisc_util plr_qdisc_util = {
++    .id = "plr",
++    .parse_qopt = plr_parse_opt,
++    .print_qopt = plr_print_opt,
++    .print_xstats = plr_print_xstats,
++
++    .parse_copt = plr_parse_class_opt,
++    .print_copt = plr_print_opt,
++};
+diff -urN iproute-2.6.20-070313/tc/tc_qdisc.c iproute-2.6.20-070313-emulab/tc/tc_qdisc.c
+--- iproute-2.6.20-070313/tc/tc_qdisc.c	2007-03-13 15:50:56.000000000 -0600
++++ iproute-2.6.20-070313-emulab/tc/tc_qdisc.c	2007-08-09 14:42:02.000000000 -0600
+@@ -36,7 +36,7 @@
+ 	fprintf(stderr, "\n");
+ 	fprintf(stderr, "       tc qdisc show [ dev STRING ] [ingress]\n");
+ 	fprintf(stderr, "Where:\n");
+-	fprintf(stderr, "QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n");
++	fprintf(stderr, "QDISC_KIND := { [p|b]fifo | delay | plr | tbf | prio | cbq | red | etc. }\n");
+ 	fprintf(stderr, "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n");
+ 	return -1;
+ }