Initial check-in (somewhat belated).
authormdw <mdw>
Thu, 25 Jan 2001 22:03:40 +0000 (22:03 +0000)
committermdw <mdw>
Thu, 25 Jan 2001 22:03:40 +0000 (22:03 +0000)
15 files changed:
.cvsignore [new file with mode: 0644]
.links [new file with mode: 0644]
.skelrc [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
configure.in [new file with mode: 0644]
makedev.unet.in [new file with mode: 0755]
setup [new file with mode: 0755]
tests/bidi [new file with mode: 0755]
tests/packets [new file with mode: 0755]
tests/vpn.ssh [new file with mode: 0755]
unet.c [new file with mode: 0644]
unet.h [new file with mode: 0644]
unet.texi [new file with mode: 0644]
unetcfg.c [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..20a417e
--- /dev/null
@@ -0,0 +1,7 @@
+Makefile.in
+aclocal.m4
+build
+configure
+stamp-h.in
+unet.info
+unetconf.h.in
diff --git a/.links b/.links
new file mode 100644 (file)
index 0000000..2c77e58
--- /dev/null
+++ b/.links
@@ -0,0 +1,9 @@
+COPYING
+config.guess
+config.sub
+gpl.texi
+install-sh
+missing
+mkinstalldirs
+texinfo.tex
+texinice.tex
diff --git a/.skelrc b/.skelrc
new file mode 100644 (file)
index 0000000..1275dad
--- /dev/null
+++ b/.skelrc
@@ -0,0 +1,8 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((author . "Mark Wooding")
+        (full-title . "Usernet")
+        (program . "Usernet"))
+       skel-alist))
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..ea82408
--- /dev/null
@@ -0,0 +1,49 @@
+## -*-makefile-*-
+##
+## $Id: Makefile.am,v 1.1 2001/01/25 22:03:39 mdw Exp $
+##
+## Skeleton makefile for usernet
+##
+## (c) 1998 Mark Wooding
+##
+
+##----- Licensing notice ----------------------------------------------------
+##
+## 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.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software Foundation,
+## Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+## --- Boilerplate ---
+
+AUTOMAKE_OPTIONS = foreign
+
+## --- Strangeness for building modules ---
+
+moduledir = @moduledir@
+linuxdir = @linuxdir@
+
+## --- Important things to build ---
+
+module_DATA = unet.o
+sbin_PROGRAMS = unetcfg
+sbin_SCRIPTS = makedev.unet
+include_HEADERS = unet.h
+info_TEXINFOS = unet.texi
+EXTRA_DIST = unet.c
+
+## --- Building the kernel modules ---
+
+unet_INCLUDES = -D__KERNEL__ -DMODULE -I$(linuxdir)/include
+
+unet.o: unet.c
+       $(COMPILE) $(unet_INCLUDES) -c $(srcdir)/unet.c
diff --git a/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..0b164f4
--- /dev/null
@@ -0,0 +1,84 @@
+/* -*-c-*-
+ *
+ * $Id: acconfig.h,v 1.1 2001/01/25 22:03:39 mdw Exp $
+ *
+ * Configuration data for Usernet
+ *
+ * (c) 1998 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: acconfig.h,v $
+ * Revision 1.1  2001/01/25 22:03:39  mdw
+ * Initial check-in (somewhat belated).
+ *
+ */
+
+#ifndef UNETCONF_H
+#define UNETCONF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Configuration data ------------------------------------------------*/
+@TOP@
+
+/* --- Package name and version number --- */
+
+#define PACKAGE "usernet"
+/* PACKAGE -- package name string */
+
+#define VERSION "0.0"
+/* VERSION -- package version number */
+
+/* --- Device numbers --- */
+
+#define UNET_MAJOR 120
+/* UNET_MAJOR -- major device for /dev/unet and friends */
+
+#define UNET_NPERSIST 1
+/* UNET_NPERSIST -- number of persistent devices and interfaces */
+
+#define UNET_TRANSMINOR 256
+/* UNET_TRANSMINOR -- minor device number for create-a-transient device */
+
+/* --- Other tweaking things --- */
+
+#define UNET_QMAXLEN 64
+/* UNET_QMAXLEN -- maximum number of packets waiting for collection */
+
+#define UNET_MAXIF 32
+/* UNET_MAX -- maximum number of unets to allow */
+
+#undef UNET_DEBUG
+/* UNET_DEBUG -- 1 for debugging on permanently, -1 for no debugging,
+ * or 0 for debugging switchable at runtime */
+
+@BOTTOM@
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..3932174
--- /dev/null
@@ -0,0 +1,155 @@
+dnl -*-fundamental-*-
+dnl
+dnl $Id: configure.in,v 1.1 2001/01/25 22:03:39 mdw Exp $
+dnl
+dnl Configuration script for usernet
+dnl
+dnl (c) 1998 Mark Wooding
+dnl
+
+dnl----- Licensing notice ---------------------------------------------------
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software Foundation,
+dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+dnl --- Boring header things ---
+
+AC_INIT(unet.c)
+AM_INIT_AUTOMAKE(usernet, 1.1)
+AM_CONFIG_HEADER(unetconf.h)
+AC_PROG_CC
+AC_CANONICAL_HOST
+
+dnl --- Make sure we recognise the environment ---
+
+case $host in
+  *-linux*)  ;;
+  *)
+    AC_MSG_ERROR([It would help a lot if you compiled under Linux.])
+    ;;
+esac
+
+kernelversion=`uname -r`
+
+dnl --- Find the Linux kernel sources ---
+
+AC_ARG_WITH([linux-source],
+[  --with-linux-source=DIR directory containing Linux kernel source],
+[linuxdir="$withval"],
+[AC_CACHE_CHECK([where the Linux kernel source is],
+[mdw_cv_linux_source],
+[for i in /usr/src/linux /usr/src/linux-$kernelversion; do
+  if test -f $i/kernel/ksyms.c; then
+    mdw_cv_linux_source=$i
+    break
+  fi
+done
+if test -z "$mdw_cv_linux_source"; then
+  AC_MSG_ERROR([Failed to find the Linux source.  Where is it?])
+fi])
+linuxdir="$mdw_cv_linux_source"])
+AC_SUBST(linuxdir)
+
+dnl --- Find Perl ---
+
+mdw_PROG_PERL(5.003)
+mdw_CHECK_PERL(5.003)
+
+dnl --- Play with GCC command line arguments ---
+
+mdw_GCC_FLAGS([-Wall -fomit-frame-pointer -fno-strength-reduce])
+
+NCFLAGS=""
+for i in $CFLAGS; do
+  case $i in
+    -g) ;;
+    *) NCFLAGS="$NCFLAGS $i"
+  esac
+done
+CFLAGS="$NCFLAGS"
+
+dnl --- Decide where to put the module ---
+
+AC_ARG_WITH([module-dir],
+[  --with-module-dir=DIR   directory to install the module in],
+[moduledir="$withval"],
+[AC_CACHE_CHECK([for a good place to store kernel modules],
+[mdw_cv_module_dir],
+[for i in /lib/modules/misc /lib/modules/$kernelversion; do
+  if test -d $i; then
+    mdw_cv_module_dir=$i
+    break
+  fi
+done])
+if test -z "$mdw_cv_module_dir"; then
+  mdw_cv_module_dir="/lib/modules/misc"
+fi
+moduledir=$mdw_cv_module_dir])
+AC_SUBST(moduledir)
+
+dnl --- Tweakable parameters ---
+
+AC_ARG_WITH([major-device],
+[  --with-major-device=NUM set major device number for Usernet],
+[MAJORDEV="$WITHVAL"],
+[MAJORDEV=63])
+AC_SUBST(MAJORDEV)
+AC_DEFINE_UNQUOTED(UNET_MAJOR, $MAJORDEV)
+
+AC_ARG_WITH([persistent-devices],
+[  --with-persistent-devices=NUM
+                          create NUM persistent devices],
+[NPERSIST="$withval"],
+[NPERSIST=1])
+AC_SUBST(NPERSIST)
+AC_DEFINE_UNQUOTED(UNET_NPERSIST, $NPERSIST)
+
+AC_ARG_WITH([transient-minor],
+[  --with-transient-minor=NUM
+                          set minor device number of /dev/unet],
+[TRANSMINOR="$withval"],
+[TRANSMINOR=255])
+AC_SUBST(TRANSMINOR)
+AC_DEFINE_UNQUOTED(UNET_TRANSMINOR, $TRANSMINOR)
+
+AC_ARG_WITH([max-queue-length],
+[  --with-max-queue-length=NUM
+                          queue at most NUM packets],
+[QMAXLEN="$withval"],
+[QMAXLEN=128])
+AC_SUBST(QMAXLEN)
+AC_DEFINE_UNQUOTED(UNET_QMAXLEN, $QMAXLEN)
+
+AC_ARG_WITH([max-interfaces],
+[  --with-max-interfaces=NUM
+                          maximum number of interfaces allowed],
+[MAXIF="$withval"],
+[MAXIF=64])
+AC_SUBST(MAXIF)
+AC_DEFINE_UNQUOTED(UNET_MAXIF, $MAXIF)
+
+AC_ARG_WITH([debugging],
+[  --with-debugging=OPT    enable debugging output from the module
+                          (options are "yes", "no" and "runtime")],
+[case "$withval" in
+  yes)         AC_DEFINE(UNET_DEBUG, 1) ;;
+  no)          AC_DEFINE(UNET_DEBUG, -1) ;;
+  runtime)     AC_DEFINE(UNET_DEBUG, 0) ;;
+  *)           AC_MSG_ERROR([bad argument to --with-debugging]) ;;
+esac],
+[AC_DEFINE(UNET_DEBUG, 0)])
+
+dnl --- Should be enough for today ---
+
+AC_OUTPUT(Makefile makedev.unet)
diff --git a/makedev.unet.in b/makedev.unet.in
new file mode 100755 (executable)
index 0000000..ae0fea3
--- /dev/null
@@ -0,0 +1,140 @@
+#! /bin/sh
+#
+# $Id: makedev.unet.in,v 1.1 2001/01/25 22:03:39 mdw Exp $
+#
+# Make usernet devices
+#
+# (c) 1998 Mark Wooding
+#
+
+#----- Licensing notice -----------------------------------------------------
+#
+# 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#----- Revision history -----------------------------------------------------
+#
+# $Id: makedev.unet.in,v 1.1 2001/01/25 22:03:39 mdw Exp $
+
+# --- Configuration stuff ---
+
+unet_major=@MAJORDEV@
+unet_transMinor=@TRANSMINOR@
+unet_persistent=@NPERSIST@
+unet_mode=600
+
+# --- Sanity check ---
+
+case `uname -s` in
+  [Ll]inux) ;;
+  *)
+    echo >&2 "$0: this program is Linux-specific" ;;
+esac
+
+# --- Sort out command line arguments ---
+
+while [ $# -gt 0 ]; do
+  opt="$1"; shift;
+  case "$opt" in
+
+    # --- Help requests ---
+
+    -h|-he|-hel|-help | --h|--he|--hel|--help)
+      cat <<EOF
+makedev.unet [OPTIONS...]
+
+Constructs usernet device nodes.
+
+The usernet device allows userspace processes to process and distribute
+network packets.  See the Usernet manual for more details.
+
+Options provided:
+
+    --help             Display this help text.
+-p, --persistent=NUM   Configure NUM persistent unet devices.
+-t, --transient=NUM    Use minor device NUM for transient unets.
+-M, --major=NUM                Use major device NUM instead of $unet_major.
+EOF
+      exit
+      ;;
+
+    # --- Set number of persistent devices ---
+
+    -p | --p|--pe|--per|--pers|--persi|--persis|--persist|\
+    --persiste|--persisten|--persistent)
+      unet_persistent="$1"; shift ;;
+
+    -p*)
+      unet_persistent="`echo "$opt" | cut -b 3-`" ;;
+
+    --p=*|--pe=*|--per=*|--pers=*|--persi=*|--persis=*|--persist=*|\
+    --persiste=*|--persisten=*|--persistent=*)
+      unet_persistent="`echo "$opt" | sed -e "s/^[-a-z]*=//"`" ;;
+
+    # --- Set transient minor device ---
+
+    -t | --t|--tr|--tra|--tran|--trans|--transi|--transie|\
+    --transien|--transient)
+      unet_transMinor="$1"; shift ;;
+
+    -t*)
+      unet_transMinor="`echo "$opt" | cut -b 3-`" ;;
+
+    --t=*|--tr=*|--tra=*|--tran=*|--trans=*|--transi=*|--transie=*|\
+    --transien=*|--transient=*)
+      unet_transMinor="`echo "$opt" | sed -e "s/^[-a-z]*=//"`" ;;
+
+    # --- Set major number (caution!) ---
+
+    -M | --ma|--maj|--majo|--major)
+      unet_major="$1"; shift ;;
+
+    -M*)
+      unet_major="`echo "$opt" | cut -b 3-`" ;;
+
+    --ma=*|--maj=*|--majo=*|--major=*)
+      unet_major="`echo "$opt" | sed -e "s/^[-a-z]*=//"`" ;;
+
+    # --- Set default mode ---
+
+    -m | --m|--mo|--mod|--mode)
+      unet_mode="$1"; shift ;;
+
+    -m*)
+      unet_mode="`echo "$opt" | cut -b 3-`" ;;
+
+    --m=*|--mo=*|--mod=*|--mode=*)
+      unet_mode="`echo "$opt" | sed -e "s/^[-a-z]*=//"`" ;;
+
+    # --- Unknown option ---
+
+    *)
+      echo >&2 "$0: unknown option $opt"; exit 1 ;;
+
+  esac
+done
+
+# --- Do the stuff ---
+
+rm -f /dev/unet*
+if [ "$unet_persistent" -gt 0 ]; then
+  i=0
+  while [ "$i" -lt "$unet_persistent" ]; do
+    mknod -m "$unet_mode" /dev/unet$i c "$unet_major" $i
+    i=`expr $i + 1`
+  done
+fi
+mknod -m "$unet_mode" /dev/unet c "$unet_major" "$unet_transMinor"
+
+exit 0
diff --git a/setup b/setup
new file mode 100755 (executable)
index 0000000..5173638
--- /dev/null
+++ b/setup
@@ -0,0 +1,9 @@
+#! /bin/sh
+
+set -e
+mklinks
+mkaclocal
+autoheader
+autoconf
+automake
+mkdir build
diff --git a/tests/bidi b/tests/bidi
new file mode 100755 (executable)
index 0000000..5ea39de
--- /dev/null
@@ -0,0 +1,57 @@
+#! /bin/perl
+
+open LEFT, "+</dev/unet0" or die "open /dev/unet0: $!";
+open RIGHT, "+</dev/unet1" or die "open /dev/unet1: $!";
+
+$rd = 0;
+vec($rd, fileno(LEFT), 1) = 1;
+vec($rd, fileno(RIGHT), 1) = 1;
+
+for (;;) {
+  select($ord = $rd, undef, undef, undef);
+  if (vec($ord, fileno(LEFT), 1)) {
+    defined sysread(LEFT, $buf, 65536) or die "read(/dev/unet0): $!";
+    print "read packet from unet0\n";
+    hexdump($buf);
+    syswrite(RIGHT, $buf, length($buf));
+  }
+  if (vec($ord, fileno(RIGHT), 1)) {
+    defined sysread(RIGHT, $buf, 65536) or die "read(/dev/unet1): $!";
+    print "read packet from unet1\n";
+    hexdump($buf);
+    syswrite(LEFT, $buf, length($buf));
+  }
+}
+
+sub hexdump {
+  my $buf = shift;
+  my ($off, $i);
+
+  while ($off < length($buf)) {
+    printf "%08x : ", $off;
+    for ($i = $off; $i < $off + 16; $i++) {
+      if ($i >= length($buf)) {
+       print "** ";
+      } else {
+       printf "%02x ", ord(substr($buf, $i, 1));
+      }
+    }
+    print ": ";
+    for ($i = $off; $i < $off + 16; $i++) {
+      if ($i >= length($buf)) {
+       print "*";
+      } else {
+       $ch = substr($buf, $i, 1);
+       $code = ord($ch);
+       if ($code < 32 || $code > 126) {
+         print ".";
+       } else {
+         print $ch;
+       }
+      }
+    }
+    print "\n";
+    $off += 16;
+  }
+  print "\n";
+}
diff --git a/tests/packets b/tests/packets
new file mode 100755 (executable)
index 0000000..09e5b69
--- /dev/null
@@ -0,0 +1,41 @@
+#! /bin/perl
+
+$| = 1;
+sleep 10;
+for (;;) {
+  sysread(STDIN, $buf, 65536) or die "read: $!";
+  hexdump($buf);
+}
+
+sub hexdump {
+  my $buf = shift;
+  my ($off, $i);
+
+  while ($off < length($buf)) {
+    printf "%08x : ", $off;
+    for ($i = $off; $i < $off + 16; $i++) {
+      if ($i >= length($buf)) {
+       print "** ";
+      } else {
+       printf "%02x ", ord(substr($buf, $i, 1));
+      }
+    }
+    print ": ";
+    for ($i = $off; $i < $off + 16; $i++) {
+      if ($i >= length($buf)) {
+       print "*";
+      } else {
+       $ch = substr($buf, $i, 1);
+       $code = ord($ch);
+       if ($code < 32 || $code > 126) {
+         print ".";
+       } else {
+         print $ch;
+       }
+      }
+    }
+    print "\n";
+    $off += 16;
+  }
+  print "\n";
+}
diff --git a/tests/vpn.ssh b/tests/vpn.ssh
new file mode 100755 (executable)
index 0000000..bbe35bd
--- /dev/null
@@ -0,0 +1,51 @@
+#! /bin/perl
+
+use Socket;
+
+# --- Read the network interface to steal ---
+
+$netif = shift;
+
+# --- Start a child if so requested ---
+
+if (@ARGV) {
+  socketpair(ONE, TOTHER, PF_UNIX, SOCK_STREAM, 0)
+    or die "socketpair: $!";
+  $kid = fork();
+  defined $kid or die "fork: $!";
+  if ($kid) {
+    close ONE;
+    open STDIN, ">&TOTHER" or die "dup stdin: $!";
+    open STDOUT, ">&TOTHER" or die "dup stdout: $!";
+    close TOTHER;
+    exec @ARGV;
+    die "exec: $!";
+  }
+  close TOTHER;
+  open STDIN, ">&ONE" or die "dup stdin: $!";
+  open STDOUT, ">&ONE" or die "dup stdout: $!";
+  close ONE;
+}
+
+# --- Now start work on this ---
+
+open NETIF, "+> $netif" or die "open($netif): $!";
+
+for (;;) {
+  $rfd = '';
+  vec($rfd, fileno(STDIN), 1) = 1;
+  vec($rfd, fileno(NETIF), 1) = 1;
+  select($rfd, undef, undef, undef) or die "select: $!";
+
+  if (vec($rfd, fileno(NETIF), 1)) {
+    sysread(NETIF, $pkt, 65536);
+    $pkt = pack("n", length($pkt)) . $pkt;
+    syswrite(STDOUT, $pkt, length($pkt));
+  }
+  if (vec($rfd, fileno(STDIN), 1)) {
+    sysread(STDIN, $clen, 2) or die "tunnel has vanished: $!";
+    $len = unpack("n", $clen);
+    sysread(STDIN, $pkt, $len);
+    syswrite(NETIF, $pkt, length($pkt));
+  }
+}
diff --git a/unet.c b/unet.c
new file mode 100644 (file)
index 0000000..09020eb
--- /dev/null
+++ b/unet.c
@@ -0,0 +1,1087 @@
+/* -*-c-*-
+ *
+ * $Id: unet.c,v 1.1 2001/01/25 22:03:39 mdw Exp $
+ *
+ * User-space network device support.
+ *
+ * (c) 1998 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Usernet.
+ *
+ * Usernet 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.
+ *
+ * Usernet is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Usernet; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: unet.c,v $
+ * Revision 1.1  2001/01/25 22:03:39  mdw
+ * Initial check-in (somewhat belated).
+ *
+ */
+
+/*----- Include files -----------------------------------------------------*/
+
+#include <linux/module.h>
+
+#include <asm/byteorder.h>
+#include <asm/segment.h>
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include "unetconf.h"
+#include "unet.h"
+
+MODULE_AUTHOR("Mark Wooding");
+MODULE_DESCRIPTION("Allows userland handling of a network interface");
+
+/*----- Debugging macros --------------------------------------------------*/
+
+#define UNET_DEBUGALWAYS 1
+#define UNET_DEBUGRUNTIME 0
+#define UNET_DEBUGNEVER -1
+
+/* --- If the macro isn't defined then be switchable at runtime --- */
+
+#ifndef UNET_DEBUG
+#  define UNET_DEBUG UNET_DEBUGRUNTIME
+#endif
+
+/* --- Define the base macro @D@ according to the debug setting --- */
+
+#if UNET_DEBUG == UNET_DEBUGALWAYS
+#  define D(x) { x }
+#  define DIF(u, x) if ((u)->f & UNIF_DEBUG) { x }
+#elif UNET_DEBUG == UNET_DEBUGRUNTIME
+#  define D(x) if (unet_debug) { x }
+#  define DIF(u, x) if ((u)->f & UNIF_DEBUG) { x }
+#elif UNET_DEBUG == UNET_DEBUGNEVER
+#  define D(x)
+#  define DIF(u, x)
+#elif
+#  error UNET_DEBUG set to invalid value (bug in configure script?)
+#endif
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Unet connection status --- *
+ *
+ * Records are stored in a slightly strange doubly linked list.  The list
+ * exists so that I can find the next unused sequence number when creating
+ * new connections.  It's odd because of the type of the @prev@ node;
+ * rather than being the address of the previous item, it's the address of
+ * the previous item's pointer to me, which among other good things means
+ * that it works on the list head too.
+ */
+
+struct unet {
+  struct unet *next, **prev;           /* List of unet blocks */
+  struct device nif;                   /* Network interface block */
+  int seq;                             /* Sequence number for connection */
+  char name[UNET_NAMEMAX];             /* Buffer for my interface name */
+  struct wait_queue *q;                        /* Wait list for device reads */
+  struct sk_buff_head skbq;            /* Queue of packets waiting */
+  struct enet_statistics e;            /* Pointer to statistics block */
+  unsigned short protocol;             /* Protocol for outgoing packets */
+  unsigned f;                          /* Userful flags */
+};
+
+/*----- Static variables --------------------------------------------------*/
+
+#if UNET_DEBUG == UNET_DEBUGRUNTIME
+static int unet_debug = 0;
+MODULE_PARM(unet_debug, "i");
+#endif
+
+static int unet_npersist = UNET_NPERSIST;
+static int unet_maxif = UNET_MAXIF;
+static struct unet *unet_persistent;
+static struct unet *unet_list = 0;
+
+MODULE_PARM(unet_npersist, "i");
+MODULE_PARM(unet_maxif, "i");
+
+/*----- Debugging code ----------------------------------------------------*/
+
+#if UNET_DEBUG != UNET_DEBUGNEVER
+
+/* --- @unet_dumpBlock@ --- *
+ *
+ * Arguments:  @struct unet *u@ = pointer to block to dump
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps a unet object to syslogd.
+ */
+
+static void unet_dumpBlock(struct unet *u)
+{
+  printk(KERN_DEBUG "unet: dumping unet block at %p\n", u);
+  printk(KERN_DEBUG "      sequence number = %d\n", u->seq);
+  printk(KERN_DEBUG "      interface name = `%s'\n", u->name);
+  printk(KERN_DEBUG "      flags =%s%s%s\n",
+        u->f & UNIF_TRANS ? " TRANS" : "",
+        u->f & UNIF_OPEN ? " OPEN" : "",
+        u->f & UNIF_DEBUG ? " DEBUG" : "");
+  printk(KERN_DEBUG "      interface type = %d\n", u->nif.type);
+  printk(KERN_DEBUG "      header len = %d\n", u->nif.hard_header_len);
+  printk(KERN_DEBUG "      mtu = %d\n", u->nif.mtu);
+  printk(KERN_DEBUG "      protocol = %d\n", ntohs(u->protocol));
+  printk(KERN_DEBUG "      address len = %d\n", u->nif.addr_len);
+}
+
+/* --- @unet_dump@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps the entire unet state to syslogd.
+ */
+
+static void unet_dump(void)
+{
+  int i;
+  struct unet *u;
+
+  for (i = 0; i < unet_npersist; i++)
+    unet_dumpBlock(&unet_persistent[i]);
+  for (u = unet_list; u; u = u->next)
+    unet_dumpBlock(u);
+}
+
+/* --- @unet_hexdump@ --- *
+ *
+ * Arguments:  @const char *prefix@ = prefix to print on output lines
+ *             @const void *b@ = pointer to block to dump
+ *             @size_t sz@ = size of block to dump
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps a hex block to the kernel log.
+ */
+
+#define UNET_HEXROWSZ 16
+
+static void unet_hexdump(const char *prefix, const char *b, size_t sz)
+{
+  const unsigned char *p = b;
+  size_t i;
+  unsigned long o = 0;
+  size_t c;
+
+  char buf[256], *q;
+
+  /* --- Now start work --- */
+
+  while (sz) {
+    q = buf;
+    q += sprintf(q, "%s%08lx : ", prefix, o);
+    for (i = 0; i < UNET_HEXROWSZ; i++) {
+      if (i < sz)
+       q += sprintf(q, "%02x ", p[i]);
+      else
+       q += sprintf(q, "** ");
+    }
+    *q++ = ':'; *q++ = ' ';
+    for (i = 0; i < UNET_HEXROWSZ; i++) {
+      if (i < sz)
+       *q++ = (p[i] >= 32 && p[i] < 127) ? p[i] : '.';
+      else
+       *q++ = '*';
+    }
+    *q++ = '\n';
+    *q++ = 0;
+    printk("%s", buf);
+    c = (sz >= UNET_HEXROWSZ) ? UNET_HEXROWSZ : sz;
+    sz -= c, p += c, o += c;
+  }
+}
+
+#endif
+
+/*----- The unet network interfaces ---------------------------------------*/
+
+/* --- @unet_ifopen@ --- *
+ *
+ * Arguments:  @struct device *nif@ = pointer to network interface
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Turns on the network interface ready for action.  Oh, yes.
+ */
+
+static int unet_ifopen(struct device *nif)
+{
+  D( struct unet *u = nif->priv;
+     if (u->f & UNIF_DEBUG)
+       printk(KERN_DEBUG "unet: opening interface %s\n", u->name); )
+  MOD_INC_USE_COUNT;
+  return (0);
+}
+
+/* --- @unet_ifclose@ --- *
+ *
+ * Arguments:  @struct device *nif@ = pointer to network interface
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Turns off the network interface.
+ */
+
+static int unet_ifclose(struct device *nif)
+{
+  D( struct unet *u = nif->priv;
+     if (u->f & UNIF_DEBUG)
+       printk(KERN_DEBUG "unet: closing interface %d\n", u->seq); )
+  MOD_DEC_USE_COUNT;
+  return (0);
+}
+
+/* --- @unet_iftx@ --- *
+ *
+ * Arguments:  @struct sk_buff *skb@ = incoming network packet
+ *             @struct device *nif@ = pointer to network interface
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Queues a network packet ready for collection by the user
+ *             end of the business.
+ */
+
+static int unet_iftx(struct sk_buff *skb, struct device *nif)
+{
+  struct unet *u = nif->priv;
+  int qed;
+
+  DIF(u,
+      printk(KERN_DEBUG "unet: packet received on if %s\n", u->name);
+      unet_hexdump(KERN_DEBUG "      ", skb->data, skb->len); )
+
+  /* --- Discard packets when nobody's listening --- */
+
+  if ((u->f & UNIF_OPEN) == 0) {
+    DIF(u, printk(KERN_DEBUG "unet: dropped packet: nobody's listening\n"); )
+    dev_kfree_skb(skb);
+    u->e.tx_dropped++;
+    return (0);
+  }
+
+  /* --- Discard packets when the queue's too long --- */
+
+  if (u->skbq.qlen >= UNET_QMAXLEN) {
+    DIF(u, printk(KERN_DEBUG "unet: refused packet: queue overflow\n"); )
+    return (-1);
+  }
+
+  /* --- Attach the buffer to the waiting list --- */
+
+  qed = u->skbq.qlen;
+  skb_queue_tail(&u->skbq, skb);
+  u->e.tx_packets++;
+  DIF(u, printk(KERN_DEBUG "unet: queued packet OK\n"); )
+
+  /* --- If there are waiting processes, give 'em a kick --- */
+
+  if (qed == 0) {
+    DIF(u, printk(KERN_DEBUG "unet: waking up sleeping listeners\n"); )
+    wake_up_interruptible(&u->q);
+  }
+  return (0);
+}
+
+/* --- @unet_ifgetstats@ --- *
+ *
+ * Arguments:  @struct device *nif@ = pointer to network interface
+ *
+ * Returns:    Pointer to a statistics buffer.
+ *
+ * Use:                Returns a block of interface statistics.
+ */
+
+static struct enet_statistics *unet_ifgetstats(struct device *nif)
+{
+  struct unet *u = nif->priv;
+  DIF(u, printk(KERN_DEBUG "unet: stats request for if %s\n", u->name); )
+  return (&u->e);
+}
+
+/* --- @unet_ifinit@ --- *
+ *
+ * Arguments:  @struct device *nif@ = pointer to network interface
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Initialises a unet network interface.
+ */
+
+static int unet_ifinit(struct device *nif)
+{
+  struct unet *u = nif->priv;
+
+  DIF(u, printk(KERN_DEBUG "unet: initialise interface %s\n", u->name); )
+
+  /* --- Initialise statistics gathering --- */
+
+  memset(&u->e, 0, sizeof(u->e));
+  u->nif.get_stats = unet_ifgetstats;
+
+  /* --- Opening and closing interfaces --- */
+
+  u->nif.open = unet_ifopen;
+  u->nif.stop = unet_ifclose;
+
+  /* --- Sending packets to the `outside world' --- */
+
+  u->nif.hard_start_xmit = unet_iftx;
+
+  /* --- Some initialisation magic --- */
+
+#ifdef notdef
+  for (i = 0; i < DEV_NUMBUFFS; i++)
+    skb_queue_head_init(&u->nif.buffs[i]);
+#endif
+
+  /* --- Configure other grotty bits of the interface --- */
+
+  u->nif.hard_header = 0;
+  u->nif.rebuild_header = 0;
+  u->nif.set_mac_address = 0;
+
+  u->nif.type = ARPHRD_LOOPBACK;       /* Got a better idea? */
+  u->nif.hard_header_len = 0;
+  u->nif.mtu = 1500 - MAX_HEADER;
+  u->nif.addr_len = 0;
+  u->nif.tx_queue_len = 16;            /* I keep my own queue */
+
+  memset(u->nif.broadcast, 0xFFu, MAX_ADDR_LEN);
+  
+  u->nif.flags = IFF_NOARP;
+
+  /* --- Finished! --- */
+
+  return (0);
+}
+
+/*----- Attachment management ---------------------------------------------*/
+
+/* --- @unet_setup@ --- *
+ *
+ * Arguments:  @struct unet *u@ = pointer to a unet block
+ *             @int seq@ = sequence number to allocate
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Initialises a unet block ready for action.
+ */
+
+static int unet_setup(struct unet *u, int seq)
+{
+  static struct device tpl;
+  int e;
+
+  D( printk(KERN_DEBUG "unet: setting up unet block %d\n", seq); )
+
+  /* --- A little bit of initialisation --- */
+
+  u->seq = seq;
+  u->f = 0;
+  u->q = 0;
+
+  /* --- Inherit device debug flag from global flag --- */
+
+#if UNET_DEBUG == UNET_DEBUGRUNTIME
+  if (unet_debug)
+    u->f |= UNIF_DEBUG;
+#elif UNET_DEBUG == UNET_DEBUGALWAYS
+  u->f |= UNIF_DEBUG;
+#endif
+
+  /* --- Set up the network device --- */
+
+  u->nif = tpl;
+  sprintf(u->name, "unet%d", seq);
+  u->nif.name = u->name;
+  u->nif.priv = u;
+  u->nif.init = unet_ifinit;
+  u->protocol = htons(ETH_P_IP);
+
+  if ((e = register_netdev(&u->nif)) != 0) {
+    printk(KERN_ERR "unet: couldn't register net interface\n");
+    return (e);
+  }
+
+  /* --- Empty the skbuff list --- */
+
+  skb_queue_head_init(&u->skbq);
+      
+  /* --- We're only finished, that's all --- */
+
+  return (0);
+}
+
+/* --- @unet_flush@ --- *
+ *
+ * Arguments:  @struct unet *u@ = pointer to a unet block
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases all the packets waiting for transmission.
+ */
+
+static void unet_flush(struct unet *u)
+{
+  struct sk_buff *skb;
+
+#if UNET_DEBUG != UNET_DEBUGNEVER
+  int i = 0;
+#endif
+
+  DIF(u, printk(KERN_DEBUG "unet: flushing packets on %s\n", u->name); )
+
+  while ((skb = skb_dequeue(&u->skbq)) != 0) {
+    dev_kfree_skb(skb);
+    D( i++; )
+  }
+  DIF(u, printk(KERN_DEBUG "unet: released %d waiting packets\n", i); )
+}
+
+/* --- @unet_kill@ --- *
+ *
+ * Arguments:  @struct unet *u@ = pointer to a unet block
+ *
+ * Returns:    ---
+ *
+ * Use:                Kills and decommissions a Usernet block.
+ */
+
+static void unet_kill(struct unet *u)
+{
+  unet_flush(u);
+  unregister_netdev(&u->nif);
+}
+
+/*----- Handling the unet device ------------------------------------------*/
+
+/* --- @unet_devopen@ --- *
+ *
+ * Arguments:  @struct inode *ino@ = inode block to open
+ *             @struct file *f@ = file block to play with
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Handles the opening of a unet device.
+ */
+
+static int unet_devopen(struct inode *ino, struct file *f)
+{
+  struct unet *u, **up;
+  int seq;
+  int e = 0;
+
+  D( printk(KERN_DEBUG "unet: device open request\n"); )
+
+  /* --- Decide whether this is a persistent unet --- */
+
+  if ((seq = MINOR(ino->i_rdev)) < unet_npersist) {
+    u = &unet_persistent[seq];
+    if (u->f & UNIF_OPEN) {
+      e = -EBUSY;
+      goto tidy_0;
+    }
+    D( printk(KERN_DEBUG "unet: opened persistent %s\n", u->name); )
+  }
+
+  /* --- Otherwise we've got to create a new one --- */
+
+  else {
+
+    /* --- Try to find a spare sequence number --- */
+
+    for (seq = unet_npersist, up = &unet_list; *up != 0;
+        seq++, up = &(*up)->next) {
+      if ((*up)->seq > seq)
+       break;
+      if (seq >= unet_maxif) {
+       printk("unet: all unets are occupied\n");
+       e = -ENFILE;
+       goto tidy_0;
+      }
+    }
+
+    D( printk(KERN_DEBUG "unet: allocated sequence number %d\n", seq); )
+
+    /* --- Allocate a new block --- */
+
+    if ((u = kmalloc(sizeof(*u), GFP_KERNEL)) == 0) {
+      printk(KERN_ERR "unet: couldn't allocate a unet block\n");
+      e = -ENOMEM;
+      goto tidy_0;
+    }
+
+    /* --- Initialise the block --- */
+
+    if ((e = unet_setup(u, seq)) != 0)
+      goto tidy_1;
+    u->f |= UNIF_TRANS;
+
+    /* --- Link the block into the list --- */
+
+    u->next = *up;
+    u->prev = up;
+    if (*up)
+      (*up)->prev = &u->next;
+    *up = u;
+
+    D( printk(KERN_DEBUG "unet: opened transient %d\n", seq);
+       unet_dumpBlock(u); )
+  }
+
+  /* --- Done --- */
+
+  u->f |= UNIF_OPEN;
+  f->private_data = u;
+  MOD_INC_USE_COUNT;
+  return (0);
+
+  /* --- Tidy up after little disasters --- */
+
+tidy_1:
+  kfree(u);
+tidy_0:
+  return (e);
+}
+
+/* --- @unet_devclose@ --- *
+ *
+ * Arguments:  @struct inode *ino@ = pointer to inode block
+ *             @struct file *f@ = pointer to file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees up a unet connection.
+ */
+
+static int unet_devclose(struct inode *ino, struct file *f)
+{
+  struct unet *u = f->private_data;
+
+  DIF(u, printk(KERN_DEBUG "unet: closing %s\n", u->name); )
+
+  /* --- A transient unet needs to be destroyed --- */
+
+  if (u->f & UNIF_TRANS) {
+    *u->prev = u->next;
+    if (u->next)
+      u->next->prev = u->prev;
+    unet_kill(u);
+    kfree(u);
+    D( printk(KERN_DEBUG "unet: released transient unet\n"); )
+  }
+
+  /* --- A persistent unet needs to be shutdown --- */
+
+  else {
+    u->f &= ~UNIF_OPEN;
+    unet_flush(u);
+    DIF(u, printk(KERN_DEBUG "unet: unblocked persistent unet\n"); )
+  }
+
+  MOD_DEC_USE_COUNT;
+  return (0);
+}
+
+/* --- @unet_devpoll@ --- *
+ *
+ * Arguments:  @struct file *f@ = pointer to my file block
+ *             @struct poll_table_struct *p@ = poll table to wait on
+ *
+ * Returns:    Nonzero if OK to continue, zero if waiting.
+ *
+ * Use:                Plays a unet device's part in a @poll@(2) call.
+ */
+
+static unsigned unet_devpoll(struct file *f, struct poll_table_struct *p)
+{
+  struct unet *u = f->private_data;
+  unsigned m = 0;
+
+  DIF(u, printk(KERN_DEBUG "unet: poll request for %s\n", u->name); )
+
+  poll_wait(f, &u->q, p);
+  if (skb_peek(&u->skbq))
+    m |= POLLIN | POLLRDNORM;
+  m |= POLLOUT | POLLWRNORM;
+  return (m);
+}
+
+/* --- @unet_devwrite@ --- *
+ *
+ * Arguments:  @struct file *f@ = pointer to the file block
+ *             @char *buf@ = pointer to caller's buffer
+ *             @size_t sz@ = size of data to send
+ *             @loff_t *off@ = offset to set (ignored)
+ *
+ * Returns:    Number of bytes written, or error condition.
+ *
+ * Use:                Sends a packet of data.  No buffering is done.
+ */
+
+static ssize_t unet_devwrite(struct file *f, const char *buf, size_t sz,
+                            loff_t *off)
+{
+  struct unet *u = f->private_data;
+  struct sk_buff *skb;
+
+  DIF(u, printk(KERN_DEBUG "unet: write request for %s\n", u->name); )
+
+  /* --- Dump the packet we're meant to send --- */
+
+#ifdef UNET_DEBUG
+  if (u->f & UNIF_DEBUG) {
+    void *b = kmalloc(sz, GFP_KERNEL);
+    if (b) {
+      copy_from_user(b, buf, sz);
+      unet_hexdump(KERN_DEBUG "      ", b, sz);
+      kfree(b);
+    } else
+      printk(KERN_NOTICE "unet: not enough memory to dump block");
+  }
+#endif
+
+  /* --- Allocate an skbuff for the block --- */
+
+  if ((skb = dev_alloc_skb(sz)) == 0) {
+    printk(KERN_ERR "unet: failed to allocate skbuff\n");
+    u->e.rx_dropped++;
+    return (-ENOSR);
+  }
+
+  /* --- Copy my data into the skbuff --- */
+
+  copy_from_user(skb_put(skb, sz), buf, sz);
+  skb->dev = &u->nif;
+  skb->mac.raw = skb->data;
+  skb->protocol = u->protocol;
+  netif_rx(skb);
+  u->e.rx_packets++;
+  return (sz);
+}
+
+/* --- @unet_devread@ --- *
+ *
+ * Arguments:  @struct file *f@ = pointer to the file block
+ *             @char *buf@ = pointer to caller's buffer
+ *             @size_t sz@ = size of caller's buffer
+ *             @loff_t *off@ = offset to set (ignored)
+ *
+ * Returns:    Number of bytes read, or error condition.
+ *
+ * Use:                Reads the next packet waiting for the device.
+ */
+
+static ssize_t unet_devread(struct file *f, char *buf, size_t sz,
+                           loff_t *off)
+{
+  struct unet *u = f->private_data;
+  struct sk_buff *skb;
+
+  DIF(u, printk(KERN_DEBUG "unet: read request for %s\n", u->name); )
+
+  /* --- Is the user sane? --- *
+   *
+   * The UDP protocol returns immediately in response to a zero-length read,
+   * and following this behaviour seems to cause `least surprise'.
+   */
+
+  if (!sz)
+    return (0);
+
+  /* --- Make sure there's a packet waiting for me --- */
+
+  if ((skb = skb_dequeue(&u->skbq)) == 0) {
+    struct wait_queue wq = { current, 0 };
+
+    DIF(u, printk(KERN_DEBUG "unet: no packets waiting\n"); )
+
+    /* --- Check for nonblocking I/O --- */
+
+    if (f->f_flags & O_NONBLOCK) {
+      DIF(u, printk(KERN_DEBUG "unet: nonblocking read: fail\n"); )
+      return (-EWOULDBLOCK);
+    }
+
+    /* --- Otherwise block until there's a packet --- */
+
+    current->state = TASK_INTERRUPTIBLE;
+    add_wait_queue(&u->q, &wq);
+
+    do {
+
+      if (signal_pending(current)) {
+
+       DIF(u, printk(KERN_DEBUG "unet: interrupted by signal\n"); )
+
+       remove_wait_queue(&u->q, &wq);
+       current->state = TASK_RUNNING;
+       return (-ERESTARTSYS);
+      }
+
+      DIF(u, printk(KERN_DEBUG "unet: blocking until packet arrives\n"); )
+
+      schedule();
+
+    } while ((skb = skb_dequeue(&u->skbq)) == 0);
+
+    remove_wait_queue(&u->q, &wq);
+    current->state = TASK_RUNNING;
+  }
+
+  DIF(u, printk(KERN_DEBUG "unet: found a packet\n"); )
+
+  /* --- There is now a packet waiting --- */
+
+  if (sz > skb->len)
+    sz = skb->len;
+  copy_to_user(buf, skb->data, sz);
+  dev_kfree_skb(skb);
+
+  DIF(u, printk(KERN_DEBUG "unet: passed packet on to user\n"); )
+
+  return (sz);
+}
+
+/* --- @unet_devioctl@ --- *
+ *
+ * Arguments:  @struct inode *ino@ = pointer to inode block
+ *             @struct file *f@ = pointer to file block
+ *             @unsigned int c@ = command code to execute
+ *             @unsigned long arg@ = argument passed to me
+ *
+ * Returns:    Positive return value, or negative error condition.
+ *
+ * Use:                Performs miscellaneous operations on a unet device.
+ */
+
+static int unet_devioctl(struct inode *ino,
+                        struct file *f,
+                        unsigned int c,
+                        unsigned long arg)
+{
+  struct unet *u = f->private_data;
+  int e = 0;
+
+  DIF(u, printk(KERN_DEBUG "unet: ioctl request for %s\n", u->name); )
+
+  switch (c) {
+
+    /* --- @FIONREAD@ --- *
+     *
+     * Caller wants to know how big the next packet will be.  Don't
+     * disappoint.
+     */
+
+    case FIONREAD: {
+      struct sk_buff *skb = skb_peek(&u->skbq);
+
+      DIF(u, printk(KERN_DEBUG "unet: FIONREAD found %d bytes\n",
+                   skb ? skb->len : 0); )
+
+      if (skb)
+       e = skb->len;
+    } break;
+
+    /* --- @UNIOCGINFO@ --- *
+     *
+     * Caller wants information about me.
+     */
+
+    case UNIOCGINFO: {
+      struct unet_info uni, *unip;
+
+      DIF(u, printk(KERN_DEBUG "unet: UNIOCGINFO called\n"); )
+
+      unip = (struct unet_info *)arg;
+
+      /* --- Special case --- *
+       *
+       * If the pointer is null, this call means `are you there?' and can
+       * be used to check for a Usernet attachment.
+       */
+
+      if (!unip)
+       break;
+
+      /* --- Ensure that the area can be written to --- */
+
+      if ((e = verify_area(VERIFY_WRITE, unip, sizeof(*unip))) != 0)
+       return (e);
+
+      /* --- Build the information block in memory --- */
+
+      memset(&uni, 0, sizeof(uni));    /* Paranoia */
+      strncpy(uni.uni_ifname, u->name, UNET_NAMEMAX);
+      uni.uni_mtu = u->nif.mtu;
+      uni.uni_family = AF_INET;
+      uni.uni_proto = ntohs(u->protocol);
+      uni.uni_flags = u->f;
+
+      /* --- Copy it to the user and return --- */
+
+      copy_to_user(unip, &uni, sizeof(uni));
+    } break;
+
+    /* --- @UNIOCSDEBUG@ --- */
+
+#if UNET_DEBUG != UNET_DEBUGNEVER
+    case UNIOCSDEBUG: {
+      int n = !!arg;
+      int o = !!(u->f & UNIF_DEBUG);
+
+      if (n || o) {
+       printk(KERN_DEBUG "unet: UNIOCSDEBUG on %s: %s\n", u->name,
+              (o && n) ? "debugging still on" :
+              (!o && n) ? "debugging turned on" :
+              (o && !n) ? "debugging turned off" :
+              (!o && !n) ? "you can't see this message" :
+              "Logic failure: universe exploding");
+      }
+      if (n)
+       u->f |= UNIF_DEBUG;
+      else
+       u->f &= ~UNIF_DEBUG;
+    } break;
+#endif
+
+    /* --- @UNIOCGPROTO@ and @UNIOCSPROTO@ --- */
+
+    case UNIOCGPROTO:
+      D( printk(KERN_DEBUG "unet: UNIOCGPROTO on %s: read protocol: %d\n",
+               u->name, ntohs(u->protocol)); )
+      e = ntohs(u->protocol);
+      break;
+    case UNIOCSPROTO:
+      D( printk(KERN_DEBUG "unet: UNIOCSPROTO on %s: set protocol: %d\n",
+               u->name, (int)arg); )
+      u->protocol = htons(arg);
+      break;
+
+    /* --- @UNIOCGGDEBUG@ and @UNIOCSGDEBUG@ --- */
+
+    case UNIOCGGDEBUG:
+      D( printk(KERN_DEBUG "unet: UNIOCGGDEBUG: get global debug: on\n"); )
+#if UNET_DEBUG == UNET_DEBUGRUNTIME
+      e = unet_debug;
+#elif UNET_DEBUG == UNET_DEBUGALWAYS
+      e = 1;
+#elif UNET_DEBUG == UNET_DEBUGNEVER
+      e = 0;
+#endif
+      break;
+
+#if UNET_DEBUG == UNET_DEBUGRUNTIME
+    case UNIOCSGDEBUG:
+      printk(KERN_DEBUG "unet: UNIOCSGDEBUG: set global debug: %s\n",
+            (arg && unet_debug) ? "debugging still on" :
+             (!arg && unet_debug) ? "debugging turned off" :
+            (arg && !unet_debug) ? "debugging turned on" :
+            (!arg && !unet_debug) ? "you can't see this message" :
+            "Logic failure: universe exploding");
+      unet_debug = !!arg;
+      break;
+#endif
+
+    /* --- @UNIOCDUMP@ --- */
+
+#if UNET_DEBUG != UNET_DEBUGNEVER
+    case UNIOCDUMP:
+      D( unet_dumpBlock(u); )
+      break;
+#endif
+
+    /* --- @UNIOCGMAXIF@ --- */
+
+    case UNIOCGMAXIF:
+      D( printk(KERN_DEBUG "unet: UNIOCGMAXIF: unet_maxif = %d\n",
+               unet_maxif); )
+      e = unet_maxif;
+      break;
+
+    /* --- @UNIOCSMAXIF@ --- */
+
+    case UNIOCSMAXIF:
+      e = -EINVAL;
+      if (arg < unet_npersist || arg > INT_MAX)
+       return (-EINVAL);
+      for (u = unet_list; u; u = u->next) {
+       if (u->seq >= unet_npersist)
+         return (-EBUSY);
+      }
+      unet_maxif = arg;
+      D( printk(KERN_DEBUG "unet: UNIOCSMAXIF: unet_maxif = %d\n",
+               unet_maxif); )
+      break;
+
+    /* --- Everything else --- *
+     *
+     * You lose.
+     */
+
+    default:
+      D( printk(KERN_DEBUG "unet: unknown ioctl %08x\n", c); )
+      e = -EINVAL;
+  }
+
+  return (e);
+}
+
+/*----- The unet device ---------------------------------------------------*/
+
+/* --- Device operations --- */
+
+struct file_operations unet_fops = {
+  0,                                   /* unet_devlseek */
+  unet_devread,
+  unet_devwrite,
+  0,                                   /* unet_devreaddir */
+  unet_devpoll,
+  unet_devioctl,
+  0,                                   /* unet_devmmap */
+  unet_devopen,
+  0,                                   /* unet_flush */
+  unet_devclose
+};
+
+/*----- Initaliseation and shutdown ---------------------------------------*/
+
+/* --- @unet_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Registers the unet device.
+ */
+
+__initfunc(int unet_init(void))
+{
+  int e = 0;
+  int i = 0;
+
+  /* --- Register my character device --- */
+
+  if ((e = register_chrdev(UNET_MAJOR, "unet", &unet_fops)) != 0) {
+    printk(KERN_ERR "unet: can't claim major device %d\n", UNET_MAJOR);
+    goto tidy_0;
+  }
+
+  /* --- Make the persistent unet devices --- */
+
+  if ((unet_persistent = kmalloc(unet_npersist * sizeof(struct unet),
+                                GFP_KERNEL)) == 0) {
+    printk(KERN_ERR "unet: can't allocate %i persistent unet blocks",
+          unet_npersist);
+    e = -ENOMEM;
+    goto tidy_1;
+  }
+    
+  for (i = 0; i < unet_npersist; i++) {
+    if ((e = unet_setup(&unet_persistent[i], i)) != 0) {
+      printk(KERN_ERR "unet: can't create persistent unet %d\n", i);
+      goto tidy_2;
+    }
+  }
+
+  /* --- Done --- */
+
+  D( unet_dump(); )
+  return (0);
+
+  /* --- Some bad stuff happened --- */
+
+tidy_2:
+  while (i > 0) {
+    i--;
+    unregister_netdev(&unet_persistent[i].nif);
+  }
+  kfree(unet_persistent);
+
+tidy_1:
+  unregister_chrdev(UNET_MAJOR, "unet");
+
+tidy_0:
+  return (e);
+}
+
+#ifdef MODULE
+
+/* --- @init_module@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero or error condition.
+ *
+ * Use:                Initialises the unet kernel module.
+ */
+
+int init_module(void)
+{
+  if (unet_npersist < 0 || unet_maxif < unet_npersist
+#if UNET_DEBUG == UNET_DEBUGRUNTIME
+      || (unet_debug != 0 && unet_debug != 1)
+#endif
+    )
+    return (-EINVAL);
+  return (unet_init());
+}
+
+/* --- @cleanup_module@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Tidies up the unet module ready for it to be killed.
+ */
+
+void cleanup_module(void)
+{
+  int i;
+
+  for (i = 0; i < unet_npersist; i++) {
+    D( printk(KERN_DEBUG "unet: releasing persistent unet %d\n", i); )
+    unet_kill(&unet_persistent[i]);
+  }
+  kfree(unet_persistent);
+
+  unregister_chrdev(UNET_MAJOR, "unet");
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/unet.h b/unet.h
new file mode 100644 (file)
index 0000000..8609e5f
--- /dev/null
+++ b/unet.h
@@ -0,0 +1,160 @@
+/* -*-c-*-
+ *
+ * $Id: unet.h,v 1.1 2001/01/25 22:03:39 mdw Exp $
+ *
+ * User-space network device support.
+ *
+ * (c) 1998 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Usernet.
+ *
+ * Usernet 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.
+ *
+ * Usernet is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Usernet; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: unet.h,v $
+ * Revision 1.1  2001/01/25 22:03:39  mdw
+ * Initial check-in (somewhat belated).
+ *
+ */
+
+#ifndef _LINUX_UNET_H
+#define _LINUX_UNET_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- What's the story? -------------------------------------------------*
+ *
+ * Based on a conversation with Clive Jones about FreeBSD's tunnel device,
+ * I've decided to try to write something similar.  The basic idea is to
+ * tie together a character device and a network interface, so that anything
+ * written to one pops out the other.  I create a device /dev/unet.
+ * Each open(2) of my device creates a network device, whose name can be
+ * read by calling ioctl(2).  A read(2) on the device fetches the next
+ * packet received from the network interface; conversely, a write(2) sends
+ * a network packet through the interface.
+ *
+ * Permissions on /dev/unet ought to be fairly strict.  Remember that
+ * anyone who can get access to it can inject arbitrary IP packets.
+ *
+ * This is my first stab at hacking Linux, so there'll be mistakes and
+ * infelicities.  All I ask is that you tell me what they are.
+ *
+ *                                             [mdw]
+ *                                             mdw@excessus.demon.co.uk
+ */
+
+/*----- @ioctl@(2) calls supported ----------------------------------------*/
+
+/* --- @UNIOCGINFO@ --- *
+ *
+ * Reads useful information about a unet.  The argument is a pointer to a
+ * @unet_info@ structure, which is filled in by the call.  As a special case,
+ * the argument may be a null pointer, in which case the call does nothing
+ * and may be used to verify that a file descriptor refers to a Usernet
+ * attachment.
+ */
+
+#define UNIOCGINFO _IOR('U', 0, sizeof(struct unet_info))
+
+#define UNET_NAMEMAX 20
+
+struct unet_info {
+  char uni_ifname[UNET_NAMEMAX];       /* Interface name string */
+  unsigned short uni_mtu;              /* Maximum transmission unit */
+  unsigned short uni_family;           /* My address family */
+  unsigned short uni_proto;            /* Protocol to stamp on packets */
+  unsigned int uni_flags;              /* Various useful flags */
+};
+
+#define UNIF_TRANS 1                   /* This device is transient */
+#define UNIF_OPEN 2                    /* Not useful to users */
+#define UNIF_DEBUG 4                   /* Debugging enable flag */
+
+/* --- @UNIOCSDEBUG@ --- *
+ *
+ * Sets the debugging state for the attachment.  When the debug flag is set,
+ * all packets sent and received by the device will be logged, as will other
+ * events.
+ */
+
+#define UNIOCSDEBUG _IO('U', 1)
+
+/* --- @UNIOCGPROTO@ --- *
+ *
+ * Reads the protocol stamped on packets received through the character
+ * device interface.  The default is @ETH_P_IP@; the various values are
+ * defined in @<linux/if_ether.h>@.
+ */
+
+#define UNIOCGPROTO _IO('U', 2)
+
+/* --- @UNIOCSPROTO@ --- *
+ *
+ * Sets the protocol to be stamped on outgoing packets.
+ */
+
+#define UNIOCSPROTO _IO('U', 3)
+
+/* --- @UNIOCGGDEBUG@ --- *
+ *
+ * Gets the global debugging flag.
+ */
+
+#define UNIOCGGDEBUG _IO('U', 4)
+
+/* --- @UNIOCSGDEBUG@ --- *
+ *
+ * Sets the global debugging flag.  This is only available when runtime
+ * debugging configuration is compiled in.
+ */
+
+#define UNIOCSGDEBUG _IO('U', 5)
+
+/* --- @UNIOCDUMP@ --- *
+ *
+ * Dumps a unet block's information to the debug device.
+ */
+
+#define UNIOCDUMP _IO('U', 6)
+
+/* --- @UNIOCGMAXIF@ --- *
+ *
+ * Returns the maximum number of interfaces allowed.
+ */
+
+#define UNIOCGMAXIF _IO('U', 7)
+
+/* --- @UNIOCGMAXIF@ --- *
+ *
+ * Sets the maximum number of interfaces allowed.  It's an error to lower
+ * this below the number of the highest currently-used interface.
+ */
+
+#define UNIOCSMAXIF _IO('U', 8)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/unet.texi b/unet.texi
new file mode 100644 (file)
index 0000000..4dd7338
--- /dev/null
+++ b/unet.texi
@@ -0,0 +1,644 @@
+\input texinfo @c -*-texinfo-*-
+@c
+@c $Id: unet.texi,v 1.1 2001/01/25 22:03:39 mdw Exp $
+@c
+@c Manual for usernet device
+@c
+@c (c) 1998 Mark Wooding
+@c
+
+@c ----- Revision history ---------------------------------------------------
+@c
+@c $Log: unet.texi,v $
+@c Revision 1.1  2001/01/25 22:03:39  mdw
+@c Initial check-in (somewhat belated).
+@c
+
+@c ----- Standard boilerplate -----------------------------------------------
+
+@c %**start of header
+@setfilename unet.info
+@settitle The Linux Usernet network interface
+@setchapternewpage odd
+@footnotestyle end
+@paragraphindent 0
+@iftex
+@input texinice
+@afourpaper
+@c @parindent=0pt
+@end iftex
+@c %**end of header
+
+@c ----- Useful macros ------------------------------------------------------
+
+@set version 1.1
+
+@c ----- Copyright matters --------------------------------------------------
+
+@c --- The `Info' version ---
+
+@ifinfo
+
+This file documents the Linux Usernet network interface version
+@value{version}. 
+
+Copyright (c) 1998 Mark Wooding
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+@ignore
+Permission is granted to process this file through TeX and print the
+results, provided the printed document carries a copying permission
+notice identical to this one except for the removal of this paragraph
+(this paragraph not being relevant to the printed manual).
+
+@end ignore
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+sections entitled `Copying' and `GNU General Public License' are
+included exactly as in the original, and provided that the entire
+resulting derived work is distributed under the terms of a permission
+notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that this permission notice may be stated in a translation
+approved by the copyright holder.
+
+@end ifinfo
+
+@c --- Printed title page ---
+
+@titlepage
+
+@title The Linux Usernet network interface.
+@subtitle Transmitting Internet Protocol packets from user processes.
+@author Mark Wooding (@email{mdw@@excessus.demon.co.uk})
+@page
+
+@vskip 0pt plus 1filll
+
+Copyright @copyright{} 1998 Mark Wooding
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+sections entitled `Copying' and `GNU General Public License' are
+included exactly as in the original, and provided that the entire
+resulting derived work is distributed under the terms of a permission
+notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that this permission notice may be stated in a translation
+approved by the copyright holder.
+
+@end titlepage
+
+
+@c --------------------------------------------------------------------------
+@ifinfo
+@node Top, Copying, (dir), (dir)
+@top The Linux Usernet network interface
+
+Usernet allows network interfaces to be attached to character devices.  Any
+packets sent through the network interface will be passed to the process
+reading the device, and data written to the device will appear to have been
+received from the network interface.
+
+This file documents Linux Usernet version @value{version}.
+
+@menu
+* Copying::                     You may modify and redistribute Usernet
+* Introduction::                What Usernet actually does
+* Technical details::           How Usernet does what it does
+* Installing::                  How to build and install Usernet
+* Configuring attachments::     The provided configuration program
+* Programming::                 How to program the Usernet kernel module
+
+ --- The Detailed Node Listing ---
+
+Overview and technical details
+
+* Attachments::                 Attaching devices and network interfaces
+* Usernet devices::             How Usernet's devices behave
+
+Configuring and installing
+
+* Autoconfiguring::             How Usernet configures itself
+* Compiling and installing::    How to compile and install Usernet
+
+The @code{unetcfg} program
+
+* Invoking unetcfg::            Command line options
+* Selecting attachments::       Setting the current attachment
+* Attachment status::           Querying current status information
+* Protocol settings::           Setting the protocol for outgoing packets
+* Setting debugging options::   Various debugging settings
+
+Programming Usernet
+
+* Opening and closing::         Opening and closing attachments
+* Configuring the interface::   How to configure an attached interface
+* Sending and receiving::       Sending and receiving network packets
+@end menu
+
+@end ifinfo
+
+
+@c --------------------------------------------------------------------------
+@node Copying, Introduction, Top, Top
+@unnumbered The GNU General Public License
+
+@include gpl.texi
+
+
+@c --------------------------------------------------------------------------
+@node Introduction, Technical details, Copying, Top
+@unnumbered Introduction
+
+Access to Linux's networking tends to be fairly high-level.  A user process
+can send and receive datagrams, or set up connections to other processes
+easily enough.  Privileged processes can build arbitrary IP packets and send
+them, although raw IP sockets only receive packets for protocols unrecognised
+by the kernel.  Getting hold of all packets sent to a particular host is
+rather more difficult, though.  However, this can be a useful thing to want
+to do.  Usernet is a small kernel module which enables user processes to
+attach to a network interface and read and write packets to it.
+
+Usernet works by creating pairs of character devices and network interfaces.
+Any packet Linux sends to the network interface is sent unchanged to the
+process reading the character device; similarly, when a block of data is
+written to the character device, Usernet claims that it was received from the
+corresponding network interface.
+
+The @samp{diald} program needs to be able to trap packets sent along a
+particular route to know when to open a dialup connection.  The current
+implementation sets up a SLIP interface on a pseudoterminal, which is kludgy
+at best.  It could be modified to use a Usernet interface, and read and write
+packets from there.
+
+The application which motivated the writing of Usernet was setting up a
+virtual private network (VPN).  Each end could set up a point-to-point
+Usernet interface, and run a fairly small daemon, which would read packets
+sent to the remote end, encrypt them and retransmit them as IP-in-IP
+encapsulated datagrams, and decrypt any received IP-in-IP datagrams and
+reinsert them through the Usernet interface.
+
+
+@c --------------------------------------------------------------------------
+@node Technical details, Installing, Introduction, Top
+@chapter Overview and technical details
+
+This chapter explains in more detail how Usernet is arranged and how it
+works.
+
+@menu
+* Attachments::                 Attaching devices and network interfaces
+* Usernet devices::             How Usernet's devices behave
+@end menu
+
+
+@node Attachments, Usernet devices, Technical details, Technical details
+@section Attachments
+
+The Usernet kernel module creates @dfn{attachments} between character devices
+and network interfaces.  Any packet sent by the kernel through the network
+interface can be read from the character device.  Any buffers sent to the
+device will appear to have been received by the attached interface.
+
+The Usernet module understands two types of attachments:
+
+@itemize @bullet
+@item
+@dfn{Persistent attachments} between character devices with low minor device
+numbers are initialised when the module is loaded.  The network interfaces
+always exist, and can be configured at boot time if the module is loaded
+early enough.
+
+@item
+@dfn{Transient attachments} are set up dynamically when a process opens a
+Usernet device with no preattached interface.  The network interface is
+created when the device is opened, and destroyed again when the device is
+closed.  Each open of the device creates a @emph{separate} attachment, so you
+only need one device for any number of transient attachments.
+@end itemize
+
+Usernet imposes a limitation on the number of attachments, mainly to stop
+runaway processes from gobbling kernel resources; there aren't any
+pre-allocated tables which would prevent you from hiking this parameter
+upwards if you had a reason to.
+
+
+@node Usernet devices,  , Attachments, Technical details
+@section Usernet devices
+
+Usernet claims a major device number (chosen at configuration time:
+@xref{Installing}).
+
+The lowest numbered minor devices are persistently
+attached to network interfaces: the network device @code{unet@var{n}} is
+attached to minor device @var{n}, conventionally named
+@code{/dev/unet@var{n}}.
+
+If a device is opened for which there is no persistent attachment, a new
+network interface is allocated and a transient attachment is made.  Each
+separate @code{open}(2) call creates a new network interface and attachment,
+which are destroyed again when the file descriptor on the device is closed.
+It's normal to have one device with no persistent attachment, named
+@code{/dev/unet}.
+
+
+@c --------------------------------------------------------------------------
+@node Installing, Configuring attachments, Technical details, Top
+@chapter Configuring and installing
+
+Usernet is meant to be both simple to set up for most people, and
+sufficiently flexible to meet more advanced needs.
+
+@menu
+* Autoconfiguring::             How Usernet configures itself
+* Compiling and installing::    How to compile and install Usernet
+@end menu
+
+
+@node Autoconfiguring, Compiling and installing, Installing, Installing
+@section Configuration options
+
+Configuration is performed using GNU Autoconf.  Running the supplied
+@code{configure} script without any options will configure Usernet to compile
+properly under most Linux systems.
+
+The standard options accepted by Autoconf-generated configure scripts are
+described in @ref{Invoking configure, , Running @code{configure} Scripts,
+autoconf, Creating Automatic Configuration Scripts}.  In addition to the
+standard Autoconf options, Usernet's script understands these:
+
+@table @code
+@item --with-linux-source=@var{dir}
+Informs the configuration script that the Linux kernel sources are available
+in directory @var{dir}.  The configuration script will find your source code
+in most sane installations.
+
+@item --with-module-dir=@var{dir}
+Informs the configuration script that the compiled kernel module is to be
+installed in directory @var{dir}.  The configuration script will find
+somewhere sensible in most sane installations.
+
+@item --with-major-device=@var{num}
+Sets the major device of all Usernet character devices to @var{num}.  Only
+change this if you find that Usernet is conflicting with some other device.
+
+@item --with-persistent-devices=@var{num}
+Sets the number of persistent attachment devices created by Usernet when it's
+loaded to @var{num}.  The default is 1.
+
+@item --with-transient-minor=@var{num}
+Sets the minor device number of the device special file @code{/dev/unet},
+used to create transient attachments, to @var{num}.  The default is 256; you
+only need to change this if you've created a lot of persistent devices.
+
+@item --with-max-queue-length=@var{num}
+Sets the maximum number of packets Usernet will queue for the process reading
+from an attached character device to @var{num}.  You probably don't need to
+fiddle with this.
+
+@item --with-max-interfaces=@var{num}
+Sets the maximum number of attached network interfaces Usernet will allow to
+exist at any given time to @var{num}.  The default is fairly generous, so you
+shouldn't need to play with this unless you're doing something rather
+strange.
+
+@item --with-debugging
+Enables profuse logging of things Usernet is doing, including complete hex
+dumps of all the network packets the module processes.
+@end table
+
+
+@node Compiling and installing,  , Autoconfiguring, Installing
+@section Compiling and installing
+
+Once Usernet has been autoconfigured, you should be able to type
+@example
+$ make
+...
+$ su root
+Password: 
+# make install
+...
+@end example
+@noindent
+and the module will compile and install.
+
+You'll need to create the character devices to allow processes to talk to
+Userdev.  The script @code{makedev.unet} will create the appropriate
+devices.  Run without any options, it will create devices appropriate to the
+configuration passed to @code{configure}.  Type
+@example
+makedev.unet --help
+@end example
+@noindent
+for information about the options it supports: they're not particularly
+useful if you got the configuration right.
+
+If you later change your configuration, run @code{makedev.unet} again and it
+will set everything straight.
+
+
+@c --------------------------------------------------------------------------
+@node Configuring attachments, Programming, Installing, Top
+@chapter The @code{unetcfg} program
+
+
+A Usernet attachment can be interrogated and configured using the
+@code{unetcfg} program supplied.
+
+@menu
+* Invoking unetcfg::            Command line options
+* Selecting attachments::       Setting the current attachment
+* Attachment status::           Querying current status information
+* Protocol settings::           Setting the protocol for outgoing packets
+* Setting debugging options::   Various debugging settings
+@end menu
+
+
+@node Invoking unetcfg, Selecting attachments, Configuring attachments, Configuring attachments
+@section Invoking @code{unetcfg}
+
+The @code{unetcfg} program is called as:
+@example
+unetcfg [@var{option}@dots{}] @var{command}@dots{}
+@end example
+
+The various @var{option}s supported are as follows:
+@table @samp
+
+@item -h
+@itemx --help
+Displays a helpful and informative summary of @code{unetcfg}'s option
+syntax.
+
+@item -V
+@itemx --version
+Displays the version number of your copy of @code{unetcfg}.
+
+@item -v
+@itemx --verbose
+Enables output of largely useless status messages.  These might be of use
+when @code{unetcfg} doesn't seem to be doing what you want it to.
+
+@end table
+
+Each @var{command} is executed in turn, from left to right.  The command
+namees may be abbreviated, as long as the abbreviation is not ambiguous.
+
+Most of the commands work with a @dfn{current attachment}, which is assumed
+to be standard input by default.  The current attachment may be changed using
+the @code{select} and @code{fd} commands (@pxref{Selecting attachments}).
+
+
+@node Selecting attachments, Attachment status, Invoking unetcfg, Configuring attachments
+@section Changing the current attachment
+
+These commands change the current attachment.  You can use them as often as
+you like in a single invocation of @code{unetcfg}.
+
+@deffn Command select @var{filename}
+Selects @var{filename} as the current attachment.  Further operations will be
+performed on the named device.
+
+The command name @code{select} is optional: an argument which isn't a command
+name is assumed to be a filename to select.
+@end deffn
+
+@deffn Command fd @var{filedesc}
+Selects an open file descriptor to be the current attachment.  As well as
+boring old file descriptor numbers, you can use the names @code{stdin},
+@code{stdout} and @code{stderr}.
+
+Note that it's really silly to set the current attachment to be standard
+output and then perform commands which write to stdout:
+@example
+unetcfg fd stdout show
+@end example
+@noindent
+Don't do this.
+@end deffn
+
+
+@node Attachment status, Protocol settings, Selecting attachments, Configuring attachments
+@section Attachment status
+
+These commands write useful information about the current attachment to
+standard output.
+
+@deffn Command show
+Writes information about the current attachment to standard output.  The
+format of the information is not intended to be processed by other programs,
+and may vary between releases of the software.
+@end deffn
+
+@deffn Command ifname
+Writes the name of the currently attached network interface to standard
+error.  This can be useful in configuration scripts.  For example:
+@example
+ifname=`unetcfg fd 3 ifname`
+ifconfig $ifname localend pointopoint remoteend
+@end example
+@end deffn
+
+@node Protocol settings, Setting debugging options, Attachment status, Configuring attachments
+@section Protocol settings
+
+Each packet received by a network interface must have a protocol stamped on
+it.  Packets injected by writing to a Usernet-attached device are stamped
+with the attachment's current protocol.  The following command allows the
+current attachment's protocol to be set.
+
+@deffn Command protocol @var{proto}
+Sets the protocol stamped on packets injected through the current
+attachment.  A list of currently known protocols may be obtained by
+specifying the special protocol name @code{help}.  The default protocol is
+always IP.
+@end deffn
+
+
+@node Setting debugging options,  , Protocol settings, Configuring attachments
+@section Setting debugging options
+
+
+
+@deffn Command help [@var{command}]
+With no arguments, displays a summary of the commands available.  With a
+@var{command} argument, displays help on that command.
+@end deffn
+
+
+@c --------------------------------------------------------------------------
+@node Programming,  , Configuring attachments, Top
+@chapter Programming Usernet
+
+This chapter documents Usernet's programming interface.  It's not
+particularly complicated, you'll be glad to hear.
+
+@menu
+* Opening and closing::         Opening and closing attachments
+* Configuring the interface::   How to configure an attached interface
+* Sending and receiving::       Sending and receiving network packets
+@end menu
+
+
+@node Opening and closing, Configuring the interface, Programming, Programming
+@section Opening and closing
+
+Opening and closing Usernet devices is simple and obvious.  Calling
+@code{open}(2) on the appropriate special file opens the device.  What
+happens now depends on whether the device has a persistent attachment to a
+network interface:
+
+@itemize @bullet
+@item If the device has a persistent attachment, a check is made to see
+whether the device has already been opened by another process.  If this is
+the case, @code{open} returns @code{EBUSY}.  If the device was not already
+opened, it is marked as open and a file descriptor is returned.
+
+@item If the device does not have a persistent attachment, a fresh network
+interface is allocated and attached to the device.  A file descriptor for the
+opened device is returned.
+@end itemize
+
+Closing a transiently attached device will release and destroy the attached
+network interface.
+
+
+@node Configuring the interface, Sending and receiving, Opening and closing, Programming
+@section Configuring the interface
+
+Usernet interfaces can be configured using some simple @code{ioctl}(2) calls
+supported by the Usernet character devices.  The constants and data
+structures required are defined in the header file @file{unet.h} provided in
+the distribution.
+
+Most of the configuration work is performed on the network interface, and
+this is done using the traditional @code{ioctl} calls on an open socket's
+file descriptor.
+
+The following @code{ioctl} calls are provided for configuring Usernet
+attachments.
+
+@deffn {@code{ioctl} call} UNIOCGINFO
+Returns the a summary of the attachment's current configuration.  The
+argument is a pointer to a structure of type @code{struct
+unet_info}, which contains the following members:
+@table @code
+
+@item char uni_ifname[UNET_NAMEMAX];
+Interface name string.  This may be passed to the @code{ifconfig} program, or
+to the interface configuration @code{ioctl} calls to configure the attached
+network interface.
+
+@item unsigned short uni_mtu;
+Maximum transmission unit of the attached interface.  This is also available
+by calling @code{SIOCGIFMTU}, and may be set by calling @code{SIOCSIFMTU}.
+
+@item unsigned short uni_family;
+Address family of the attached interface.  This is usually @code{AF_UNIX},
+although it may be changed by calling @code{SIOCSIFADDR}.
+
+@item unsigned short uni_proto;
+Network protocol number stamped onto packets to be sent from the attached
+network interface.  The default, which is probably good enough, is
+@code{ETH_P_IP}.  This field may be changed by calling @code{UNIOCSPROTO}.
+
+@item unsigned int uni_flags;
+An inclusive-OR of the following possible values:
+@table @code
+@item UNIF_TRANS
+Attachment is transient.
+@item UNIF_OPEN
+Currently always set.  Ignore this bit.
+@item UNIF_DEBUG
+Debugging enabled on this interface.
+@end table
+
+@end table
+
+Example:
+@example
+struct unet_info uni;
+int fd = open("/dev/unet", O_RDWR);
+if (fd < 0)
+  die("couldn't open /dev/unet: %s", strerror(errno));
+if (ioctl(fd, UNIOCGINFO, &uni) < 0)
+  die("couldn't get config information: %s", strerror(errno));
+printf("interface name = `%s'\n", uni.uni_ifname);
+@end example
+
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCSDEBUG
+Sets or clears the debug state for a Usernet attachment.  If the argument is
+nonzero, the debug flag is set; if zero, the flag is cleared.  When debugging
+is enabled for an attachment, Usernet logs packets sent and received through
+it, and most changes to the attachment's state, to the kernel log.
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCGPROTO
+Reads the protocol number stamped onto packets submitted by an attached
+Usernet interface.  The value returned by the @code{ioctl} call is identical
+to the @code{uni_proto} member returned by @code{UNIOCGINFO}.
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCSPROTO
+Sets the protocl number stamped onto outgoing packets.  The protocol number,
+passed as the @code{ioctl}'s argument, must be one of the constants defined
+in @file{linux/if_ether.h}.
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCGGDEBUG
+Reads the global debug flag.  When global debugging is enabled, all newly
+created attachments have debugging turned on automatically, and various
+global events are logged to the kernel log.
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCSGDEBUG
+Sets the global debug flag; if the argument is nonzero, the global debug flag
+is set; if zero, the flag is cleared.
+@end deffn
+
+@deffn {@code{ioctl} call} UNIOCDUMP
+Dumps an attachment's information to the kernel log device.
+@end deffn
+
+
+@node Sending and receiving,  , Configuring the interface, Programming
+@section Sending and receiving
+
+A packet may be sent through a Usernet interface using the standard
+@code{write}(2) system call.  The buffer passed to @code{write} must be a
+complete packet; no coalescing or buffering is performed by Usernet.  It's
+always possible to write to a Usernet device and writing always succeeds
+without blocking.  However, packets may be silently rejected by the network
+stack.
+
+Packets received by a Usernet interface are available to programs via the
+standard @code{read}(2) system call.  If the destination buffer is too small
+for a complete packet, the remainder of the packet is silently discarded.  If
+no packets are available for reading, the process is blocked (unless
+nonblocking I/O was explicitly requested).
+
+Programs may call @code{select}(2) to wait for packets to arrive from an
+attached Usernet device.
+
+
+@c @node Hints,  , Sending and receiving, Programming
+@c @section Hints
+
+@c --------------------------------------------------------------------------
+@contents
+@bye
diff --git a/unetcfg.c b/unetcfg.c
new file mode 100644 (file)
index 0000000..dec5a9a
--- /dev/null
+++ b/unetcfg.c
@@ -0,0 +1,768 @@
+/* -*-c-*-
+ *
+ * $Id: unetcfg.c,v 1.1 2001/01/25 22:03:39 mdw Exp $
+ *
+ * User-space network device support.
+ *
+ * (c) 1998 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Usernet.
+ *
+ * Usernet 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.
+ *
+ * Usernet is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Usernet; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: unetcfg.c,v $
+ * Revision 1.1  2001/01/25 22:03:39  mdw
+ * Initial check-in (somewhat belated).
+ *
+ */
+
+/*----- Include files -----------------------------------------------------*/
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/if_ether.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "unet.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+#define VERSION "1.00"
+#define ETH_P_HELP 0xffffu
+
+static const char *quis = "unetcfg";   /* My program's name */
+static int flags = 0;                  /* Various useful status flags */
+static const char *current = "<stdin>";        /* Current Usernet device */
+static int fd = 0;                     /* Default to @stdin@ */
+
+#define f_verbose 1u
+#define f_duff 2u
+#define f_check 4u
+#define f_close 8u
+
+/*----- Common routines ---------------------------------------------------*/
+
+/* --- @die@ --- *
+ *
+ * Arguments:  @const char *format@ = format string to print
+ *             @...@ = values for the placeholders
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                Reports a fatal error message.
+ */
+
+static void die(const char *format, ...)
+{
+  va_list ap;
+  va_start(ap, format);
+  fputs(quis, stderr);
+  fputs(": ", stderr);
+  vfprintf(stderr, format, ap);
+  fputc('\n', stderr);
+  va_end(ap);
+  exit(EXIT_FAILURE);
+}
+
+/* --- @whiiter@ --- *
+ *
+ * Arguments:  @const char *format@ = format string to print
+ *             @...@ = values for the placeholders
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                Reports a non-fatal error message.
+ */
+
+static void whitter(const char *format, ...)
+{
+  va_list ap;
+
+  if ((flags & f_verbose) == 0)
+    return;
+
+  va_start(ap, format);
+  fputs(quis, stderr);
+  fputs(": ", stderr);
+  vfprintf(stderr, format, ap);
+  fputc('\n', stderr);
+  va_end(ap);
+}
+
+/* --- @lookup@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to a table
+ *             @size_t sz@ = size of the table items
+ *             @const char *s@ = pointer to string to match
+ *
+ * Returns:    Pointer to the matching block.
+ *
+ * Use:                Finds an item in a table.
+ */
+
+static void *lookup(const void *p, size_t sz, const char *s)
+{
+  const char **q = (const char **)p;
+  const void *e = 0;
+
+  while (*q) {
+    const char *x = *q, *y = s;
+
+    for (;;) {
+      if (!*y) {
+       if (!*x)
+         return ((void *)q);
+       if (e)
+         die("ambiguous name `%s'", s);
+       e = q;
+       break;
+      } else if (*x == *y) {
+       x++;
+       y++;
+       continue;
+      } else
+       break;
+    }
+    q = (const char **)((const char *)q + sz);
+  }
+  return ((void *)e);
+}
+
+#define LOOKUP(tbl, key) lookup((tbl), sizeof((tbl)[0]), (key))
+
+/* --- @check@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Ensures that the current device really is a Usernet device.
+ */
+
+static void check(void)
+{
+  if (flags & f_check)
+    return;
+  if (ioctl(fd, UNIOCGINFO, 0) < 0)
+    die("file `%s' is not a Usernet device", current);
+  flags |= f_check;
+}
+
+/*----- Command implementations -------------------------------------------*/
+
+/* --- Forward declarations --- */
+
+struct command {
+  const char *cmd;                     /* Command name */
+  int minarg;                          /* Minimum number of args */
+  int (*func)(char **av);              /* Command handler function */
+  const char *syn, *shelp, *vhelp;     /* Short and verbose help */
+};
+
+static struct command cmdtab[];
+
+struct eth_proto {
+  const char *name;                    /* User-level protocol name */
+  unsigned short proto;                        /* Protocol number */
+  const char *desc;                    /* Readable description */
+};
+
+static struct eth_proto eth_prototab[];
+
+#define YNQ_YES 1
+#define YNQ_NO 0
+#define YNQ_QUERY (-1)
+
+static struct ynq {
+  const char *s;
+  int val;
+} ynqtab[] = {
+  { "query",   YNQ_QUERY },
+  { "show",    YNQ_QUERY },
+  { "?",       YNQ_QUERY },
+  { "yes",     YNQ_YES },
+  { "on",      YNQ_YES },
+  { "no",      YNQ_NO },
+  { "off",     YNQ_NO },
+  { 0,         0 }
+};
+
+static int run(char **av);
+
+/* --- @ynq@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to string to decode
+ *
+ * Returns:    One of the @YNQ@ codes.
+ *
+ * Use:                Decodes a `yes/no/query' response.
+ */
+
+static int ynq(const char *p)
+{
+  struct ynq *ynq = LOOKUP(ynqtab, p);
+  if (!ynq)
+    die("unknown setting `%s': I understand `on', `off' and `query'", p);
+  return (ynq->val);
+}
+
+/* --- @cmd_help@ --- */
+
+static int cmd_help(char **av)
+{
+  struct command *c;
+
+  if (!*av) {
+    puts("Commands provided:\n");
+    for (c = cmdtab; c->cmd; c++)
+      printf("%-30s%s\n", c->syn, c->shelp);
+  } else if ((c = LOOKUP(cmdtab, *av)) == 0)
+    die("unknown command: `%s'", *av);
+  else {
+    fputs(c->syn, stdout);
+    fputs("\n\n", stdout);
+    fputs(c->vhelp, stdout);
+    av++;
+  }
+  return (run(av));
+}
+
+/* --- @cmd_select@ --- */
+
+static int cmd_select(char **av)
+{
+  const char *fn = *av++;
+  if (flags & f_close) {
+    close(fd);
+    whitter("closed previous device `%s'", current);
+  }
+  fd = open(fn, O_RDWR);
+  if (fd < 0)
+    die("couldn't open Usernet device `%s': %s", fn, strerror(errno));
+  current = fn;
+  flags &= ~f_check;
+  check();
+  whitter("opened new device: `%s'", fn);
+  flags |= f_close;
+  return (run(av));
+}
+
+/* --- @cmd_fd@ --- */
+
+static int cmd_fd(char **av)
+{
+  const char *p;
+  static char namebuf[20]; /* XXX Big enough? */
+
+  static struct {
+    const char *name;
+    int fd;
+    const char *desc;
+  } *fdp, fdtab[] = {
+    { "stdin", 0, "<stdin>" },
+    { "stdout",        1, "<stdout>" },
+    { "stderr",        2, "<stderr>" },
+    { 0,       0, 0 }
+  };
+
+  if (flags & f_close) {
+    close(fd);
+    whitter("closed previous device `%s'", current);
+  }
+
+  p = *av++;
+  if ((fdp = LOOKUP(fdtab, p)) != 0)
+    fd = fdp->fd;
+  else if (sscanf(p, "%i", &fd) < 1 || fd < 0)
+    die("bad file descriptor name: `%s'", p);
+
+  flags &= ~f_check | f_close;
+  if (fd < 3)
+    current = fdtab[fd].desc;
+  else
+    current = namebuf, sprintf(namebuf, "<fd %i>", fd);
+
+  check();
+  whitter("opened device on `%s'", current);
+
+  return (run(av));
+}
+
+/* --- @cmd_show@ --- */
+
+static int cmd_show(char **av)
+{
+  struct unet_info uni;
+  const char *af, *proto;
+
+  /* --- Name table for address families --- */
+
+  static const char *aftab[] = {
+    "Unspecified",
+    "Unix file domain (!)",
+    "Internet (IPv4)",
+    "Amateur radio AX.25",
+    "Novell IPX",
+    "Appletalk",
+    "Amateur radio NetROM",
+    "Multiprotocol bridge (!)",
+    "Reserved (for ATM)",
+    "Reserved (for X.25)",
+    "Internet (IPv6)",
+  };
+
+  /* --- Name table for Usernet flags --- */
+
+  static struct {
+    const char *name;
+    unsigned int f;
+  } *flp, fltab[] = {
+    { "trans", UNIF_TRANS },
+    /* @{ "open", UNIF_OPEN }@ -- ignore: this flag always appears on */
+    { "debug", UNIF_DEBUG },
+    { 0, 0 }
+  };
+
+  /* --- Read the attachment information --- */
+
+  check();
+  if (ioctl(fd, UNIOCGINFO, &uni) < 0)
+    die("couldn't read information about attachment: %s", strerror(errno));
+
+  /* --- Look the address family up --- */
+
+  if (uni.uni_family < sizeof(aftab) / sizeof(aftab[0]))
+    af = aftab[uni.uni_family];
+  else
+    af = "Unknown family";
+
+  /* --- Look the protocol up --- */
+
+  {
+    struct eth_proto *ep;
+
+    proto = "unknown";
+    for (ep = eth_prototab; ep->name; ep++) {
+      if (ep->proto == uni.uni_proto) {
+       proto = ep->desc;
+       break;
+      }
+    }
+  }
+
+  /* --- Display appropriate information --- */
+
+  printf("Interface name: %s\n", uni.uni_ifname);
+  printf("MTU: %u\n", uni.uni_mtu);
+  printf("Address family: %s\n", af);
+  printf("Protocol: %s\n", proto);
+  printf("Flags:");
+
+  for (flp = fltab; flp->name; flp++) {
+    if (uni.uni_flags & flp->f) {
+      putchar(' ');
+      fputs(flp->name, stdout);
+    }
+  }
+  putchar('\n');
+
+  /* --- Done --- */
+
+  return (run(av));
+}
+
+/* --- @cmd_ifname@ --- */
+
+static int cmd_ifname(char **av)
+{
+  struct unet_info uni;
+
+  check();
+  if (ioctl(fd, UNIOCGINFO, &uni) < 0)
+    die("couldn't read attachment information: %s", strerror(errno));
+  puts(uni.uni_ifname);
+  return (run(av));
+}
+
+/* --- @cmd_protocol@ --- */
+
+static int cmd_protocol(char **av)
+{
+  const char *pn = *av++;
+  const struct eth_proto *ep;
+
+  if ((ep = LOOKUP(eth_prototab, pn)) == 0)
+    die("unknown protocol name `%s'", pn);
+
+  if (ep->proto == ETH_P_HELP) {
+    for (ep = eth_prototab; ep->name; ep++) {
+      if (ep->proto != ETH_P_HELP)
+       printf("%s -- %s\n", ep->name, ep->desc);
+    }
+  } else {
+    check();
+    if (ioctl(fd, UNIOCSPROTO, ep->proto) < 0)
+      die("couldn't set protocol `%s': %s", ep->name, strerror(errno));
+    whitter("set protocol `%s' for `%s'", ep->name, current);
+  }
+
+  return (run(av));
+}
+
+/* --- @cmd_debug@ --- */
+
+static int cmd_debug(char **av)
+{
+  check();
+  switch (ynq(*av++)) {
+
+    case YNQ_QUERY: {
+      struct unet_info uni;
+
+      if (ioctl(fd, UNIOCGINFO, &uni))
+       die("error reading debug state: %s", strerror(errno));
+      printf("debugging for `%s' is %s\n", current,
+            uni.uni_flags & UNIF_DEBUG ? "enabled" : "disabled");
+    } break;
+
+    case YNQ_YES:
+      if (ioctl(fd, UNIOCSDEBUG, 1) < 0)
+       die("error setting debug state: %s", strerror(errno));
+      whitter("set debugging for `%s'", current);
+      break;
+
+    case YNQ_NO:
+      if (ioctl(fd, UNIOCSDEBUG, 0) < 0)
+       die("error clearing debug state: %s", strerror(errno));
+      whitter("cleared debugging for `%s'", current);
+      break;
+  }
+
+  return (run(av));
+}
+      
+/* --- @cmd_gdebug@ --- */
+
+static int cmd_gdebug(char **av)
+{
+  check();
+  switch (ynq(*av++)) {
+
+    case YNQ_QUERY: {
+      int i = ioctl(fd, UNIOCGGDEBUG);
+      if (i < 0)
+       die("error reading global debug state: %s", strerror(errno));
+      else
+       printf("global debugging is %s\n", i ? "enabled" : "disabled");
+    } break;
+
+    case YNQ_YES:
+      if (ioctl(fd, UNIOCSGDEBUG, 1) < 0)
+       die("error setting global debug state: %s", strerror(errno));
+      whitter("set global debugging");
+      break;
+
+    case YNQ_NO:
+      if (ioctl(fd, UNIOCSGDEBUG, 0) < 0)
+       die("error clearing global debug state: %s", strerror(errno));
+      whitter("cleared global debugging");
+      break;
+  }
+
+  return (run(av));
+}
+
+/* --- @cmd_maxif@ --- */
+
+static int cmd_maxif(char **av)
+{
+  check();
+  if (!*av) {
+    int i = ioctl(fd, UNIOCGMAXIF);
+    if (i < 0)
+      die("error reading maxif: %s", strerror(errno));
+    else
+      printf("interface maximum is %d\n", i);
+  } else {
+    char *p = *av++, *q;
+    long i = strtol(p, &q, 0);
+    if (*q)
+      die("malformed integer: %s", p);
+    if (ioctl(fd, UNIOCSMAXIF, i))
+      die("error setting maxif: %s", strerror(errno));
+  }
+
+  return (run(av));
+}
+
+/* --- @run@ --- *
+ *
+ * Arguments:  @char **av@ = array of command line arguments
+ *
+ * Returns:    Zero for success, nonzero for failure
+ *
+ * Use:                Handles a sequence of commands.
+ */
+
+static int run(char **av)
+{
+  struct command *c;
+  int i;
+
+  if (!*av)
+    return (EXIT_SUCCESS);
+  if ((c = LOOKUP(cmdtab, *av)) == 0)
+    c = &cmdtab[0];
+  else
+    av++;
+  if (!c->func)
+    die("command `%s' not implemented", c->cmd);
+  for (i = 0; i < c->minarg; i++) {
+    if (!av[i])
+      die("Usage: %s", c->syn);
+  }
+  return (c->func(av));
+}
+
+/* --- The command definition block --- */
+
+static struct command cmdtab[] = {
+
+  { "select", 1, cmd_select,
+    "[select] FILE", "select a Usernet device", "\
+Selects FILE as the current device.  Each command acts only on the current\n\
+device.  The word `select' may be omitted if desired.\n\
+" },
+
+  {
+    "fd", 1, cmd_fd,
+    "fd NUMBER", "select Usernet device from a file descriptor", "\
+Selects the file descriptor NUMBER as the current device.  This allows\n\
+configuration of already existing transient attachments which would\n\
+otherwise be unnecessarily awkward.\n\
+" },
+
+  { "show", 0, cmd_show,
+    "show", "show status of device", "\
+Displays status information about the current device.\n\
+" },
+
+  { "ifname", 0, cmd_ifname,
+    "ifname", "print attached network interface name", "\
+Displays the name of the network interface attached to the current device.\n\
+This is useful in configuration scripts, for example.\n\
+" },
+
+  { "protocol", 1, cmd_protocol,
+    "protocol PROT", "selects PROT as the device's protocol", "\
+Sets PROT as the current device's protocol.  All packets received by the\n\
+device are stamped with this protocol tag.  To see a list of currently\n\
+known protocols, use the `help' protocol.\n\
+" },
+
+  { "debug", 1, cmd_debug,
+    "debug on|off|query", "set attachment debugging state", "\
+Set the current debugging state for the attachment.  When debugging is\n\
+enabled, all packets flowing through the attachment are logged, along with\n\
+a large number of other informative messages.  Note: the logs generated\n\
+tend to be very large, so don't flood the interface with data while\n\
+debugging is enabled!\n\
+" },
+
+  { "gdebug", 1, cmd_gdebug,
+    "gdebug on|off|query", "set global debugging state", "\
+Set the global debugging state for Usernet.  This controls the emission\n\
+emission of various non-attachment-specific message.  Also, new\n\
+attachments inherit their debug flags from the global flag.\n\
+" },
+
+  { "maxif", 0, cmd_maxif,
+    "maxif [MAX]", "set maximum number of interfaces allowed", "\
+Configures the maximum number of interfaces allowed (actually, the highest\n\
+number of any interface).\n\
+" },
+
+  { "help", 0, cmd_help,
+    "help [COMMAND]", "display help about COMMAND", "\
+If COMMAND is given, display help about it.  If no COMAMND is specified,\n\
+provide general help.\n\
+" },
+
+  { 0, 0, 0, 0 }
+};
+
+/* --- Protocol description table --- */
+
+static struct eth_proto eth_prototab[] = {
+  { "loop",            ETH_P_LOOP,     "Ethernet loopback" },
+  { "echo",            ETH_P_ECHO,     "Ethernet echo" },
+  { "pup",             ETH_P_PUP,      "Xerox PUP" },
+  { "ip",              ETH_P_IP,       "Internet IP" },
+  { "x25",             ETH_P_X25,      "CCITT X.25" },
+  { "arp",             ETH_P_ARP,      "Address resolution protocol" },
+  { "bpq",             ETH_P_BPQ,      "G8BPQ AX.25" },
+  { "dec",             ETH_P_DEC,      "DEC assigned" },
+  { "dna-dl",          ETH_P_DNA_DL,   "DEC DNA dump/load" },
+  { "dna-rcon",                ETH_P_DNA_RC,   "DEC DNA remote console" },
+  { "dna-route",       ETH_P_DNA_RT,   "DEC DNA routing" },
+  { "lat",             ETH_P_LAT,      "DEC LAT" },
+  { "diag",            ETH_P_DIAG,     "DEC diagnostics" },
+  { "cust",            ETH_P_CUST,     "DEC customer user" },
+  { "sca",             ETH_P_SCA,      "DEC Systems Comminications "
+                                           "Architecture" },
+  { "rarp",            ETH_P_RARP,     "Reverse address resolution" },
+  { "atalk",           ETH_P_ATALK,    "Appletalk DDP" },
+  { "aarp",            ETH_P_AARP,     "Appletalk AARP" },
+  { "ipx",             ETH_P_IPX,      "Novell IPX" },
+  { "ipv6",            ETH_P_IPV6,     "Internet IPv6" },
+  { "help",            ETH_P_HELP,     "<magical help token>" },
+  { 0,                 0,              0 }
+};
+
+/*----- Informative messages ----------------------------------------------*/
+
+/* --- @usage@ --- *
+ *
+ * Arguments:  @FILE *fp@ = stream to write on
+ *
+ * Returns:    ---
+ *
+ * Use:                Displays usage information for the program.
+ */
+
+static void usage(FILE *fp)
+{
+  fprintf(fp, "Usage: %s [-v] command ...\n", quis);
+}
+
+/* --- @version@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Displays the system's version number.
+ */
+
+static void version(void)
+{
+  printf("%s version %s\n", quis, VERSION);
+}
+
+/* --- @help@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                Displays some help and quits.
+ */
+
+static void help(void)
+{
+  struct command *c;
+  version();
+  putchar('\n');
+  usage(stdout);
+  putchar('\n');
+  puts("Commands provided:\n");
+  for (c = cmdtab; c->cmd; c++)
+    printf("%-30s%s\n", c->syn, c->shelp);
+  exit(0);
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments received
+ *             @char *argv[]@ = pointers to the command line arguments
+ *
+ * Returns:    Zero for success, nonzero for failure
+ *
+ * Use:                Dumps and manipulates Usernet attachments.
+ */
+
+int main(int argc, char *argv[])
+{
+  /* --- Set the program name properly --- */
+
+  if (argc >= 1) {
+    if ((quis = strrchr(argv[0], '/')) == 0)
+      quis = argv[0];
+    else
+      quis++;
+  }
+
+  /* --- Now start parsing options --- */
+
+  for (;;) {
+    int i;
+
+    static struct option opt[] = {
+      { "help",                        0,      0,      'h' },
+      { "usage",               0,      0,      'U' },
+      { "version",             0,      0,      'V' },
+      { "verbose",             0,      0,      'v' },
+      { 0,                     0,      0,      0 }
+    };
+
+    if ((i = getopt_long(argc, argv, "hVv", opt, 0)) < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help();
+       break;
+      case 'U':
+       usage(stdout);
+       exit(0);
+       break;
+      case 'V':
+       version();
+       break;
+      case 'v':
+       flags |= f_verbose;
+       break;
+      default:
+       flags |= f_duff;
+       break;
+    }
+  }
+
+  if (flags & f_duff) {
+    usage(stderr);
+    printf("(Type `%s --help' for more information.)\n", quis);
+    exit(EXIT_FAILURE);
+  }
+
+  return (run(argv + optind));
+}
+
+/*----- That's all, folks -------------------------------------------------*/