From: mdw Date: Thu, 25 Jan 2001 22:03:40 +0000 (+0000) Subject: Initial check-in (somewhat belated). X-Git-Tag: 1.1.1~4 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/unet/commitdiff_plain/4e3819cf328abf9cb0e43d76ef1b294a3bffa720 Initial check-in (somewhat belated). --- 4e3819cf328abf9cb0e43d76ef1b294a3bffa720 diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..20a417e --- /dev/null +++ b/.cvsignore @@ -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 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 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 index 0000000..ea82408 --- /dev/null +++ b/Makefile.am @@ -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 index 0000000..0b164f4 --- /dev/null +++ b/acconfig.h @@ -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 index 0000000..3932174 --- /dev/null +++ b/configure.in @@ -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 index 0000000..ae0fea3 --- /dev/null +++ b/makedev.unet.in @@ -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 <&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 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 index 0000000..5ea39de --- /dev/null +++ b/tests/bidi @@ -0,0 +1,57 @@ +#! /bin/perl + +open LEFT, "+= 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 index 0000000..09e5b69 --- /dev/null +++ b/tests/packets @@ -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 index 0000000..bbe35bd --- /dev/null +++ b/tests/vpn.ssh @@ -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 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 @@. + */ + +#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 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 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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#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 = ""; /* 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, "" }, + { "stdout", 1, "" }, + { "stderr", 2, "" }, + { 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); + + 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, "" }, + { 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 -------------------------------------------------*/