diff --git a/AUTHORS b/AUTHORS
index 59e7cca30d0f88264af27a3dc8499f6d4ed4874c..ef2d3d1552df2828da1836be7db023fc1282bbd7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,6 +3,10 @@ benjie@lcs.mit.edu
 polling extensions, Linux kernel patches, stride scheduling, Linux kernel
 thread, device driver updates
 
+Douglas S. J. DeCouto
+decouto@lcs.mit.edu
+user level elements
+
 Thomer Gil
 thomer@lcs.mit.edu
 dynamic IP routing table
diff --git a/doc/element2man.pl b/doc/element2man.pl
index bcebd5d064d730fe4ea2ef3f3855b8c65d13fef9..4c546a7cd042bd42beca8b6c128b48916819339d 100755
--- a/doc/element2man.pl
+++ b/doc/element2man.pl
@@ -12,10 +12,10 @@
 # version. For more information, see the `COPYRIGHT' file in the source
 # distribution.
 
-my(%section_is_array) = ( 'h' => 1, 'a' => 1 );
+my(%section_is_array) = ( 'h' => 1, 'a' => 1, 'page' => 1 );
 my $directory;
 my $section = 'n';
-my(@all_created, %class_name, %processing);
+my(@all_outnames, %all_outsections, %class_name, %processing);
 
 my(%processing_constants) =
     ( 'AGNOSTIC' => 'a/a', 'PUSH' => 'h/h', 'PULL' => 'l/l',
@@ -42,24 +42,19 @@ my $prologue = <<'EOD;';
 EOD;
 chomp $prologue;
 
-sub nroffize ($;$$$) {
-  my($t, $embolden, $srelated, $related_source) = @_;
+sub nroffize ($;$$) {
+  my($t, $related, $related_source) = @_;
   my($i);
 
   # embolden & manpageize
-  if (defined $embolden) {
-    foreach $i (sort { length($b) <=> length($a) } @$embolden) {
-      $t =~ s{(^|[^\w@/])$i($|[^\w@/])}{$1<B>$i</B>$2}gs;
-    }
-  }
-  if (defined $srelated) {
-    foreach $i (@$srelated) {
-      $t =~ s{(^|[^\w@/])$i($|[^\w@/(])}{$1<\#>$i</\#>$2}gs;
+  if (defined $related) {
+    foreach $i (@$related) {
+      $t =~ s{(^|[^\w@/>])$i($|[^\w@/])}{$1<\#>$i</\#>$2}gs;
     }
   }
 
   # remove emboldening & manpaging on examples
-  1 while ($t =~ s{^= (.*)</?[B\#]>}{= $1}gm);
+  1 while ($t =~ s{^= (.*)</?[\#]>}{= $1}gm);
   
   $t =~ s/\\/\\\\/g;
   $t =~ s/^(= )?\./$1\\&./gm;
@@ -68,7 +63,14 @@ sub nroffize ($;$$$) {
   $t =~ s/<i>(.*?)<\/i>/\\fI$1\\fP/ig;
   $t =~ s/<b>(.*?)<\/b>/\\fB$1\\fP/ig;
   $t =~ s/<tt>(.*?)<\/tt>/\\f(CW$1\\fP/ig;
-  $t =~ s/<\#>(.*?)<\/\#>(\S*)\s*/\n.M $1 $related_source->{$1} $2\n/g;
+  $t =~ s{<\#>(.*?)<\/\#>(\S*)(\s*)}{
+    if ($related_source->{$1}) {
+      "\n.M $1 \"$related_source->{$1}\" $2\n";
+    } else {
+      "\\fB$1\\fP$2$3";
+    }
+  }eg;
+  $t =~ s{\n\.M (\S+) \"(\S+)\" \(\2\)\n}{\n.M $1 "$2"\n}g;
   1 while ($t =~ s/^\.PP\n\.PP\n/.PP\n/gm);
   $t =~ s/^= (.*\n)/.nf\n$1.fi\n/mg;
   $t =~ s/^\.fi\n\.nf\n//mg;
@@ -100,7 +102,8 @@ sub process_comment ($$) {
       $x{$1} .= "$2\n";
     }
   }
-  
+
+  # snarf class names
   my(@classes, %classes);
   while ($x{'c'} =~ /^\s*(\w+)\(/mg) { # configuration arguments section
     push @classes, $1 if !exists $classes{$1};
@@ -114,36 +117,76 @@ sub process_comment ($$) {
     print STDERR "$filename: no class definitions\n    (did you forget `()' in the =c section?)\n";
     return;
   }
-  my($classes_plural) = (@classes == 1 ? '' : 's');
-  my($classes) = join(', ', @classes);
+
+  # output filenames might be specified in 'page' section
+  my(@outfiles) = @classes;
+  my(@outsections) = ($section) x @classes;
+  if ($x{'page'}) {
+    @outfiles = ();
+    @outsections = ();
+    foreach $i (map(split(/\s+/), @{$x{'page'}})) {
+      if ($i =~ /^(.*)\((.*)\)$/) {
+	push @outfiles, $1;
+	push @outsections, $2;
+      } else {
+	push @outfiles, $i;
+	push @outsections, $section;
+      }
+    }
+  }
 
   # open new output file if necessary
+  my($main_outname);
   if ($directory) {
-    if (!open(OUT, ">$directory/$classes[0].$section")) {
-      print STDERR "$directory/$classes[0].$section: $!\n";
+    $main_outname = "$directory/$outfiles[0].$outsections[0]";
+    if (!open(OUT, ">$main_outname")) {
+      print STDERR "$main_outname: $!\n";
       return;
     }
   }
-  push @all_created, $classes[0];
-  
+  push @all_outfiles, $outfiles[0];
+  $all_outsections{$outfiles[0]} = $outsections[0];
+
+  # front matter
+  my($classes_text) = join(', ', @classes);
+  my($oneliner) = ($x{'desc'} ? $x{'desc'} : (@classes == 1 ? "Click element" : "Click elements"));
+  my($outfiles_text) = join(', ', @outfiles);
+
   print OUT <<"EOD;";
 .\\" -*- mode: nroff -*-
 .\\" Generated by \`element2man.pl' from \`$filename'
 $prologue
-.TH "\U$classes\E" $section "$today" "Click"
+.TH "\U$outfiles_text\E" $outsections[0] "$today" "Click"
 .SH "NAME"
-$classes \- Click element$classes_plural
+$classes_text \- $oneliner
 EOD;
   
+  # prepare related
+  my(@related, @srelated, %related_source, %srelated_source);
+  if ($x{'a'}) {
+    foreach $i (map(split(/\s+/), @{$x{'a'}})) {
+      if ($i =~ /^(.*)\((.*)\)$/) {
+	push @related, $1;
+	$related_source{$1} = $2;
+      } else {
+	push @related, $i;
+	$related_source{$i} = 'n';
+      }
+    }
+  }
+  @srelated = sort { length($b) <=> length($a) } (@related, @classes);
+  %srelated_source = %related_source;
+  map(delete $srelated_source{$_}, @classes);
+
   if ($x{'c'}) {
     print OUT ".SH \"SYNOPSIS\"\n";
     while ($x{'c'} =~ /^\s*(\S.*)$/mg) {
-      print OUT nroffize($1, \@classes), "\n.br\n";
+      print OUT nroffize($1, \@srelated), "\n.br\n";
     }
   }
 
-  if (@classes == 1 && $processing{$classes}) {
-    my $p = process_processing($processing{$classes});
+  if (@classes == 1 && $processing{$classes[0]}) {
+    my $p = process_processing($processing{$classes[0]});
     if ($p) {
       print OUT ".SH \"PROCESSING TYPE\"\n";
       print OUT nroffize($p), "\n";
@@ -155,33 +198,19 @@ EOD;
     print OUT nroffize($x{'io'});
   }
 
-  my(@related, @srelated, %related_source);
-  if ($x{'a'}) {
-    foreach $i (map(split(/\s+/), @{$x{'a'}})) {
-      if ($i =~ /^(.*)\((.*)\)$/) {
-	push @related, $1;
-	$related_source{$1} = $2;
-      } else {
-	push @related, $i;
-	$related_source{$i} = 'n';
-      }
-    }
-    @srelated = sort { length($b) <=> length($a) } @related;
-  }
-
   if ($x{'d'}) {
     print OUT ".SH \"DESCRIPTION\"\n";
-    print OUT nroffize($x{'d'}, \@classes, \@srelated, \%related_source);
+    print OUT nroffize($x{'d'}, \@srelated, \%srelated_source);
   }
 
   if ($x{'n'}) {
     print OUT ".SH \"NOTES\"\n";
-    print OUT nroffize($x{'n'}, \@classes, \@srelated, \%related_source);
+    print OUT nroffize($x{'n'}, \@srelated, \%srelated_source);
   }
 
   if ($x{'e'}) {
     print OUT ".SH \"EXAMPLES\"\n";
-    print OUT nroffize($x{'e'}, \@classes, \@srelated, \%related_source);
+    print OUT nroffize($x{'e'}, \@srelated, \%srelated_source);
   }
 
   if ($x{'h'} && @{$x{'h'}}) {
@@ -206,12 +235,14 @@ EOD;
   # close output file & make links if appropriate
   if ($directory) {
     close OUT;
-    foreach $i (@classes[1..$#classes]) {
-      unlink("$directory/$i.$section");
-      if (link "$directory/$classes[0].$section", "$directory/$i.$section") {
-	push @all_created, $i;
+    for ($i = 1; $i < @outfiles; $i++) {
+      my($outname) = "$directory/$outfiles[$i].$outsections[$i]";
+      unlink($outname);
+      if (link $main_outname, $outname) {
+	push @all_outfiles, $outfiles[$i];
+	$all_outsections{$outfiles[$i]} = $outsections[$i];
       } else {
-	print STDERR "$directory/$i.$section: $!\n";
+	print STDERR "$outname: $!\n";
       }
     }
   }
@@ -323,13 +354,13 @@ This page lists all Click element classes that have manual page documentation.
 .SH "SEE ALSO"
 .nh
 EOD;
-  @all_created = sort @all_created;
-  my($last) = pop @all_created;
-  print OUT map(".M $_ n ,\n", @all_created);
-  print OUT ".M $last n\n.hy\n";
+  @all_outfiles = sort @all_outfiles;
+  my($last) = pop @all_outfiles;
+  print OUT map(".M $_ $all_outsections{$_} ,\n", @all_outfiles);
+  print OUT ".M $last $all_outsections{$last}\n.hy\n";
   close OUT if $directory;
 }
   
-if ($elementlist && @all_created) {
+if ($elementlist && @all_outfiles) {
   make_elementlist();
 }
diff --git a/elements/ip/ipprint.cc b/elements/ip/ipprint.cc
index 3a098cefa4e9ab49a6bd14dbab99ec01329921c4..a5410f776c0d58967c56ec16fd0951f30e95ab81 100644
--- a/elements/ip/ipprint.cc
+++ b/elements/ip/ipprint.cc
@@ -48,7 +48,7 @@ IPPrint::configure(const Vector<String> &conf, ErrorHandler* errh)
   if (cp_va_parse(conf, this, errh,
 		  cpString, "label", &_label,
 		  cpOptional,
-		  cpBool, "print packet contents in hex", &_hex,
+		  cpBool, "print packet contents in hex?", &_hex,
 		  cpInteger, "number of bytes to dump", &_bytes,
 		  cpEnd) < 0)
     return -1;
diff --git a/elements/linuxmodule/fromdevice.hh b/elements/linuxmodule/fromdevice.hh
index b4b0522e5afffe5ed0e09d6254f28fc5b66a6ca4..3f82fb4fdc2beecc7b12377ba70e384f52d02e16 100644
--- a/elements/linuxmodule/fromdevice.hh
+++ b/elements/linuxmodule/fromdevice.hh
@@ -5,6 +5,11 @@
  * =c
  * FromDevice(DEVNAME)
  * =d
+ *
+ * This manual page describes the Linux kernel module version of the
+ * FromDevice element. For the user-level element, read the FromDevice.u
+ * manual page.
+ *
  * Intercepts all packets received by the Linux network interface
  * named DEVNAME and pushes them out output 0.
  * The packets include the link-level header.
@@ -17,12 +22,11 @@
  * This is bad for performance. If you care about performance and have a
  * polling-capable device, use PollDevice instead.
  *
- * This element is only available inside the kernel module.
- *
  * =a PollDevice
  * =a ToDevice
  * =a FromLinux
- * =a ToLinux */
+ * =a ToLinux
+ * =a FromDevice.u */
 
 #include "anydevice.hh"
 
diff --git a/elements/linuxmodule/todevice.hh b/elements/linuxmodule/todevice.hh
index 10fad5a1682800e1cc1556b186130f9d6a948247..1091f76b1e5da3e52903a7ac6682501f2cd22c76 100644
--- a/elements/linuxmodule/todevice.hh
+++ b/elements/linuxmodule/todevice.hh
@@ -5,6 +5,10 @@
  * =c
  * ToDevice(DEVNAME)
  * =d
+ *
+ * This manual page describes the Linux kernel module version of the ToDevice
+ * element. For the user-level element, read the ToDevice.u(n) manual page.
+ *
  * Sends packets out the Linux network interface named DEVNAME.
  *
  * Packets must have a link header. For ethernet, ToDevice
@@ -21,13 +25,11 @@
  * we depend on the net driver's send operation for synchronization (e.g.
  * tulip send operation uses a bit lock).
  *
- * This element is only available inside the kernel module.
- *
  * =a FromDevice
  * =a PollDevice
  * =a FromLinux
  * =a ToLinux
- */
+ * =a ToDevice.u */
 
 #include "anydevice.hh"
 
diff --git a/elements/userlevel/fromdevice.cc b/elements/userlevel/fromdevice.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1d6510e3a2cfd066c422fdb768dfb724791a0b5e
--- /dev/null
+++ b/elements/userlevel/fromdevice.cc
@@ -0,0 +1,281 @@
+/*
+ * fromdevice.{cc,hh} -- element reads packets live from network via pcap
+ * Douglas S. J. DeCouto, Eddie Kohler, John Jannotti
+ *
+ * Copyright (c) 1999-2000 Massachusetts Institute of Technology.
+ *
+ * This software is being provided by the copyright holders under the GNU
+ * General Public License, either version 2 or, at your discretion, any later
+ * version. For more information, see the `COPYRIGHT' file in the source
+ * distribution.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "fromdevice.hh"
+#include "todevice.hh"
+#include "error.hh"
+#include "packet.hh"
+#include "confparse.hh"
+#include "elements/standard/scheduleinfo.hh"
+#include "glue.hh"
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#if FROMDEVICE_LINUX
+# include <sys/socket.h>
+# include <sys/ioctl.h>
+# include <net/if.h>
+# include <net/if_packet.h>
+# include <features.h>
+# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
+#  include <netpacket/packet.h>
+#  include <net/ethernet.h>
+# else
+#  include <linux/if_packet.h>
+#  include <linux/if_ether.h>
+# endif
+#endif
+
+FromDevice::FromDevice()
+  : _promisc(0), _packetbuf_size(0)
+{
+  add_output();
+#if FROMDEVICE_PCAP
+  _pcap = 0;
+#endif
+#if FROMDEVICE_LINUX
+  _fd = -1;
+  _packetbuf = 0;
+#endif
+}
+
+FromDevice::~FromDevice()
+{
+  uninitialize();
+}
+
+FromDevice *
+FromDevice::clone() const
+{
+  return new FromDevice;
+}
+
+int
+FromDevice::configure(const Vector<String> &conf, ErrorHandler *errh)
+{
+  bool promisc = false;
+  _packetbuf_size = 2048;
+  if (cp_va_parse(conf, this, errh,
+		  cpString, "interface name", &_ifname,
+		  cpOptional,
+		  cpBool, "be promiscuous?", &promisc,
+#if FROMDEVICE_LINUX
+		  cpUnsigned, "maximum packet length", &_packetbuf_size,
+#endif
+		  cpEnd) < 0)
+    return -1;
+  if (_packetbuf_size > 8192 || _packetbuf_size < 128)
+    return errh->error("maximum packet length out of range");
+  _promisc = promisc;
+  return 0;
+}
+
+#if FROMDEVICE_LINUX
+int
+FromDevice::open_packet_socket(String ifname, ErrorHandler *errh)
+{
+  int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+  if (fd == -1)
+    return errh->error("%s: socket: %s", ifname.cc(), strerror(errno));
+
+  // get interface index
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifname.cc(), sizeof(ifr.ifr_name));
+  int res = ioctl(fd, SIOCGIFINDEX, &ifr);
+  if (res != 0) {
+    close(fd);
+    return errh->error("%s: SIOCGIFINDEX: %s", ifname.cc(), strerror(errno));
+  }
+  int ifindex = ifr.ifr_ifindex;
+
+  // bind to the specified interface.  from packet man page, only
+  // sll_protocol and sll_ifindex fields are used; also have to set
+  // sll_family
+  sockaddr_ll sa;
+  memset(&sa, 0, sizeof(sa));
+  sa.sll_family = AF_PACKET;
+  sa.sll_protocol = htons(ETH_P_ALL);
+  sa.sll_ifindex = ifindex;
+  res = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+  if (res != 0) {
+    close(fd);
+    return errh->error("%s: bind: %s", ifname.cc(), strerror(errno));
+  }
+
+  // nonblocking I/O on the packet socket so we can poll
+  fcntl(fd, F_SETFL, O_NONBLOCK);
+  
+  return fd;
+}
+
+int
+FromDevice::set_promiscuous(int fd, String ifname, bool promisc)
+{
+  // get interface flags
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifname.cc(), sizeof(ifr.ifr_name));
+  int res = ioctl(fd, SIOCGIFFLAGS, &ifr);
+  if (res != 0)
+    return -2;
+  int was_promisc = (ifr.ifr_flags & IFF_PROMISC ? 1 : 0);
+
+  // set or reset promiscuous flag
+  if (was_promisc != promisc) {
+    ifr.ifr_flags = (promisc ? ifr.ifr_flags | IFF_PROMISC : ifr.ifr_flags & ~IFF_PROMISC);
+    res = ioctl(fd, SIOCSIFFLAGS, &ifr);
+    if (res != 0)
+      return -3;
+  }
+
+  return was_promisc;
+}
+#endif
+
+int
+FromDevice::initialize(ErrorHandler *errh)
+{
+  if (!_ifname)
+    return errh->error("interface not set");
+  
+  /* 
+   * Later versions of pcap distributed with linux (e.g. the redhat
+   * linux pcap-0.4-16) want to have a filter installed before they
+   * will pick up any packets.
+   */
+
+#if FROMDEVICE_PCAP
+  
+  assert(!_pcap);
+  char *ifname = _ifname.mutable_c_str();
+  char ebuf[PCAP_ERRBUF_SIZE];
+  _pcap = pcap_open_live(ifname,
+                         12000, /* XXX snaplen */
+                         _promisc,
+                         1,     /* timeout: don't wait for packets */
+                         ebuf);
+  if (!_pcap)
+    return errh->error("%s: %s", ifname, ebuf);
+
+  // nonblocking I/O on the packet socket so we can poll
+  int fd = pcap_fileno(_pcap);
+  fcntl(fd, F_SETFL, O_NONBLOCK);
+
+  bpf_u_int32 netmask;
+  bpf_u_int32 localnet;
+  if (pcap_lookupnet(ifname, &localnet, &netmask, ebuf) < 0) {
+    errh->warning("%s: %s", ifname, ebuf);
+  }
+  
+  struct bpf_program fcode;
+  /* 
+   * assume we can use 0 pointer for program string and get ``empty''
+   * filter program.  
+   */
+  if (pcap_compile(_pcap, &fcode, 0, 0, netmask) < 0) {
+    return errh->error("%s: %s", ifname, pcap_geterr(_pcap));
+  }
+
+  if (pcap_setfilter(_pcap, &fcode) < 0) {
+    return errh->error("%s: %s", ifname, pcap_geterr(_pcap));
+  }
+
+#elif FROMDEVICE_LINUX
+
+  _fd = open_packet_socket(_ifname, errh);
+  if (_fd < 0) return -1;
+
+  int promisc_ok = set_promiscuous(_fd, _ifname, _promisc);
+  if (promisc_ok < 0) {
+    if (_promisc)
+      errh->warning("cannot set promiscuous mode");
+    _was_promisc = -1;
+  } else
+    _was_promisc = promisc_ok;
+
+  // create packet buffer
+  _packetbuf = new unsigned char[_packetbuf_size];
+  if (!_packetbuf) {
+    close(_fd);
+    return errh->error("out of memory");
+  }
+  
+#else
+
+  return errh->error("FromDevice is not supported on this platform");
+  
+#endif
+
+  ScheduleInfo::join_scheduler(this, errh);
+  return 0;
+}
+
+void
+FromDevice::uninitialize()
+{
+#if FROMDEVICE_PCAP
+  if (_pcap)
+    pcap_close(_pcap);
+  _pcap = 0;
+#endif
+#if FROMDEVICE_LINUX
+  if (_was_promisc >= 0)
+    set_promiscuous(_fd, _ifname, _was_promisc);
+  if (_fd >= 0)
+    close(_fd);
+  _fd = -1;
+  delete[] _packetbuf;
+  _packetbuf = 0;
+#endif
+}
+
+#if FROMDEVICE_PCAP
+void
+FromDevice::get_packet(u_char* clientdata,
+		       const struct pcap_pkthdr* pkthdr,
+		       const u_char* data)
+{
+  FromDevice *fd = (FromDevice *) clientdata;
+  int length = pkthdr->caplen;
+  Packet* p = Packet::make(data, length);
+  fd->output(0).push(p);
+}
+#endif
+
+void
+FromDevice::run_scheduled()
+{
+#if FROMDEVICE_PCAP
+  // Read and push() at most one packet.
+  pcap_dispatch(_pcap, 1, FromDevice::get_packet, (u_char *) this);
+#endif
+#if FROMDEVICE_LINUX
+  struct sockaddr_ll sa;
+  //memset(&sa, 0, sizeof(sa));
+  socklen_t fromlen = sizeof(sa);
+  int len = recvfrom(_fd, _packetbuf, _packetbuf_size, 0,
+		     (sockaddr *)&sa, &fromlen);
+  if (len > 0) {
+    if (sa.sll_pkttype != PACKET_OUTGOING)
+      output(0).push(Packet::make(_packetbuf, len));
+  } else if (errno != EAGAIN)
+    click_chatter("FromDevice(%s): recvfrom: %s", _ifname.cc(), strerror(errno));
+#endif
+  reschedule();
+}
+
+EXPORT_ELEMENT(FromDevice)
diff --git a/elements/userlevel/fromdevice.hh b/elements/userlevel/fromdevice.hh
new file mode 100644
index 0000000000000000000000000000000000000000..e5c5ef72535232ed9bf3bef93dda22c3a337c41e
--- /dev/null
+++ b/elements/userlevel/fromdevice.hh
@@ -0,0 +1,100 @@
+#ifndef FROMDEVICE_HH
+#define FROMDEVICE_HH
+
+/*
+ * =page FromDevice.u
+ * =c
+ * FromDevice(DEVNAME [, PROMISC? [, MAXPACKETSIZE]])
+ * =d
+ *
+ * This manual page describes the user-level version of the FromDevice
+ * element. For the Linux kernel module element, read the FromDevice(n) manual
+ * page.
+ *
+ * Reads packets from the kernel that were received on the network controller
+ * named DEVNAME. Puts the device in promiscuous mode if PROMISC? (a Boolean)
+ * is true. PROMISC? defaults to false. On some systems, packets larger than
+ * MAXPACKETSIZE will be truncated; default MAXPACKETSIZE is 2048 bytes.
+ *
+ * The kernel networking code sees all of the packets that FromDevice
+ * produces; be careful that at most one of Click and the kernel forwards each
+ * packet.
+ *
+ * Under Linux, a FromDevice element will not receive packets sent by a
+ * ToDevice element for the same device. Under other operating systems, your
+ * mileage may vary.
+ *
+ * =e
+ * = FromDevice(eth0, 0) -> ...
+ *
+ * =a ToDevice.u
+ * =a FromDump
+ * =a ToDump
+ * =a FromDevice */
+
+#include "element.hh"
+
+#ifdef __linux__
+# define FROMDEVICE_LINUX 1
+#elif defined(HAVE_PCAP)
+# define FROMDEVICE_PCAP 1
+extern "C" {
+# include <pcap.h>
+}
+#endif
+
+class FromDevice : public Element {
+
+  String _ifname;
+  bool _promisc : 1;
+  int _was_promisc : 2;
+  int _packetbuf_size;
+
+#if FROMDEVICE_LINUX
+  int _fd;
+  unsigned char *_packetbuf;
+#endif
+#if FROMDEVICE_PCAP
+  pcap_t* _pcap;
+  static void get_packet(u_char *, const struct pcap_pkthdr *,
+			 const u_char *);
+  int do_select(int waitms);
+#endif
+
+ public:
+
+  enum ConfigurePhase {
+    CONFIGURE_PHASE_FROMDEVICE = CONFIGURE_PHASE_DEFAULT,
+    CONFIGURE_PHASE_TODEVICE = CONFIGURE_PHASE_FROMDEVICE + 1
+  };
+  
+  FromDevice();
+  ~FromDevice();
+  
+  const char *class_name() const	{ return "FromDevice"; }
+  const char *processing() const	{ return PUSH; }
+  
+  FromDevice *clone() const;
+  int configure_phase() const		{ return CONFIGURE_PHASE_FROMDEVICE; }
+  int configure(const Vector<String> &, ErrorHandler *);
+  int initialize(ErrorHandler *);
+  void uninitialize();
+  
+  String ifname() const			{ return _ifname; }
+#if FROMDEVICE_PCAP
+  pcap_t *pcap() const			{ return _pcap; }  
+#endif
+#if FROMDEVICE_LINUX
+  int fd() const			{ return _fd; }
+#endif
+
+  void run_scheduled();
+
+#if FROMDEVICE_LINUX
+  static int open_packet_socket(String, ErrorHandler *);
+  static int set_promiscuous(int, String, bool);
+#endif
+  
+};
+
+#endif
diff --git a/elements/userlevel/todevice.cc b/elements/userlevel/todevice.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d80d75c0283170f3df62482966a62008f295fad4
--- /dev/null
+++ b/elements/userlevel/todevice.cc
@@ -0,0 +1,182 @@
+/*
+ * todevice.{cc,hh} -- element writes packets to network via pcap library
+ * Douglas S. J. DeCouto, Eddie Kohler, John Jannotti
+ *
+ * Copyright (c) 1999-2000 Massachusetts Institute of Technology.
+ *
+ * This software is being provided by the copyright holders under the GNU
+ * General Public License, either version 2 or, at your discretion, any later
+ * version. For more information, see the `COPYRIGHT' file in the source
+ * distribution.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "todevice.hh"
+#include "error.hh"
+#include "etheraddress.hh"
+#include "confparse.hh"
+#include "router.hh"
+#include "elements/standard/scheduleinfo.hh"
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if TODEVICE_BSD_DEV_BPF
+# include <fcntl.h>
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/ioctl.h>
+# include <net/if.h>
+#elif TODEVICE_LINUX
+# include <sys/socket.h>
+# include <sys/ioctl.h>
+# include <net/if.h>
+# include <net/if_packet.h>
+# include <features.h>
+# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
+#  include <netpacket/packet.h>
+# else
+#  include <linux/if_packet.h>
+# endif
+#endif
+
+ToDevice::ToDevice()
+  : Element(1, 0), _fd(-1), _my_fd(false)
+{
+#if TODEVICE_BSD_DEV_BPF
+  _pcap = 0;
+#endif
+}
+
+ToDevice::~ToDevice()
+{
+  uninitialize();
+}
+
+ToDevice *
+ToDevice::clone() const
+{
+  return new ToDevice;
+}
+
+int
+ToDevice::configure(const Vector<String> &conf, ErrorHandler *errh)
+{
+  if (cp_va_parse(conf, this, errh,
+		  cpString, "interface name", &_ifname,
+		  0) < 0)
+    return -1;
+  if (!_ifname)
+    return errh->error("interface not set");
+  return 0;
+}
+
+int
+ToDevice::initialize(ErrorHandler *errh)
+{
+  _fd = -1;
+  
+#if TODEVICE_BSD_DEV_BPF
+  
+  /* pcap_open_live() doesn't open for writing. */
+  for (int i = 0; i < 16 && _fd < 0; i++) {
+    char tmp[64];
+    sprintf(tmp, "/dev/bpf%d", i);
+    _fd = open(tmp, 1);
+  }
+  if (_fd < 0)
+    return(errh->error("can't open a bpf"));
+
+  struct ifreq ifr;
+  strncpy(ifr.ifr_name, _ifname, sizeof(ifr.ifr_name));
+  ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0;
+  if (ioctl(_fd, BIOCSETIF, (caddr_t)&ifr) < 0)
+    return errh->error("BIOCSETIF %s failed", ifr.ifr_name);
+
+#elif TODEVICE_LINUX
+  
+  // find a FromDevice and reuse its socket if possible
+  for (int ei = 0; ei < router()->nelements() && _fd < 0; ei++) {
+    Element *e = router()->element(ei);
+    ToDevice *td = (ToDevice *)e->cast("FromDevice");
+    if (td && td->ifname() == _ifname && td->fd() >= 0) {
+      _fd = td->fd();
+      _my_fd = false;
+    }
+  }
+
+  if (_fd < 0) {
+    _fd = FromDevice::open_packet_socket(_ifname, errh);
+    _my_fd = true;
+  }
+
+  if (_fd < 0) return -1;
+
+#else
+  
+  return errh->error("ToDevice is not supported on this platform");
+  
+#endif
+
+  if (input_is_pull(0))
+    ScheduleInfo::join_scheduler(this, errh);
+  return 0;
+}
+
+void
+ToDevice::uninitialize()
+{
+#if TODEVICE_BSD_DEV_BPF
+  if (_pcap) pcap_close(_pcap);
+  _pcap = 0;
+#endif
+#if TODEVICE_LINUX
+  if (_fd >= 0 && _my_fd) close(_fd);
+  _fd = -1;
+#endif
+  unschedule();
+}
+
+void
+ToDevice::send_packet(Packet *p)
+{
+  int retval;
+  const char *syscall;
+
+#if TODEVICE_WRITE
+  retval = (write(_fd, p->data(), p->length()) > 0 ? 0 : -1);
+  syscall = "write";
+#elif TODEVICE_SEND
+  retval = send(_fd, p->data(), p->length(), 0);
+  syscall = "send";
+#else
+  retval = 0;
+#endif
+
+  if (retval < 0)
+    click_chatter("ToDevice(%d) %s: %s", _ifname.cc(), syscall, strerror(errno));
+  p->kill();
+}
+
+void
+ToDevice::push(int, Packet *p)
+{
+  assert(p->length() >= 14);
+  send_packet(p);
+}
+
+void
+ToDevice::run_scheduled()
+{
+  // XXX reduce tickets when idle
+  if (Packet *p = input(0).pull())
+    send_packet(p); 
+  reschedule();
+}
+
+ELEMENT_REQUIRES(FromDevice)
+EXPORT_ELEMENT(ToDevice)
diff --git a/elements/userlevel/todevice.hh b/elements/userlevel/todevice.hh
new file mode 100644
index 0000000000000000000000000000000000000000..3f0b6a8806a6da7f7a27f25aaa7e9487ce7b1da3
--- /dev/null
+++ b/elements/userlevel/todevice.hh
@@ -0,0 +1,93 @@
+#ifndef TODEVICE_HH
+#define TODEVICE_HH
+#include "element.hh"
+#include "string.hh"
+#include "elements/userlevel/fromdevice.hh"
+
+/*
+ * =page ToDevice.u
+ * =c
+ * ToDevice(DEVNAME)
+ * =d
+ *
+ * This manual page describes the user-level version of the ToDevice element.
+ * For the Linux kernel module element, read the ToDevice(n) manual page.
+ *
+ * Pulls packets and sends them out the named device using
+ * Berkeley Packet Filters (or Linux equivalent).
+ *
+ * Packets sent via ToDevice should already have a link-level
+ * header prepended. This means that ARP processing,
+ * for example, must already have been done.
+ *
+ * Under Linux, a FromDevice element will not receive packets sent by a
+ * ToDevice element for the same device. Under other operating systems, your
+ * mileage may vary.
+ *
+ * This element is only available at user level.
+ * 
+ * =a FromDevice.u
+ * =a FromDump
+ * =a ToDump
+ * =a ToDevice */
+
+#ifdef HAVE_PCAP
+extern "C" {
+# include <pcap.h>
+}
+#else
+# include "fakepcap.h"
+#endif
+
+#ifdef HAVE_PCAP
+# if defined(__FreeBSD__) || defined(__OpenBSD__)
+#  define TODEVICE_BSD_DEV_BPF 1
+#  define TODEVICE_WRITE 1
+# endif
+#endif
+#if defined(__linux__)
+# define TODEVICE_LINUX 1
+# define TODEVICE_SEND 1
+#endif
+
+/*
+ * Write packets to the ethernet via the bpf.
+ * Expects packets that already have an ether header.
+ * Can push or pull.
+ */
+
+class ToDevice : public Element {
+
+  String _ifname;
+  int _fd;
+  bool _my_fd;
+  
+#if TODEVICE_BSD_DEV_BPF
+  pcap_t *_pcap;
+#endif
+
+  void send_packet(Packet *);
+  
+ public:
+  
+  ToDevice();
+  ~ToDevice();
+  
+  const char *class_name() const		{ return "ToDevice"; }
+  const char *processing() const		{ return AGNOSTIC; }
+  
+  ToDevice *clone() const;
+  int configure_phase() const { return FromDevice::CONFIGURE_PHASE_TODEVICE; }
+  int configure(const Vector<String> &, ErrorHandler *);
+  int initialize(ErrorHandler *);
+  void uninitialize();
+
+  String ifname() const				{ return _ifname; }
+  int fd() const				{ return _fd; }
+
+  void push(int port, Packet *);
+  void run_scheduled();
+  
+};
+
+#endif
diff --git a/lib/router.cc b/lib/router.cc
index ad7398e3878c4dffb9de5c5dba4e3a91b5155fab..44396040e277ac446aa5b850252e344b283088ef 100644
--- a/lib/router.cc
+++ b/lib/router.cc
@@ -813,7 +813,7 @@ Router::initialize(ErrorHandler *errh)
   // If there were errors, uninitialize any elements that we initialized
   // successfully and return -1 (error). Otherwise, we're all set!
   if (!all_ok) {
-    errh->error("router could not be initialized");
+    errh->error("Router could not be initialized!");
     for (int i = 0; i < _elements.size(); i++)
       if (element_ok[i])
 	_elements[i]->uninitialize();