From 4e3819cf328abf9cb0e43d76ef1b294a3bffa720 Mon Sep 17 00:00:00 2001 Message-Id: <4e3819cf328abf9cb0e43d76ef1b294a3bffa720.1715564493.git.mdw@distorted.org.uk> From: Mark Wooding Date: Thu, 25 Jan 2001 22:03:40 +0000 Subject: [PATCH] Initial check-in (somewhat belated). Organization: Straylight/Edgeware From: mdw --- .cvsignore | 7 + .links | 9 + .skelrc | 8 + Makefile.am | 49 +++ acconfig.h | 84 ++++ configure.in | 155 +++++++ makedev.unet.in | 140 ++++++ setup | 9 + tests/bidi | 57 +++ tests/packets | 41 ++ tests/vpn.ssh | 51 +++ unet.c | 1087 +++++++++++++++++++++++++++++++++++++++++++++++ unet.h | 160 +++++++ unet.texi | 644 ++++++++++++++++++++++++++++ unetcfg.c | 768 +++++++++++++++++++++++++++++++++ 15 files changed, 3269 insertions(+) create mode 100644 .cvsignore create mode 100644 .links create mode 100644 .skelrc create mode 100644 Makefile.am create mode 100644 acconfig.h create mode 100644 configure.in create mode 100755 makedev.unet.in create mode 100755 setup create mode 100755 tests/bidi create mode 100755 tests/packets create mode 100755 tests/vpn.ssh create mode 100644 unet.c create mode 100644 unet.h create mode 100644 unet.texi create mode 100644 unetcfg.c 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 -------------------------------------------------*/ -- [mdw]