chiark / gitweb /
Import release 0.1.2 v0.1.2
authorStephen Early <steve@greenend.org.uk>
Thu, 4 Oct 2001 16:57:00 +0000 (17:57 +0100)
committerStephen Early <steve@greenend.org.uk>
Wed, 18 May 2011 12:29:40 +0000 (13:29 +0100)
24 files changed:
INSTALL
Makefile.in
TODO
conffile.c
debian/changelog [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/dirs [new file with mode: 0644]
debian/files [new file with mode: 0644]
debian/init [new file with mode: 0644]
debian/postinst.debhelper [new file with mode: 0644]
debian/postrm.debhelper [new file with mode: 0644]
debian/prerm.debhelper [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/substvars [new file with mode: 0644]
example.conf
modules.c
netlink.c
netlink.h [new file with mode: 0644]
secnet.c
secnet.h
site.c
slip.c [new file with mode: 0644]
tun.c [new file with mode: 0644]

diff --git a/INSTALL b/INSTALL
index af48a93..8ad676f 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -61,11 +61,16 @@ http://www.ucam.org/cam-grin/ may be useful.
 
 * Installation
 
+If you installed the Debian package of secnet, skip to "If installing
+for the first time", below, and note that example.conf can be found in
+/usr/share/doc/secnet/examples.
+
 To install secnet do
 
 $ ./configure
 $ make
 # make install
+# mkdir /etc/secnet
 
 (Note: you may see the following warning while compiling
 conffile.tab.c; I believe this is a bison bug:
@@ -79,7 +84,6 @@ steve@greenend.org.uk.
 
 If installing for the first time, do
 
-# mkdir /etc/secnet
 # cp example.conf /etc/secnet/secnet.conf
 # cd /etc/secnet
 # ssh-keygen -f key -N ""
@@ -105,7 +109,7 @@ is described in the README file at the moment.
 
 You need the following information:
 
-1. a short name for your site, eg.  "greenend".  This is used to
+1. a short name for your site, eg. "greenend".  This is used to
 identify your site in the vpn-sites file.
 
 2. the name your site will use in the key setup protocol,
@@ -113,7 +117,7 @@ eg. "greenend" (these two will usually be similar or the same).
 
 3. the DNS name of the machine that will be the "front-end" for your
 secnet installation.  This will typically be the name of the gateway
-machine for your network, eg.  sinister.dynamic.greenend.org.uk
+machine for your network, eg. sinister.dynamic.greenend.org.uk
 
 secnet does not actually have to run on this machine, as long as the
 machine can be configured to forward UDP packets to the machine that
index 881a726..d025e3a 100644 (file)
@@ -18,7 +18,7 @@
 .PHONY:        all clean realclean dist install
 
 PACKAGE:=secnet
-VERSION:=0.1.1
+VERSION:=0.1.2
 
 @SET_MAKE@
 
@@ -45,15 +45,20 @@ TARGETS:=secnet
 
 OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \
        resolver.o random.o udp.o site.o transform.o netlink.o rsa.o dh.o \
-       serpent.o md5.o version.o
-
-DISTFILES:=COPYING CREDITS INSTALL Makefile.in NOTES README TODO conffile.c \
-       conffile.fl conffile.h conffile.y conffile_internal.h config.h.bot \
-       config.h.in config.h.top configure configure.in dh.c \
-       example-sites-file example.conf install.sh linux md5.c md5.h \
-       modules.c modules.h netlink.c random.c resolver.c rsa.c \
+       serpent.o md5.o version.o tun.o slip.o
+
+DISTFILES:=COPYING CREDITS INSTALL Makefile.in NOTES README TODO \
+       conffile.c conffile.fl conffile.h conffile.y \
+       conffile_internal.h config.h.bot \
+       config.h.in config.h.top configure \
+       configure.in debian dh.c \
+       example-sites-file example.conf \
+       install.sh linux md5.c md5.h \
+       modules.c modules.h netlink.c netlink.h \
+       random.c resolver.c rsa.c \
        secnet.c secnet.h serpent.c serpent.h serpentsboxes.h \
-       site.c stamp-h.in transform.c udp.c unaligned.h util.c util.h
+       site.c slip.c stamp-h.in transform.c tun.c udp.c \
+       unaligned.h util.c util.h
 
 %.c:   %.y
 
@@ -98,6 +103,7 @@ conffile.o: modules.h
 site.c util.c: unaligned.h
 conffile.yy.c: conffile.fl conffile.tab.c
 conffile.tab.c:        conffile.y
+netlink.o tun.o slip.o: netlink.h
 # End of manual dependencies section
 
 secnet:        $(OBJECTS)
@@ -106,7 +112,7 @@ version.c: Makefile
        echo "char version[]=\"secnet-$(VERSION)\";" >version.c
 
 install: all
-       $(INSTALL_PROGRAM) secnet $(sbindir)/`echo secnet|sed '$(transform)'`
+       $(INSTALL_PROGRAM) -D secnet $(sbindir)/`echo secnet|sed '$(transform)'`
 
 clean:
        $(RM) -f *.o *.yy.c *.tab.[ch] $(TARGETS) core version.c
diff --git a/TODO b/TODO
index 811ee1a..aea5e0d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -7,7 +7,11 @@ conffile.c: done
 dh.c: change format to binary from decimal string (without introducing
 endianness problems)
 
-netlink.c: done. jdamery reports tun-old code works on Linux-2.2
+netlink.c: done
+
+slip.c: done. Detect failure of userv-ipif to start.
+
+tun.c: jdamery reports tun-old code works on Linux-2.2.
 Unresolved problem with ioctl(TUNSETIFF) sometimes return EINVAL.
 
 random.c: test
@@ -30,4 +34,10 @@ udp.c: done
 
 util.c: sort out logging
 
-General: check over MBM's patches for BSD.
+General: separate the transforms in transform.c into multiple parts,
+which can then be combined in the configuration file.  Will allow the
+user to plug in different block ciphers, invent an authenticity-only
+mode, etc.
+
+Write scripts to generate the 'real' sites file from a less-expressive
+version that's more easily checked by external tools.
index c9796fc..9e10efa 100644 (file)
@@ -379,6 +379,36 @@ static list_t *makelist(closure_t *self, struct cloc loc,
     return r;
 }
 
+/* Take a list consisting of a closure and some other things. Apply the
+   closure to the other things, and return the resulting list */
+static list_t *map(closure_t *self, struct cloc loc, dict_t *context,
+                  list_t *args)
+{
+    list_t *r=NULL, *al;
+    item_t *ci;
+    closure_t *cl;
+    list_t se;
+    
+    ci=list_elem(args,0);
+    if (ci && ci->type==t_closure) {
+       cl=ci->data.closure;
+       if (!cl->apply) {
+           cfgfatal(loc,"map","closure cannot be applied\n");
+       }
+       for (al=args->next; al; al=al->next) {
+           /* Construct a single-element list */
+           se.next=NULL;
+           se.item=al->item;
+           /* Invoke the closure, append its result to the output */
+           r=list_append_list(r,cl->apply(cl,loc,context,&se));
+       }
+    } else {
+       cfgfatal(loc,"map","you must supply a closure as the "
+                "first argument\n");
+    }
+    return r;
+}
+
 /* Read a file and turn it into a string */
 static list_t *readfile(closure_t *self, struct cloc loc,
                        dict_t *context, list_t *args)
@@ -457,6 +487,7 @@ static dict_t *process_config(struct p_node *c)
 
     add_closure(root,"makelist",makelist);
     add_closure(root,"readfile",readfile);
+    add_closure(root,"map",map);
 
     init_builtin_modules(root);
 
@@ -742,6 +773,24 @@ void dict_read_subnet_list(dict_t *dict, string_t key, bool_t required,
     }
 }
 
+uint32_t string_list_to_word(list_t *l, struct flagstr *f, string_t desc)
+{
+    list_t *i;
+    uint32_t r=0;
+    struct flagstr *j;
+
+    for (i=l; i; i=i->next) {
+       if (i->item->type!=t_string) {
+           cfgfatal(i->item->loc,desc,"all elements of list must be "
+                    "strings\n");
+       }
+       for (j=f; j->name; j++)
+           if (strcmp(i->item->data.string,j->name)==0)
+               r|=j->value;
+    }
+    return r;
+}
+
 dict_t *read_conffile(char *name)
 {
     FILE *conffile;
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..27e885f
--- /dev/null
@@ -0,0 +1,5 @@
+secnet (0.1.2-1) unstable; urgency=low
+
+  * New upstream version.
+
+ -- Stephen Early <steve@greenend.org.uk>  Sat, 29 Jun 2001 15:21:25 +0100
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..461d52a
--- /dev/null
@@ -0,0 +1,12 @@
+Source: secnet
+Section: net
+Priority: extra
+Maintainer: Stephen Early <steve@greenend.org.uk>
+Standards-Version: 3.0.1
+
+Package: secnet
+Architecture: any
+Depends: ${shlibs:Depends}
+Description: VPN software for distributed networks
+ secnet allows multiple private networks, each 'hidden' behind a single
+ globally-routable IP address, to be bridged together.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..c760a78
--- /dev/null
@@ -0,0 +1,67 @@
+This is the Debian GNU/Linux packaged version of secnet.
+
+The developers of this software are:
+  Stephen Early <steve@greenend.org.uk>
+
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+On Debian systems, the complete text of the GNU General Public License
+can be found in `/usr/share/common-licenses/GPL'.
+
diff --git a/debian/dirs b/debian/dirs
new file mode 100644 (file)
index 0000000..80a26ab
--- /dev/null
@@ -0,0 +1,2 @@
+etc/secnet
+usr/sbin
diff --git a/debian/files b/debian/files
new file mode 100644 (file)
index 0000000..e821923
--- /dev/null
@@ -0,0 +1 @@
+secnet_0.1.1-1_i386.deb net extra
diff --git a/debian/init b/debian/init
new file mode 100644 (file)
index 0000000..761c122
--- /dev/null
@@ -0,0 +1,73 @@
+#! /bin/sh
+# /etc/init.d/secnet
+#
+# skeleton     example file to build /etc/init.d/ scripts.
+#              This file should be used to construct scripts for /etc/init.d.
+#
+#              Written by Miquel van Smoorenburg <miquels@cistron.nl>.
+#              Modified for Debian GNU/Linux
+#              by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+# Version:     @(#)skeleton  1.8  03-Mar-1998  miquels@cistron.nl
+#
+
+set -e
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/bnetd
+NAME=secnet
+DESC=VPN server
+
+test -f $DAEMON || exit 0
+test -f /etc/secnet/secnet.conf || exit 0
+
+set -e
+
+case "$1" in
+  start)
+       echo -n "Starting $DESC: "
+       start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       echo "$NAME."
+       ;;
+  stop)
+       echo -n "Stopping $DESC: "
+       start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       echo "$NAME."
+       ;;
+  #reload)
+       #
+       #       If the daemon can reload its config files on the fly
+       #       for example by sending it SIGHUP, do it here.
+       #
+       #       If the daemon responds to changes in its config file
+       #       directly anyway, make this a do-nothing entry.
+       #
+       # echo "Reloading $DESC configuration files."
+       # start-stop-daemon --stop --signal 1 --quiet --pidfile \
+       #       /var/run/$NAME.pid --exec $DAEMON
+  #;;
+  restart|force-reload)
+       #
+       #       If the "reload" option is implemented, move the "force-reload"
+       #       option to the "reload" entry above. If not, "force-reload" is
+       #       just the same as "restart".
+       #
+       echo -n "Restarting $DESC: "
+       start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       sleep 1
+       start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       echo "$NAME."
+       ;;
+  *)
+       N=/etc/init.d/$NAME
+       # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $N {start|stop|restart|force-reload}" >&2
+       exit 1
+       ;;
+esac
+
+exit 0
diff --git a/debian/postinst.debhelper b/debian/postinst.debhelper
new file mode 100644 (file)
index 0000000..2ebe82f
--- /dev/null
@@ -0,0 +1,13 @@
+# Automatically added by dh_installdocs
+if [ "$1" = "configure" ]; then
+       if [ -d /usr/doc -a ! -e /usr/doc/secnet -a -d /usr/share/doc/secnet ]; then
+               ln -sf ../share/doc/secnet /usr/doc/secnet
+       fi
+fi
+# End automatically added section
+# Automatically added by dh_installinit
+if [ -e "/etc/init.d/secnet" ]; then
+       update-rc.d secnet defaults >/dev/null
+       /etc/init.d/secnet start
+fi
+# End automatically added section
diff --git a/debian/postrm.debhelper b/debian/postrm.debhelper
new file mode 100644 (file)
index 0000000..5f1bce9
--- /dev/null
@@ -0,0 +1,5 @@
+# Automatically added by dh_installinit
+if [ "$1" = "purge" ] ; then
+       update-rc.d secnet remove >/dev/null
+fi
+# End automatically added section
diff --git a/debian/prerm.debhelper b/debian/prerm.debhelper
new file mode 100644 (file)
index 0000000..23a07d7
--- /dev/null
@@ -0,0 +1,10 @@
+# Automatically added by dh_installdocs
+if [ \( "$1" = "upgrade" -o "$1" = "remove" \) -a -L /usr/doc/secnet ]; then
+       rm -f /usr/doc/secnet
+fi
+# End automatically added section
+# Automatically added by dh_installinit
+if [ -e "/etc/init.d/secnet" ]; then
+       /etc/init.d/secnet stop
+fi
+# End automatically added section
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..6237c9d
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# This file is public domain software, originally written by Joey Hess. 
+# Modified for secnet by Stephen Early <steve@greenend.org.uk>
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatibility version to use.
+export DH_COMPAT=3
+
+build: build-stamp
+build-stamp:
+       dh_testdir
+
+       # Add here commands to compile the package.
+       ./configure --prefix=/usr --sysconfdir=/etc && $(MAKE)
+
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp
+
+       # Add here commands to clean up after the build process.
+       $(MAKE) realclean
+
+       dh_clean
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       dh_installdirs
+
+       # Add here commands to install the package into debian/<packagename>
+       $(MAKE) prefix=`pwd`/debian/`dh_listpackages`/usr install
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+#      dh_installdebconf       
+       dh_installdocs INSTALL README NOTES TODO
+       dh_installexamples example.conf example-sites-file
+#      dh_installmenu
+#      dh_installlogrotate
+#      dh_installemacsen
+#      dh_installpam
+#      dh_installmime
+       dh_installinit
+#      dh_installcron
+#      dh_installman
+#      dh_installinfo
+       dh_undocumented
+       dh_installchangelogs
+       dh_link
+       dh_strip
+       dh_compress
+       dh_fixperms
+#      dh_makeshlibs
+       dh_installdeb
+#      dh_perl
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
diff --git a/debian/substvars b/debian/substvars
new file mode 100644 (file)
index 0000000..aa787d5
--- /dev/null
@@ -0,0 +1 @@
+shlibs:Depends=libc6 (>= 2.2.3-7), libgmp2
index e3f6011..3b4a32f 100644 (file)
@@ -44,6 +44,7 @@ system {
 # setup-retries         max retransmits of a key setup packet
 # setup-timeout         wait between retransmits of key setup packets, in ms
 # wait-time             wait between unsuccessful key setup attempts, in ms
+# renegotiate-time      set up a new key if we see any traffic after this time
 
 # Use the universal TUN/TAP driver to get packets to and from the kernel
 #  (use tun-old if you are not on Linux-2.4)
@@ -103,7 +104,8 @@ resolver adns {
 };
 
 # log is defined earlier - we share it with the system
-log-events "init","up","down"; # XXX not yet used
+log-events "setup-init","setup-timeout","activate-key","timeout-key","errors",
+       "security";
 
 # A source of random bits for nonces and session keys. The 'no' specifies
 # that it's non-blocking. XXX 'yes' isn't implemented yet.
@@ -125,9 +127,12 @@ include /etc/secnet/sites
 # a newer version. MAKE SURE YOU GET AN AUTHENTIC COPY OF THE FILE - it
 # contains public keys for all sites.
 
-# Do not include your own site in this list!
-
 sites
        site(example-vpn/some-site),
        site(example-vpn/some-other-site),
        site(example-vpn/a-third-site);
+
+# If you want to communicate with all the VPN sites, you can use something
+# like the following instead:
+
+# sites map(site,makelist(example-vpn));
index 8b90606..0d16c2f 100644 (file)
--- a/modules.c
+++ b/modules.c
@@ -10,6 +10,8 @@ extern init_module netlink_module;
 extern init_module rsa_module;
 extern init_module dh_module;
 extern init_module md5_module;
+extern init_module slip_module;
+extern init_module tun_module;
 
 void init_builtin_modules(dict_t *dict)
 {
@@ -23,4 +25,6 @@ void init_builtin_modules(dict_t *dict)
     rsa_module(dict);
     dh_module(dict);
     md5_module(dict);
+    slip_module(dict);
+    tun_module(dict);
 }
index dea05f2..886ce05 100644 (file)
--- a/netlink.c
+++ b/netlink.c
@@ -1,16 +1,5 @@
 /* User-kernel network link */
 
-/* We support a variety of methods for extracting packets from the
-   kernel: userv-ipif, ipif on its own (when we run as root), the
-   kernel TUN driver.  Possible future methods: SLIP to a pty, an
-   external netlink daemon.  There is a performance/security
-   tradeoff. */
-
-/* When dealing with SLIP (to a pty, or ipif) we have separate rx, tx
-   and client buffers.  When receiving we may read() any amount, not
-   just whole packets.  When transmitting we need to bytestuff anyway,
-   and may be part-way through receiving. */
-
 /* Each netlink device is actually a router, with its own IP address.
    We do things like decreasing the TTL and recalculating the header
    checksum, generating ICMP, responding to pings, etc. */
@@ -20,65 +9,8 @@
    reasonably have produced it. */
 
 #include "secnet.h"
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
 #include "util.h"
-
-#ifdef HAVE_LINUX_IF_H
-#include <linux/if.h>
-#include <linux/if_tun.h>
-#endif
-
-/* XXX where do we find if_tun on other platforms? */
-
-#define DEFAULT_BUFSIZE 2048
-#define DEFAULT_MTU 1000
-#define ICMP_BUFSIZE 1024
-
-#define SLIP_END    192
-#define SLIP_ESC    219
-#define SLIP_ESCEND 220
-#define SLIP_ESCESC 221
-
-struct netlink_client {
-    struct subnet_list *networks;
-    netlink_deliver_fn *deliver;
-    void *dst;
-    string_t name;
-    uint32_t link_quality;
-    struct netlink_client *next;
-};
-
-struct netlink_route {
-    struct subnet net;
-    struct netlink_client *c;
-};
-
-/* Netlink provides one function to the device driver, to call to deliver
-   a packet from the device. The device driver provides one function to
-   netlink, for it to call to deliver a packet to the device. */
-
-struct netlink {
-    closure_t cl;
-    struct netlink_if ops;
-    void *dst; /* Pointer to host interface state */
-    string_t name;
-    uint32_t max_start_pad;
-    uint32_t max_end_pad;
-    struct subnet_list networks;
-    struct subnet_list exclude_remote_networks;
-    uint32_t local_address; /* host interface address */
-    uint32_t secnet_address; /* our own address */
-    uint32_t mtu;
-    struct netlink_client *clients;
-    netlink_deliver_fn *deliver_to_host; /* Provided by driver */
-    struct buffer_if icmp; /* Buffer for assembly of outgoing ICMP */
-    uint32_t n_routes; /* How many routes do we know about? */
-    struct netlink_route *routes;
-};
+#include "netlink.h"
 
 /* Generic IP checksum routine */
 static inline uint16_t ip_csum(uint8_t *iph,uint32_t count)
@@ -310,25 +242,12 @@ static bool_t netlink_check(struct netlink *st, struct buffer_if *buf)
     struct iphdr *iph=(struct iphdr *)buf->start;
     uint32_t len;
 
-    if (iph->ihl < 5 || iph->version != 4) {
-       printf("ihl/version check failed\n");
-       return False;
-    }
-    if (buf->size < iph->ihl*4) {
-       printf("buffer size check failed\n");
-       return False;
-    }
-    if (ip_fast_csum((uint8_t *)iph, iph->ihl)!=0) {
-       printf("checksum failed\n");
-       return False;
-    }
+    if (iph->ihl < 5 || iph->version != 4) return False;
+    if (buf->size < iph->ihl*4) return False;
+    if (ip_fast_csum((uint8_t *)iph, iph->ihl)!=0) return False;
     len=ntohs(iph->tot_len);
     /* There should be no padding */
-    if (buf->size!=len || len<(iph->ihl<<2)) {
-       printf("length check failed buf->size=%d len=%d\n",buf->size,len);
-       return False;
-    }
-
+    if (buf->size!=len || len<(iph->ihl<<2)) return False;
     /* XXX check that there's no source route specified */
     return True;
 }
@@ -360,7 +279,7 @@ static void netlink_packet_deliver(struct netlink *st,
        best_quality=0;
        best_match=-1;
        for (i=0; i<st->n_routes; i++) {
-           if (subnet_match(&st->routes[i].net,dest)) {
+           if (st->routes[i].up && subnet_match(&st->routes[i].net,dest)) {
                if (st->routes[i].c->link_quality>best_quality
                    || best_quality==0) {
                    best_quality=st->routes[i].c->link_quality;
@@ -383,6 +302,8 @@ static void netlink_packet_deliver(struct netlink *st,
            if (source!=st->secnet_address) {
                Message(M_ERROR,"netlink_packet_deliver: outgoing packet "
                        "from host that won't fit down any of our tunnels!\n");
+               /* XXX I think this could also occur if a soft tunnel just
+                  went down, but still had packets queued in the kernel. */
                BUF_FREE(buf);
            } else {
                st->deliver_to_host(st->dst,NULL,buf);
@@ -438,8 +359,7 @@ static void netlink_packet_forward(struct netlink *st,
     BUF_ASSERT_FREE(buf);
 }
 
-/* Someone has been foolish enough to address a packet to us. I
-   suppose we should reply to it, just to be polite. */
+/* Deal with packets addressed explicitly to us */
 static void netlink_packet_local(struct netlink *st,
                                 struct netlink_client *client,
                                 struct buffer_if *buf)
@@ -449,7 +369,8 @@ static void netlink_packet_local(struct netlink *st,
     h=(struct icmphdr *)buf->start;
 
     if ((ntohs(h->iph.frag_off)&0xbfff)!=0) {
-       Message(M_WARNING,"%s: fragmented packet addressed to us\n",st->name);
+       Message(M_WARNING,"%s: fragmented packet addressed to secnet; "
+               "ignoring it\n",st->name);
        BUF_FREE(buf);
        return;
     }
@@ -480,19 +401,19 @@ static void netlink_packet_local(struct netlink *st,
     BUF_FREE(buf);
 }
 
-/* Called by site code when remote packet is available */
-/* buf is allocated on entry and free on return */
-static void netlink_from_tunnel(void *sst, void *cst, struct buffer_if *buf)
+/* If cid==NULL packet is from host, otherwise cid specifies which tunnel 
+   it came from. */
+static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
 {
     struct netlink *st=sst;
-    struct netlink_client *client=cst;
+    struct netlink_client *client=cid;
     uint32_t source,dest;
     struct iphdr *iph;
 
     BUF_ASSERT_USED(buf);
     if (!netlink_check(st,buf)) {
-       Message(M_WARNING,"%s: bad IP packet from tunnel %s\n",
-               st->name,client->name);
+       Message(M_WARNING,"%s: bad IP packet from %s\n",
+               st->name,client?client->name:"host");
        BUF_FREE(buf);
        return;
     }
@@ -501,94 +422,87 @@ static void netlink_from_tunnel(void *sst, void *cst, struct buffer_if *buf)
     source=ntohl(iph->saddr);
     dest=ntohl(iph->daddr);
 
-    /* Check that the packet source is in 'nets' and its destination is
-       in st->networks */
-    if (!subnet_matches_list(client->networks,source)) {
-       string_t s,d;
-       s=ipaddr_to_string(source);
-       d=ipaddr_to_string(dest);
-       Message(M_WARNING,"%s: packet from tunnel %s with bad source address "
-               "(s=%s,d=%s)\n",st->name,client->name,s,d);
-       free(s); free(d);
-       BUF_FREE(buf);
-       return;
+    /* Check source */
+    if (client) {
+       /* Check that the packet source is in 'nets' and its destination is
+          in st->networks */
+       if (!subnet_matches_list(client->networks,source)) {
+           string_t s,d;
+           s=ipaddr_to_string(source);
+           d=ipaddr_to_string(dest);
+           Message(M_WARNING,"%s: packet from tunnel %s with bad "
+                   "source address (s=%s,d=%s)\n",st->name,client->name,s,d);
+           free(s); free(d);
+           BUF_FREE(buf);
+           return;
+       }
+    } else {
+       if (!subnet_matches_list(&st->networks,source)) {
+           string_t s,d;
+           s=ipaddr_to_string(source);
+           d=ipaddr_to_string(dest);
+           Message(M_WARNING,"%s: outgoing packet with bad source address "
+                   "(s=%s,d=%s)\n",st->name,s,d);
+           free(s); free(d);
+           BUF_FREE(buf);
+           return;
+       }
     }
-    /* (st->secnet_address needs checking before matching against
-       st->networks because secnet's IP address may not be in the
-       range the host is willing to deal with) */
+    /* (st->secnet_address needs checking before matching destination
+       addresses) */
     if (dest==st->secnet_address) {
-        netlink_packet_local(st,client,buf);
+       netlink_packet_local(st,client,buf);
        BUF_ASSERT_FREE(buf);
        return;
     }
-    if (!subnet_matches_list(&st->networks,dest)) {
-       string_t s,d;
-       s=ipaddr_to_string(source);
-       d=ipaddr_to_string(dest);
-       Message(M_WARNING,"%s: incoming packet from tunnel %s "
-               "with bad destination address "
-               "(s=%s,d=%s)\n",st->name,client->name,s,d);
-       free(s); free(d);
-       BUF_FREE(buf);
-       return;
+    if (client) {
+       if (!subnet_matches_list(&st->networks,dest)) {
+           string_t s,d;
+           s=ipaddr_to_string(source);
+           d=ipaddr_to_string(dest);
+           Message(M_WARNING,"%s: incoming packet from tunnel %s "
+                   "with bad destination address "
+                   "(s=%s,d=%s)\n",st->name,client->name,s,d);
+           free(s); free(d);
+           BUF_FREE(buf);
+           return;
+       }
     }
-
     netlink_packet_forward(st,client,buf);
-
     BUF_ASSERT_FREE(buf);
 }
 
-/* Called by driver code when packet is received from kernel */
-/* cid should be NULL */
-/* buf should be allocated on entry, and is free on return */
-static void netlink_from_host(void *sst, void *cid, struct buffer_if *buf)
+static void netlink_set_softlinks(struct netlink *st, struct netlink_client *c,
+                                 bool_t up)
 {
-    struct netlink *st=sst;
-    uint32_t source,dest;
-    struct iphdr *iph;
-
-    BUF_ASSERT_USED(buf);
-    if (!netlink_check(st,buf)) {
-       Message(M_WARNING,"%s: bad IP packet from host\n",
-               st->name);
-       BUF_FREE(buf);
-       return;
-    }
-    iph=(struct iphdr *)buf->start;
-
-    source=ntohl(iph->saddr);
-    dest=ntohl(iph->daddr);
+    uint32_t i;
 
-    if (!subnet_matches_list(&st->networks,source)) {
-       string_t s,d;
-       s=ipaddr_to_string(source);
-       d=ipaddr_to_string(dest);
-       Message(M_WARNING,"%s: outgoing packet with bad source address "
-               "(s=%s,d=%s)\n",st->name,s,d);
-       free(s); free(d);
-       BUF_FREE(buf);
-       return;
-    }
-    if (dest==st->secnet_address) {
-       netlink_packet_local(st,NULL,buf);
-       BUF_ASSERT_FREE(buf);
-       return;
+    if (!st->routes) return; /* Table has not yet been created */
+    for (i=0; i<st->n_routes; i++) {
+       if (!st->routes[i].hard && st->routes[i].c==c) {
+           st->routes[i].up=up;
+           st->set_route(st->dst,&st->routes[i]);
+       }
     }
-    netlink_packet_forward(st,NULL,buf);
-    BUF_ASSERT_FREE(buf);
 }
 
 static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
 {
+    struct netlink *st=sst;
     struct netlink_client *c=cid;
 
     c->link_quality=quality;
+    if (c->link_quality==LINK_QUALITY_DOWN) {
+       netlink_set_softlinks(st,c,False);
+    } else {
+       netlink_set_softlinks(st,c,True);
+    }
 }
 
 static void *netlink_regnets(void *sst, struct subnet_list *nets,
                             netlink_deliver_fn *deliver, void *dst,
                             uint32_t max_start_pad, uint32_t max_end_pad,
-                            string_t client_name)
+                            bool_t hard_routes, string_t client_name)
 {
     struct netlink *st=sst;
     struct netlink_client *c;
@@ -597,13 +511,33 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
            "max_start_pad=%d, max_end_pad=%d\n",
            nets->entries,max_start_pad,max_end_pad);
 
-    /* Check that nets does not intersect with st->networks or
-       st->exclude_remote_networks; refuse to register if it does. */
+    if (!hard_routes && !st->set_route) {
+       Message(M_ERROR,"%s: this netlink device does not support "
+               "soft routes.\n");
+       return NULL;
+    }
+
+    if (!hard_routes) {
+       /* XXX for now we assume that soft routes require root privilege;
+          this may not always be true. */
+       require_root_privileges=True;
+       require_root_privileges_explanation="netlink: soft routes";
+    }
+
+#if 0
+    /* XXX POLICY: do we check nets against local networks?  If we do,
+       that prevents things like laptop tunnels working.  Perhaps we
+       can have a configuration option for this.  Or, if the admin
+       really doesn't want remote sites to be able to claim local
+       addresses, he can list them in exclude-remote-networks. */
     if (subnet_lists_intersect(&st->networks,nets)) {
        Message(M_ERROR,"%s: site %s specifies networks that "
                "intersect with our local networks\n",st->name,client_name);
        return False;
     }
+#endif /* 0 */
+    /* Check that nets do not intersect st->exclude_remote_networks;
+       refuse to register if they do. */
     if (subnet_lists_intersect(&st->exclude_remote_networks,nets)) {
        Message(M_ERROR,"%s: site %s specifies networks that "
                "intersect with the explicitly excluded remote networks\n",
@@ -616,6 +550,7 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
     c->deliver=deliver;
     c->dst=dst;
     c->name=client_name; /* XXX copy it? */
+    c->hard_routes=hard_routes;
     c->link_quality=LINK_QUALITY_DOWN;
     c->next=st->clients;
     st->clients=c;
@@ -626,6 +561,28 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
     return c;
 }
 
+static void netlink_dump_routes(struct netlink *st)
+{
+    int i;
+    string_t net;
+
+    Message(M_INFO,"%s: routing table:\n",st->name);
+    for (i=0; i<st->n_routes; i++) {
+       net=subnet_to_string(&st->routes[i].net);
+       Message(M_INFO,"%s -> tunnel %s (%s,%s)\n",net,st->routes[i].c->name,
+               st->routes[i].hard?"hard":"soft",
+               st->routes[i].up?"up":"down");
+       free(net);
+    }
+    Message(M_INFO,"%s/32 -> netlink \"%s\"\n",
+           ipaddr_to_string(st->secnet_address),st->name);
+    for (i=0; i<st->networks.entries; i++) {
+       net=subnet_to_string(&st->networks.list[i]);
+       Message(M_INFO,"%s -> host\n",net);
+       free(net);
+    }
+}
+
 static int netlink_compare_route_specificity(const void *ap, const void *bp)
 {
     const struct netlink_route *a=ap;
@@ -653,6 +610,10 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
        for (j=0; j<c->networks->entries; j++) {
            st->routes[i].net=c->networks->list[j];
            st->routes[i].c=c;
+           st->routes[i].up=c->hard_routes; /* Hard routes are always up;
+                                               soft routes default to down */
+           st->routes[i].kup=False;
+           st->routes[i].hard=c->hard_routes;
            i++;
        }
     }
@@ -664,27 +625,15 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
     /* Sort the table in descending order of specificity */
     qsort(st->routes,st->n_routes,sizeof(*st->routes),
          netlink_compare_route_specificity);
-    Message(M_INFO,"%s: routing table:\n",st->name);
-    for (i=0; i<st->n_routes; i++) {
-       string_t net;
-       net=subnet_to_string(&st->routes[i].net);
-       Message(M_INFO,"%s -> tunnel %s\n",net,st->routes[i].c->name);
-       free(net);
-    }
-    Message(M_INFO,"%s/32 -> netlink \"%s\"\n",
-           ipaddr_to_string(st->secnet_address),st->name);
-    for (i=0; i<st->networks.entries; i++) {
-       string_t net;
-       net=subnet_to_string(&st->networks.list[i]);
-       Message(M_INFO,"%s -> host\n",net);
-       free(net);
-    }
+
+    netlink_dump_routes(st);
 }
 
-static netlink_deliver_fn *netlink_init(struct netlink *st,
-                                       void *dst, struct cloc loc,
-                                       dict_t *dict, string_t description,
-                                       netlink_deliver_fn *to_host)
+netlink_deliver_fn *netlink_init(struct netlink *st,
+                                void *dst, struct cloc loc,
+                                dict_t *dict, string_t description,
+                                netlink_route_fn *set_route,
+                                netlink_deliver_fn *to_host)
 {
     st->dst=dst;
     st->cl.description=description;
@@ -693,11 +642,12 @@ static netlink_deliver_fn *netlink_init(struct netlink *st,
     st->cl.interface=&st->ops;
     st->ops.st=st;
     st->ops.regnets=netlink_regnets;
-    st->ops.deliver=netlink_from_tunnel;
+    st->ops.deliver=netlink_incoming;
     st->ops.set_quality=netlink_set_quality;
     st->max_start_pad=0;
     st->max_end_pad=0;
     st->clients=NULL;
+    st->set_route=set_route;
     st->deliver_to_host=to_host;
 
     st->name=dict_read_string(dict,"name",False,"netlink",loc);
@@ -720,524 +670,31 @@ static netlink_deliver_fn *netlink_init(struct netlink *st,
 
     add_hook(PHASE_SETUP,netlink_phase_hook,st);
 
-    return netlink_from_host;
-}
-
-/* Connection to the kernel through userv-ipif */
-
-struct userv {
-    struct netlink nl;
-    int txfd; /* We transmit to userv */
-    int rxfd; /* We receive from userv */
-    string_t userv_path;
-    string_t service_user;
-    string_t service_name;
-    uint32_t txbuflen;
-    struct buffer_if *buff; /* We unstuff received packets into here
-                              and send them to the site code. */
-    bool_t pending_esc;
-    netlink_deliver_fn *netlink_to_tunnel;
-};
-
-static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
-                           int *timeout_io, const struct timeval *tv_now,
-                           uint64_t *now)
-{
-    struct userv *st=sst;
-    *nfds_io=2;
-    fds[0].fd=st->txfd;
-    fds[0].events=POLLERR; /* Might want to pick up POLLOUT sometime */
-    fds[1].fd=st->rxfd;
-    fds[1].events=POLLIN|POLLERR|POLLHUP;
-    return 0;
-}
-
-static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds,
-                           const struct timeval *tv_now, uint64_t *now)
-{
-    struct userv *st=sst;
-    uint8_t rxbuf[DEFAULT_BUFSIZE];
-    int l,i;
-
-    if (fds[1].revents&POLLERR) {
-       printf("userv_afterpoll: hup!\n");
-    }
-    if (fds[1].revents&POLLIN) {
-       l=read(st->rxfd,rxbuf,DEFAULT_BUFSIZE);
-       if (l<0) {
-           fatal_perror("userv_afterpoll: read(rxfd)");
-       }
-       if (l==0) {
-           fatal("userv_afterpoll: read(rxfd)=0; userv gone away?\n");
-       }
-       /* XXX really crude unstuff code */
-       /* XXX check for buffer overflow */
-       BUF_ASSERT_USED(st->buff);
-       for (i=0; i<l; i++) {
-           if (st->pending_esc) {
-               st->pending_esc=False;
-               switch(rxbuf[i]) {
-               case SLIP_ESCEND:
-                   *(uint8_t *)buf_append(st->buff,1)=SLIP_END;
-                   break;
-               case SLIP_ESCESC:
-                   *(uint8_t *)buf_append(st->buff,1)=SLIP_ESC;
-                   break;
-               default:
-                   fatal("userv_afterpoll: bad SLIP escape character\n");
-               }
-           } else {
-               switch (rxbuf[i]) {
-               case SLIP_END:
-                   if (st->buff->size>0) {
-                       st->netlink_to_tunnel(&st->nl,NULL,
-                                             st->buff);
-                       BUF_ALLOC(st->buff,"userv_afterpoll");
-                   }
-                   buffer_init(st->buff,st->nl.max_start_pad);
-                   break;
-               case SLIP_ESC:
-                   st->pending_esc=True;
-                   break;
-               default:
-                   *(uint8_t *)buf_append(st->buff,1)=rxbuf[i];
-                   break;
-               }
-           }
-       }
-    }
-}
-
-/* Send buf to the kernel. Free buf before returning. */
-static void userv_deliver_to_kernel(void *sst, void *cid,
-                                   struct buffer_if *buf)
-{
-    struct userv *st=sst;
-    uint8_t txbuf[DEFAULT_BUFSIZE];
-    uint8_t *i;
-    uint32_t j;
-
-    BUF_ASSERT_USED(buf);
-
-    /* Spit the packet at userv-ipif: SLIP start marker, then
-       bytestuff the packet, then SLIP end marker */
-    /* XXX crunchy bytestuff code */
-    j=0;
-    txbuf[j++]=SLIP_END;
-    for (i=buf->start; i<(buf->start+buf->size); i++) {
-       switch (*i) {
-       case SLIP_END:
-           txbuf[j++]=SLIP_ESC;
-           txbuf[j++]=SLIP_ESCEND;
-           break;
-       case SLIP_ESC:
-           txbuf[j++]=SLIP_ESC;
-           txbuf[j++]=SLIP_ESCESC;
-           break;
-       default:
-           txbuf[j++]=*i;
-           break;
-       }
-    }
-    txbuf[j++]=SLIP_END;
-    if (write(st->txfd,txbuf,j)<0) {
-       fatal_perror("userv_deliver_to_kernel: write()");
-    }
-    BUF_FREE(buf);
+    return netlink_incoming;
 }
 
-static void userv_phase_hook(void *sst, uint32_t newphase)
-{
-    struct userv *st=sst;
-    pid_t child;
-    int c_stdin[2];
-    int c_stdout[2];
-    string_t addrs;
-    string_t nets;
-    string_t s;
-    struct netlink_client *c;
-    int i;
-
-    /* This is where we actually invoke userv - all the networks we'll
-       be using should already have been registered. */
-
-    addrs=safe_malloc(512,"userv_phase_hook:addrs");
-    snprintf(addrs,512,"%s,%s,%d,slip",ipaddr_to_string(st->nl.local_address),
-            ipaddr_to_string(st->nl.secnet_address),st->nl.mtu);
-
-    nets=safe_malloc(1024,"userv_phase_hook:nets");
-    *nets=0;
-    for (c=st->nl.clients; c; c=c->next) {
-       for (i=0; i<c->networks->entries; i++) {
-           s=subnet_to_string(&c->networks->list[i]);
-           strcat(nets,s);
-           strcat(nets,",");
-           free(s);
-       }
-    }
-    nets[strlen(nets)-1]=0;
-
-    Message(M_INFO,"\nuserv_phase_hook: %s %s %s %s %s\n",st->userv_path,
-          st->service_user,st->service_name,addrs,nets);
-
-    /* Allocate buffer, plus space for padding. Make sure we end up
-       with the start of the packet well-aligned. */
-    /* ALIGN(st->max_start_pad,16); */
-    /* ALIGN(st->max_end_pad,16); */
-
-    st->pending_esc=False;
-
-    /* Invoke userv */
-    if (pipe(c_stdin)!=0) {
-       fatal_perror("userv_phase_hook: pipe(c_stdin)");
-    }
-    if (pipe(c_stdout)!=0) {
-       fatal_perror("userv_phase_hook: pipe(c_stdout)");
-    }
-    st->txfd=c_stdin[1];
-    st->rxfd=c_stdout[0];
-
-    child=fork();
-    if (child==-1) {
-       fatal_perror("userv_phase_hook: fork()");
-    }
-    if (child==0) {
-       char **argv;
-
-       /* We are the child. Modify our stdin and stdout, then exec userv */
-       dup2(c_stdin[0],0);
-       dup2(c_stdout[1],1);
-       close(c_stdin[1]);
-       close(c_stdout[0]);
-
-       /* The arguments are:
-          userv
-          service-user
-          service-name
-          local-addr,secnet-addr,mtu,protocol
-          route1,route2,... */
-       argv=malloc(sizeof(*argv)*6);
-       argv[0]=st->userv_path;
-       argv[1]=st->service_user;
-       argv[2]=st->service_name;
-       argv[3]=addrs;
-       argv[4]=nets;
-       argv[5]=NULL;
-       execvp(st->userv_path,argv);
-       perror("netlink-userv-ipif: execvp");
-
-       exit(1);
-    }
-    /* We are the parent... */
-          
-    /* Register for poll() */
-    register_for_poll(st, userv_beforepoll, userv_afterpoll, 2, st->nl.name);
-}
-
-static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
-                          list_t *args)
-{
-    struct userv *st;
-    item_t *item;
-    dict_t *dict;
-
-    st=safe_malloc(sizeof(*st),"userv_apply");
-
-    /* First parameter must be a dict */
-    item=list_elem(args,0);
-    if (!item || item->type!=t_dict)
-       cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
-    
-    dict=item->data.dict;
-
-    st->netlink_to_tunnel=
-       netlink_init(&st->nl,st,loc,dict,
-                    "netlink-userv-ipif",userv_deliver_to_kernel);
-
-    st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
-                                   loc);
-    st->service_user=dict_read_string(dict,"service-user",False,
-                                     "userv-netlink",loc);
-    st->service_name=dict_read_string(dict,"service-name",False,
-                                     "userv-netlink",loc);
-    if (!st->userv_path) st->userv_path="userv";
-    if (!st->service_user) st->service_user="root";
-    if (!st->service_name) st->service_name="ipif";
-    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"userv-netlink",loc);
-    BUF_ALLOC(st->buff,"netlink:userv_apply");
-
-    st->rxfd=-1; st->txfd=-1;
-    add_hook(PHASE_DROPPRIV,userv_phase_hook,st);
-
-    return new_closure(&st->nl.cl);
-}
-
-/* Connection to the kernel through the universal TUN/TAP driver */
+/* No connection to the kernel at all... */
 
-struct tun {
+struct null {
     struct netlink nl;
-    int fd;
-    string_t device_path;
-    string_t interface_name;
-    string_t ifconfig_path;
-    string_t route_path;
-    bool_t tun_old;
-    bool_t search_for_if; /* Applies to tun-old only */
-    struct buffer_if *buff; /* We receive packets into here
-                              and send them to the netlink code. */
-    netlink_deliver_fn *netlink_to_tunnel;
 };
 
-static int tun_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
-                         int *timeout_io, const struct timeval *tv_now,
-                         uint64_t *now)
-{
-    struct tun *st=sst;
-    *nfds_io=1;
-    fds[0].fd=st->fd;
-    fds[0].events=POLLIN|POLLERR|POLLHUP;
-    return 0;
-}
-
-static void tun_afterpoll(void *sst, struct pollfd *fds, int nfds,
-                           const struct timeval *tv_now, uint64_t *now)
-{
-    struct tun *st=sst;
-    int l;
-
-    if (fds[0].revents&POLLERR) {
-       printf("tun_afterpoll: hup!\n");
-    }
-    if (fds[0].revents&POLLIN) {
-       BUF_ALLOC(st->buff,"tun_afterpoll");
-       buffer_init(st->buff,st->nl.max_start_pad);
-       l=read(st->fd,st->buff->start,st->buff->len-st->nl.max_start_pad);
-       if (l<0) {
-           fatal_perror("tun_afterpoll: read()");
-       }
-       if (l==0) {
-           fatal("tun_afterpoll: read()=0; device gone away?\n");
-       }
-       if (l>0) {
-           st->buff->size=l;
-           st->netlink_to_tunnel(&st->nl,NULL,st->buff);
-           BUF_ASSERT_FREE(st->buff);
-       }
-    }
-}
-
-static void tun_deliver_to_kernel(void *sst, void *cid,
-                                 struct buffer_if *buf)
-{
-    struct tun *st=sst;
-
-    BUF_ASSERT_USED(buf);
-
-    /* No error checking, because we'd just throw the packet away anyway */
-    write(st->fd,buf->start,buf->size);
-    BUF_FREE(buf);
-}
-
-static void tun_phase_hook(void *sst, uint32_t newphase)
-{
-    struct tun *st=sst;
-    string_t hostaddr,secnetaddr;
-    uint8_t mtu[6];
-    string_t network,mask;
-    struct netlink_client *c;
-    int i;
-
-    if (st->tun_old) {
-       if (st->search_for_if) {
-           string_t dname;
-           int i;
-
-           /* ASSERT st->interface_name */
-           dname=safe_malloc(strlen(st->device_path)+4,"tun_old_apply");
-           st->interface_name=safe_malloc(8,"tun_phase_hook");
-       
-           for (i=0; i<255; i++) {
-               sprintf(dname,"%s%d",st->device_path,i);
-               if ((st->fd=open(dname,O_RDWR))>0) {
-                   sprintf(st->interface_name,"tun%d",i);
-                   Message(M_INFO,"%s: allocated network interface %s "
-                           "through %s\n",st->nl.name,st->interface_name,
-                           dname);
-                   break;
-               }
-           }
-           if (st->fd==-1) {
-               fatal("%s: unable to open any TUN device (%s...)\n",
-                     st->nl.name,st->device_path);
-           }
-       } else {
-           st->fd=open(st->device_path,O_RDWR);
-           if (st->fd==-1) {
-               fatal_perror("%s: unable to open TUN device file %s",
-                            st->nl.name,st->device_path);
-           }
-       }
-    } else {
-#ifdef HAVE_LINUX_IF_H
-       struct ifreq ifr;
-
-       /* New TUN interface: open the device, then do ioctl TUNSETIFF
-          to set or find out the network interface name. */
-       st->fd=open(st->device_path,O_RDWR);
-       if (st->fd==-1) {
-           fatal_perror("%s: can't open device file %s",st->nl.name,
-                        st->device_path);
-       }
-       memset(&ifr,0,sizeof(ifr));
-       ifr.ifr_flags = IFF_TUN | IFF_NO_PI; /* Just send/receive IP packets,
-                                               no extra headers */
-       if (st->interface_name)
-           strncpy(ifr.ifr_name,st->interface_name,IFNAMSIZ);
-       Message(M_INFO,"%s: about to ioctl(TUNSETIFF)...\n",st->nl.name);
-       if (ioctl(st->fd,TUNSETIFF,&ifr)<0) {
-           fatal_perror("%s: ioctl(TUNSETIFF)",st->nl.name);
-       }
-       if (!st->interface_name) {
-           st->interface_name=safe_malloc(strlen(ifr.ifr_name)+1,"tun_apply");
-           strcpy(st->interface_name,ifr.ifr_name);
-           Message(M_INFO,"%s: allocated network interface %s\n",st->nl.name,
-                   st->interface_name);
-       }
-#else
-       fatal("netlink.c:tun_phase_hook:!tun_old unexpected\n");
-#endif /* HAVE_LINUX_IF_H */
-    }
-    /* All the networks we'll be using have been registered. Invoke ifconfig
-       to set the TUN device's address, and route to add routes to all
-       our networks. */
-
-    hostaddr=ipaddr_to_string(st->nl.local_address);
-    secnetaddr=ipaddr_to_string(st->nl.secnet_address);
-    snprintf(mtu,6,"%d",st->nl.mtu);
-    mtu[5]=0;
-
-    sys_cmd(st->ifconfig_path,"ifconfig",st->interface_name,
-           hostaddr,"netmask","255.255.255.255","-broadcast",
-           "pointopoint",secnetaddr,"mtu",mtu,"up",(char *)0);
-
-    for (c=st->nl.clients; c; c=c->next) {
-       for (i=0; i<c->networks->entries; i++) {
-           network=ipaddr_to_string(c->networks->list[i].prefix);
-           mask=ipaddr_to_string(c->networks->list[i].mask);
-           sys_cmd(st->route_path,"route","add","-net",network,
-                   "netmask",mask,"gw",secnetaddr,(char *)0);
-       }
-    }
-
-    /* Register for poll() */
-    register_for_poll(st, tun_beforepoll, tun_afterpoll, 1, st->nl.name);
-}
-
-#ifdef HAVE_LINUX_IF_H
-static list_t *tun_apply(closure_t *self, struct cloc loc, dict_t *context,
-                        list_t *args)
+static bool_t null_set_route(void *sst, struct netlink_route *route)
 {
-    struct tun *st;
-    item_t *item;
-    dict_t *dict;
-
-    st=safe_malloc(sizeof(*st),"tun_apply");
-
-    /* First parameter must be a dict */
-    item=list_elem(args,0);
-    if (!item || item->type!=t_dict)
-       cfgfatal(loc,"tun","parameter must be a dictionary\n");
-    
-    dict=item->data.dict;
-
-    st->netlink_to_tunnel=
-       netlink_init(&st->nl,st,loc,dict,
-                    "netlink-tun",tun_deliver_to_kernel);
-
-    st->tun_old=False;
-    st->device_path=dict_read_string(dict,"device",False,"tun-netlink",loc);
-    st->interface_name=dict_read_string(dict,"interface",False,
-                                       "tun-netlink",loc);
-    st->ifconfig_path=dict_read_string(dict,"ifconfig-path",
-                                      False,"tun-netlink",loc);
-    st->route_path=dict_read_string(dict,"route-path",
-                                   False,"tun-netlink",loc);
-
-    if (!st->device_path) st->device_path="/dev/net/tun";
-    if (!st->ifconfig_path) st->ifconfig_path="ifconfig";
-    if (!st->route_path) st->route_path="route";
-    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc);
+    struct null *st=sst;
+    string_t t;
 
-    add_hook(PHASE_GETRESOURCES,tun_phase_hook,st);
-
-    return new_closure(&st->nl.cl);
-}
-#endif /* HAVE_LINUX_IF_H */
-
-static list_t *tun_old_apply(closure_t *self, struct cloc loc, dict_t *context,
-                            list_t *args)
-{
-    struct tun *st;
-    item_t *item;
-    dict_t *dict;
-
-    st=safe_malloc(sizeof(*st),"tun_old_apply");
-
-    Message(M_WARNING,"the tun-old code has never been tested. Please report "
-           "success or failure to steve@greenend.org.uk\n");
-
-    /* First parameter must be a dict */
-    item=list_elem(args,0);
-    if (!item || item->type!=t_dict)
-       cfgfatal(loc,"tun","parameter must be a dictionary\n");
-    
-    dict=item->data.dict;
-
-    st->netlink_to_tunnel=
-       netlink_init(&st->nl,st,loc,dict,
-                    "netlink-tun",tun_deliver_to_kernel);
-
-    st->tun_old=True;
-    st->device_path=dict_read_string(dict,"device",False,"tun-netlink",loc);
-    st->interface_name=dict_read_string(dict,"interface",False,
-                                       "tun-netlink",loc);
-    st->search_for_if=dict_read_bool(dict,"interface-search",False,
-                                    "tun-netlink",loc,st->device_path==NULL);
-    st->ifconfig_path=dict_read_string(dict,"ifconfig-path",False,
-                                      "tun-netlink",loc);
-    st->route_path=dict_read_string(dict,"route-path",False,"tun-netlink",loc);
-
-    if (!st->device_path) st->device_path="/dev/tun";
-    if (!st->ifconfig_path) st->ifconfig_path="ifconfig";
-    if (!st->route_path) st->route_path="route";
-    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc);
-
-    /* Old TUN interface: the network interface name depends on which
-       /dev/tunX file we open. If 'interface-search' is set to true, treat
-       'device' as the prefix and try numbers from 0--255. If it's set
-       to false, treat 'device' as the whole name, and require than an
-       appropriate interface name be specified. */
-    if (st->search_for_if && st->interface_name) {
-       cfgfatal(loc,"tun-old","you may not specify an interface name "
-                "in interface-search mode\n");
+    if (route->up!=route->kup) {
+       t=subnet_to_string(&route->net);
+       Message(M_INFO,"%s: setting route %s to state %s\n",st->nl.name,
+               t, route->up?"up":"down");
+       free(t);
+       route->kup=route->up;
+       return True;
     }
-    if (!st->search_for_if && !st->interface_name) {
-       cfgfatal(loc,"tun-old","you must specify an interface name "
-                "when you explicitly specify a TUN device file\n");
-    }
-
-
-    add_hook(PHASE_GETRESOURCES,tun_phase_hook,st);
-
-    return new_closure(&st->nl.cl);
+    return False;
 }
-
-/* No connection to the kernel at all... */
-
-struct null {
-    struct netlink nl;
-};
-
+           
 static void null_deliver(void *sst, void *cid, struct buffer_if *buf)
 {
     return;
@@ -1258,7 +715,8 @@ static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context,
     
     dict=item->data.dict;
 
-    netlink_init(&st->nl,st,loc,dict,"null-netlink",null_deliver);
+    netlink_init(&st->nl,st,loc,dict,"null-netlink",null_set_route,
+                null_deliver);
 
     return new_closure(&st->nl.cl);
 }
@@ -1266,15 +724,5 @@ static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context,
 init_module netlink_module;
 void netlink_module(dict_t *dict)
 {
-    add_closure(dict,"userv-ipif",userv_apply);
-#ifdef HAVE_LINUX_IF_H
-    add_closure(dict,"tun",tun_apply);
-#endif
-    add_closure(dict,"tun-old",tun_old_apply);
     add_closure(dict,"null-netlink",null_apply);
-#if 0
-    /* TODO */
-    add_closure(dict,"pty-slip",ptyslip_apply);
-    add_closure(dict,"slipd",slipd_apply);
-#endif /* 0 */
 }
diff --git a/netlink.h b/netlink.h
new file mode 100644 (file)
index 0000000..faf3282
--- /dev/null
+++ b/netlink.h
@@ -0,0 +1,58 @@
+#ifndef netlink_h
+#define netlink_h
+
+#define DEFAULT_BUFSIZE 2048
+#define DEFAULT_MTU 1000
+#define ICMP_BUFSIZE 1024
+
+struct netlink_client {
+    struct subnet_list *networks;
+    netlink_deliver_fn *deliver;
+    void *dst;
+    string_t name;
+    uint32_t link_quality;
+    bool_t hard_routes;
+    struct netlink_client *next;
+};
+
+struct netlink_route {
+    struct subnet net;
+    bool_t hard;
+    bool_t up;
+    bool_t kup;
+    struct netlink_client *c;
+};
+
+typedef bool_t netlink_route_fn(void *cst, struct netlink_route *route);
+
+/* Netlink provides one function to the device driver, to call to deliver
+   a packet from the device. The device driver provides one function to
+   netlink, for it to call to deliver a packet to the device. */
+
+struct netlink {
+    closure_t cl;
+    struct netlink_if ops;
+    void *dst; /* Pointer to host interface state */
+    string_t name;
+    uint32_t max_start_pad;
+    uint32_t max_end_pad;
+    struct subnet_list networks;
+    struct subnet_list exclude_remote_networks;
+    uint32_t local_address; /* host interface address */
+    uint32_t secnet_address; /* our own address */
+    uint32_t mtu;
+    struct netlink_client *clients;
+    netlink_deliver_fn *deliver_to_host; /* Provided by driver */
+    netlink_route_fn *set_route; /* Provided by driver */
+    struct buffer_if icmp; /* Buffer for assembly of outgoing ICMP */
+    uint32_t n_routes; /* How many routes do we know about? */
+    struct netlink_route *routes;
+};
+
+extern netlink_deliver_fn *netlink_init(struct netlink *st,
+                                       void *dst, struct cloc loc,
+                                       dict_t *dict, string_t description,
+                                       netlink_route_fn *set_route,
+                                       netlink_deliver_fn *to_host);
+
+#endif /* netlink_h */
index be94f95..ab562f1 100644 (file)
--- a/secnet.c
+++ b/secnet.c
@@ -26,6 +26,8 @@ static char *userid=NULL;
 static uid_t uid=0;
 static bool_t background=True;
 static char *pidfile=NULL;
+bool_t require_root_privileges=False;
+string_t require_root_privileges_explanation=NULL;
 
 /* Structures dealing with poll() call */
 struct poll_interest {
@@ -178,22 +180,31 @@ static void setup(dict_t *config)
     /* Pidfile name */
     pidfile=dict_read_string(system,"pidfile",False,"system",loc);
 
+    /* Check whether we need root privileges */
+    if (require_root_privileges && uid!=0) {
+       fatal("the following configured feature (\"%s\") requires "
+             "that secnet retain root privileges while running.\n",
+             require_root_privileges_explanation);
+    }
+
     /* Go along site list, starting sites */
     l=dict_lookup(config,"sites");
     if (!l) {
-       fatal("configuration did not define any remote sites\n");
-    }
-    i=0;
-    while ((site=list_elem(l, i++))) {
-       struct site_if *s;
-       if (site->type!=t_closure) {
-           cfgfatal(site->loc,"system","non-closure in site list");
-       }
-       if (site->data.closure->type!=CL_SITE) {
-           cfgfatal(site->loc,"system","non-site closure in site list");
+       Message(M_WARNING,"secnet: configuration did not define any "
+               "remote sites\n");
+    } else {
+       i=0;
+       while ((site=list_elem(l, i++))) {
+           struct site_if *s;
+           if (site->type!=t_closure) {
+               cfgfatal(site->loc,"system","non-closure in site list");
+           }
+           if (site->data.closure->type!=CL_SITE) {
+               cfgfatal(site->loc,"system","non-site closure in site list");
+           }
+           s=site->data.closure->interface;
+           s->control(s->st,True);
        }
-       s=site->data.closure->interface;
-       s->control(s->st,True);
     }
 }
 
@@ -286,7 +297,6 @@ static void droppriv(void)
     /* Background now, if we're supposed to: we may be unable to write the
        pidfile if we don't. */
     if (background) {
-       printf("goto background\n");
        /* Open the pidfile before forking - that way the parent can tell
           whether it succeeds */
        if (pidfile) {
@@ -309,7 +319,6 @@ static void droppriv(void)
        } else if (p==0) {
            /* Child process - all done, just carry on */
            if (pf) fclose(pf);
-           printf("child\n");
        } else {
            /* Error */
            fatal_perror("cannot fork");
index f5b96b6..3ac5656 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -125,6 +125,12 @@ extern void dict_read_subnet_list(dict_t *dict, string_t key, bool_t required,
                                  string_t desc, struct cloc loc,
                                  struct subnet_list *sl);
 extern uint32_t string_to_ipaddr(item_t *i, string_t desc);
+struct flagstr {
+    string_t name;
+    uint32_t value;
+};
+extern uint32_t string_list_to_word(list_t *l, struct flagstr *f,
+                                   string_t desc);
 
 /***** END of configuration support *****/
 
@@ -199,6 +205,14 @@ typedef void hook_fn(void *self, uint32_t newphase);
 bool_t add_hook(uint32_t phase, hook_fn *f, void *state);
 bool_t remove_hook(uint32_t phase, hook_fn *f, void *state);
 
+extern bool_t require_root_privileges; /* Some features (like netlink
+                                         'soft' routes) require that
+                                         secnet retain root
+                                         privileges.  They should
+                                         indicate that here when
+                                         appropriate. */
+extern string_t require_root_privileges_explanation;
+
 /***** END of program lifetime support *****/
 
 /***** MODULE support *****/
@@ -369,12 +383,14 @@ typedef void netlink_deliver_fn(void *st, void *cid, struct buffer_if *buf);
 #define LINK_QUALITY_UP 3     /* Link active */
 #define MAXIMUM_LINK_QUALITY 3
 typedef void netlink_link_quality_fn(void *st, void *cid, uint32_t quality);
-/* Register for packets from specified networks. Return value is client
-   identifier. */
+/* Register for packets from specified networks. Return value is
+   client identifier.  'hard_route' indicates whether the routes being
+   registered are permanent (hard) or temporary (soft); some types of
+   netlink device can only cope with hard routes. */
 typedef void *netlink_regnets_fn(void *st, struct subnet_list *networks,
                                 netlink_deliver_fn *deliver, void *dst,
                                 uint32_t max_start_pad, uint32_t max_end_pad,
-                                string_t client_name);
+                                bool_t hard_routes, string_t client_name);
 
 struct netlink_if {
     void *st;
diff --git a/site.c b/site.c
index 9bfbf9d..6101b2b 100644 (file)
--- a/site.c
+++ b/site.c
@@ -11,7 +11,8 @@
 
 #define SETUP_BUFFER_LEN 2048
 
-#define DEFAULT_KEY_LIFETIME 3600000
+#define DEFAULT_KEY_LIFETIME 3600000 /* One hour */
+#define DEFAULT_KEY_RENEGOTIATE_GAP 300000 /* Five minutes */
 #define DEFAULT_SETUP_RETRIES 5
 #define DEFAULT_SETUP_TIMEOUT 1000
 #define DEFAULT_WAIT_TIME 10000
@@ -99,12 +100,27 @@ static string_t state_name(uint32_t state)
 #define LOG_SETUP_TIMEOUT 0x00000004
 #define LOG_ACTIVATE_KEY  0x00000008
 #define LOG_TIMEOUT_KEY   0x00000010
-#define LOG_SEC      0x00000020
+#define LOG_SEC           0x00000020
 #define LOG_STATE         0x00000040
 #define LOG_DROP          0x00000080
 #define LOG_DUMP          0x00000100
 #define LOG_ERROR         0x00000400
 
+static struct flagstr log_event_table[]={
+    { "unexpected", LOG_UNEXPECTED },
+    { "setup-init", LOG_SETUP_INIT },
+    { "setup-timeout", LOG_SETUP_TIMEOUT },
+    { "activate-key", LOG_ACTIVATE_KEY },
+    { "timeout-key", LOG_TIMEOUT_KEY },
+    { "security", LOG_SEC },
+    { "state-change", LOG_STATE },
+    { "packet-drop", LOG_DROP },
+    { "dump-packets", LOG_DUMP },
+    { "errors", LOG_ERROR },
+    { "all", 0xffffffff },
+    { NULL, 0 }
+};
+
 struct site {
     closure_t cl;
     struct site_if ops;
@@ -125,12 +141,16 @@ struct site {
     struct transform_if *transform;
     struct dh_if *dh;
     struct hash_if *hash;
-    void *netlink_cid;
 
     uint32_t setup_retries; /* How many times to send setup packets */
     uint32_t setup_timeout; /* Initial timeout for setup packets */
     uint32_t wait_timeout; /* How long to wait if setup unsuccessful */
     uint32_t key_lifetime; /* How long a key lasts once set up */
+    uint32_t key_renegotiate_time; /* If we see traffic (or a keepalive)
+                                     after this time, initiate a new
+                                     key exchange */
+
+    bool_t keepalive; /* Always keep the tunnel up */
 
     uint8_t *setupsig; /* Expected signature of incoming MSG1 packets */
     uint32_t setupsiglen; /* Allows us to discard packets quickly if
@@ -143,6 +163,8 @@ struct site {
     uint32_t state;
     uint64_t now; /* Most recently seen time */
 
+    void *netlink_cid;
+
     uint32_t remote_session_id;
     struct transform_inst_if *current_transform;
     bool_t current_valid;
@@ -178,6 +200,8 @@ static void slog(struct site *st, uint32_t event, string_t msg, ...)
     va_end(ap);
 }
 
+static void set_link_quality(struct site *st);
+static bool_t initiate_key_setup(struct site *st);
 static void enter_state_run(struct site *st);
 static bool_t enter_state_resolve(struct site *st);
 static bool_t enter_state_sentmsg1(struct site *st);
@@ -576,12 +600,7 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
 
     if (!st->current_valid) {
        slog(st,LOG_DROP,"incoming message but no current key -> dropping");
-       if (st->state==SITE_RUN) {
-           slog(st,LOG_SETUP_INIT|LOG_STATE,
-                "now initiating setup of new key");
-           return enter_state_resolve(st);
-       }
-       return False;
+       return initiate_key_setup(st);
     }
 
     if (!unpick_msg0(st,msg0,&m)) return False;
@@ -615,8 +634,8 @@ static void dump_packet(struct site *st, struct buffer_if *buf,
     uint32_t msgtype=ntohl(*(uint32_t *)(buf->start+8));
 
     if (st->log_events & LOG_DUMP)
-       log(st->log,0,"(%s,%s): %s: %08x<-%08x: %08x:",
-           st->localname,st->remotename,incoming?"incoming":"outgoing",
+       log(st->log,0,"%s: %s: %08x<-%08x: %08x:",
+           st->tunname,incoming?"incoming":"outgoing",
            dest,source,msgtype);
 }
 
@@ -661,6 +680,21 @@ static void site_resolve_callback(void *sst, struct in_addr *address)
     }
 }
 
+static bool_t initiate_key_setup(struct site *st)
+{
+    if (st->state!=SITE_RUN) return False;
+    if (st->address) {
+       slog(st,LOG_SETUP_INIT,"initiating key exchange; resolving address");
+       return enter_state_resolve(st);
+    } else if (st->peer_valid) {
+       slog(st,LOG_SETUP_INIT,"initiating key exchange using old "
+            "peer address");
+       st->setup_peer=st->peer;
+       return enter_state_sentmsg1(st);
+    }
+    return False;
+}
+
 static void activate_new_key(struct site *st)
 {
     struct transform_inst_if *t;
@@ -670,7 +704,6 @@ static void activate_new_key(struct site *st)
     st->new_transform=t;
 
     t->delkey(t->st);
-    st->state=SITE_RUN;
     st->timeout=0;
     st->current_valid=True;
     st->current_key_timeout=st->now+st->key_lifetime;
@@ -679,6 +712,7 @@ static void activate_new_key(struct site *st)
     st->remote_session_id=st->setup_session_id;
 
     slog(st,LOG_ACTIVATE_KEY,"new key activated");
+    enter_state_run(st);
 }
 
 static void state_assert(struct site *st, bool_t ok)
@@ -695,16 +729,36 @@ static void enter_state_stop(struct site *st)
     st->current_key_timeout=0;
     
     st->peer_valid=False;
+
+    set_link_quality(st);
     
     st->new_transform->delkey(st->new_transform->st);
 }
 
+static void set_link_quality(struct site *st)
+{
+    uint32_t quality;
+    if (st->current_valid)
+       quality=LINK_QUALITY_UP;
+    else if (st->state==SITE_WAIT || st->state==SITE_STOP)
+       quality=LINK_QUALITY_DOWN;
+    else if (st->address)
+       quality=LINK_QUALITY_DOWN_CURRENT_ADDRESS;
+    else if (st->peer_valid)
+       quality=LINK_QUALITY_DOWN_STALE_ADDRESS;
+    else
+       quality=LINK_QUALITY_DOWN;
+
+    st->netlink->set_quality(st->netlink->st,st->netlink_cid,quality);
+}
+
 static void enter_state_run(struct site *st)
 {
     slog(st,LOG_STATE,"entering state RUN");
     st->state=SITE_RUN;
     st->timeout=0;
-    st->netlink->set_quality(st->netlink->st,st->netlink_cid,LINK_QUALITY_UP);
+
+    set_link_quality(st);
     /* XXX get rid of key setup data */
 }
 
@@ -821,8 +875,7 @@ static void enter_state_wait(struct site *st)
     st->timeout=st->now+st->wait_timeout;
     st->state=SITE_WAIT;
     st->peer_valid=False;
-    st->netlink->set_quality(st->netlink->st,st->netlink_cid,
-                            LINK_QUALITY_DOWN);
+    set_link_quality(st);
     BUF_FREE(&st->buffer); /* will have had an outgoing packet in it */
     /* XXX Erase keys etc. */
 }
@@ -873,6 +926,7 @@ static void site_afterpoll(void *sst, struct pollfd *fds, int nfds,
        st->current_valid=False;
        st->current_transform->delkey(st->current_transform->st);
        st->current_key_timeout=0;
+       set_link_quality(st);
     }
 }
 
@@ -906,18 +960,9 @@ static void site_outgoing(void *sst, void *cid, struct buffer_if *buf)
        return;
     }
 
-    if (st->state==SITE_RUN) {
-       BUF_FREE(buf); /* We throw the outgoing packet away */
-       slog(st,LOG_SETUP_INIT,"initiating key exchange");
-       enter_state_resolve(st);
-       return;
-    }
-
-    /* Otherwise we're in the middle of key setup or a wait - just
-       throw the outgoing packet away */
     slog(st,LOG_DROP,"discarding outgoing packet of size %d",buf->size);
     BUF_FREE(buf);
-    return;
+    initiate_key_setup(st);
 }
 
 /* This function is called by the communication device to deliver
@@ -1124,17 +1169,29 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc);
     st->hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc);
 
-    st->key_lifetime=dict_read_number(dict,"key-lifetime",
-                                     False,"site",loc,DEFAULT_KEY_LIFETIME);
-    st->setup_retries=dict_read_number(dict,"setup-retries",
-                                      False,"site",loc,DEFAULT_SETUP_RETRIES);
-    st->setup_timeout=dict_read_number(dict,"setup-timeout",
-                                      False,"site",loc,DEFAULT_SETUP_TIMEOUT);
-    st->wait_timeout=dict_read_number(dict,"wait-time",
-                                     False,"site",loc,DEFAULT_WAIT_TIME);
-    /* XXX should be configurable */
-    st->log_events=LOG_SEC|LOG_ERROR|
-       LOG_ACTIVATE_KEY|LOG_TIMEOUT_KEY|LOG_SETUP_INIT|LOG_SETUP_TIMEOUT;
+    st->key_lifetime=dict_read_number(
+       dict,"key-lifetime",False,"site",loc,DEFAULT_KEY_LIFETIME);
+    if (st->key_lifetime < DEFAULT_KEY_RENEGOTIATE_GAP)
+       st->key_renegotiate_time=st->key_lifetime/2;
+    else
+       st->key_renegotiate_time=st->key_lifetime-DEFAULT_KEY_RENEGOTIATE_GAP;
+    st->setup_retries=dict_read_number(
+       dict,"setup-retries",False,"site",loc,DEFAULT_SETUP_RETRIES);
+    st->setup_timeout=dict_read_number(
+       dict,"setup-timeout",False,"site",loc,DEFAULT_SETUP_TIMEOUT);
+    st->wait_timeout=dict_read_number(
+       dict,"wait-time",False,"site",loc,DEFAULT_WAIT_TIME);
+    st->key_renegotiate_time=dict_read_number(
+       dict,"renegotiate-time",False,"site",loc,st->key_lifetime);
+    if (st->key_renegotiate_time > st->key_lifetime) {
+       cfgfatal(loc,"site",
+                "renegotiate-time must be less than key-lifetime\n");
+    }
+    /* XXX keepalive will eventually default to True */
+    st->keepalive=dict_read_bool(dict,"keepalive",False,"site",loc,False);
+
+    st->log_events=string_list_to_word(dict_lookup(dict,"log-events"),
+                                      log_event_table,"site");
 
     st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5,
                            "site_apply");
@@ -1171,11 +1228,12 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
                                         site_outgoing, st,
                                         st->transform->max_start_pad+(4*4),
                                         st->transform->max_end_pad,
-                                        st->tunname);
+                                        (st->address!=NULL), st->tunname);
     if (!st->netlink_cid) {
-       fatal("%s: netlink device did not let us register our remote "
-             "networks. Check that they are not local or excluded.\n",
-             st->tunname);
+       Message(M_WARNING,"%s: netlink device did not let us register "
+               "our remote networks. This site will not start.\n",
+               st->tunname);
+       return NULL; /* XXX should free site first */
     }
 
     st->comm->request_notify(st->comm->st, st, site_incoming);
diff --git a/slip.c b/slip.c
new file mode 100644 (file)
index 0000000..2928c1f
--- /dev/null
+++ b/slip.c
@@ -0,0 +1,279 @@
+/* When dealing with SLIP (to a pty, or ipif) we have separate rx, tx
+   and client buffers.  When receiving we may read() any amount, not
+   just whole packets.  When transmitting we need to bytestuff anyway,
+   and may be part-way through receiving. */
+
+#include "secnet.h"
+#include "util.h"
+#include "netlink.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SLIP_END    192
+#define SLIP_ESC    219
+#define SLIP_ESCEND 220
+#define SLIP_ESCESC 221
+
+/* Connection to the kernel through userv-ipif */
+
+struct userv {
+    struct netlink nl;
+    int txfd; /* We transmit to userv */
+    int rxfd; /* We receive from userv */
+    string_t userv_path;
+    string_t service_user;
+    string_t service_name;
+    uint32_t txbuflen;
+    struct buffer_if *buff; /* We unstuff received packets into here
+                              and send them to the site code. */
+    bool_t pending_esc;
+    netlink_deliver_fn *netlink_to_tunnel;
+};
+
+static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
+                           int *timeout_io, const struct timeval *tv_now,
+                           uint64_t *now)
+{
+    struct userv *st=sst;
+    *nfds_io=2;
+    fds[0].fd=st->txfd;
+    fds[0].events=POLLERR; /* Might want to pick up POLLOUT sometime */
+    fds[1].fd=st->rxfd;
+    fds[1].events=POLLIN|POLLERR|POLLHUP;
+    return 0;
+}
+
+static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds,
+                           const struct timeval *tv_now, uint64_t *now)
+{
+    struct userv *st=sst;
+    uint8_t rxbuf[DEFAULT_BUFSIZE];
+    int l,i;
+
+    if (fds[1].revents&POLLERR) {
+       Message(M_ERROR,"%s: userv_afterpoll: hup!\n",st->nl.name);
+    }
+    if (fds[1].revents&POLLIN) {
+       l=read(st->rxfd,rxbuf,DEFAULT_BUFSIZE);
+       if (l<0) {
+           fatal_perror("%s: userv_afterpoll: read(rxfd)",st->nl.name);
+       }
+       if (l==0) {
+           fatal("%s: userv_afterpoll: read(rxfd)=0; userv gone away?\n",
+                 st->nl.name);
+       }
+       /* XXX really crude unstuff code */
+       /* XXX check for buffer overflow */
+       BUF_ASSERT_USED(st->buff);
+       for (i=0; i<l; i++) {
+           if (st->pending_esc) {
+               st->pending_esc=False;
+               switch(rxbuf[i]) {
+               case SLIP_ESCEND:
+                   *(uint8_t *)buf_append(st->buff,1)=SLIP_END;
+                   break;
+               case SLIP_ESCESC:
+                   *(uint8_t *)buf_append(st->buff,1)=SLIP_ESC;
+                   break;
+               default:
+                   fatal("userv_afterpoll: bad SLIP escape character\n");
+               }
+           } else {
+               switch (rxbuf[i]) {
+               case SLIP_END:
+                   if (st->buff->size>0) {
+                       st->netlink_to_tunnel(&st->nl,NULL,
+                                             st->buff);
+                       BUF_ALLOC(st->buff,"userv_afterpoll");
+                   }
+                   buffer_init(st->buff,st->nl.max_start_pad);
+                   break;
+               case SLIP_ESC:
+                   st->pending_esc=True;
+                   break;
+               default:
+                   *(uint8_t *)buf_append(st->buff,1)=rxbuf[i];
+                   break;
+               }
+           }
+       }
+    }
+}
+
+/* Send buf to the kernel. Free buf before returning. */
+static void userv_deliver_to_kernel(void *sst, void *cid,
+                                   struct buffer_if *buf)
+{
+    struct userv *st=sst;
+    uint8_t txbuf[DEFAULT_BUFSIZE];
+    uint8_t *i;
+    uint32_t j;
+
+    BUF_ASSERT_USED(buf);
+
+    /* Spit the packet at userv-ipif: SLIP start marker, then
+       bytestuff the packet, then SLIP end marker */
+    /* XXX crunchy bytestuff code */
+    j=0;
+    txbuf[j++]=SLIP_END;
+    for (i=buf->start; i<(buf->start+buf->size); i++) {
+       switch (*i) {
+       case SLIP_END:
+           txbuf[j++]=SLIP_ESC;
+           txbuf[j++]=SLIP_ESCEND;
+           break;
+       case SLIP_ESC:
+           txbuf[j++]=SLIP_ESC;
+           txbuf[j++]=SLIP_ESCESC;
+           break;
+       default:
+           txbuf[j++]=*i;
+           break;
+       }
+    }
+    txbuf[j++]=SLIP_END;
+    if (write(st->txfd,txbuf,j)<0) {
+       fatal_perror("userv_deliver_to_kernel: write()");
+    }
+    BUF_FREE(buf);
+}
+
+static void userv_phase_hook(void *sst, uint32_t newphase)
+{
+    struct userv *st=sst;
+    pid_t child;
+    int c_stdin[2];
+    int c_stdout[2];
+    string_t addrs;
+    string_t nets;
+    string_t s;
+    struct netlink_route *r;
+    int i;
+
+    /* This is where we actually invoke userv - all the networks we'll
+       be using should already have been registered. */
+
+    addrs=safe_malloc(512,"userv_phase_hook:addrs");
+    snprintf(addrs,512,"%s,%s,%d,slip",ipaddr_to_string(st->nl.local_address),
+            ipaddr_to_string(st->nl.secnet_address),st->nl.mtu);
+
+    nets=safe_malloc(1024,"userv_phase_hook:nets");
+    *nets=0;
+    r=st->nl.routes;
+    for (i=0; i<st->nl.n_routes; i++) {
+       if (r[i].up) {
+           r[i].kup=True;
+           s=subnet_to_string(&r[i].net);
+           strcat(nets,s);
+           strcat(nets,",");
+           free(s);
+       }
+    }
+    nets[strlen(nets)-1]=0;
+
+    Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->nl.name,
+           st->userv_path,st->service_user,st->service_name,addrs,nets);
+
+    /* Allocate buffer, plus space for padding. Make sure we end up
+       with the start of the packet well-aligned. */
+    /* ALIGN(st->max_start_pad,16); */
+    /* ALIGN(st->max_end_pad,16); */
+
+    st->pending_esc=False;
+
+    /* Invoke userv */
+    if (pipe(c_stdin)!=0) {
+       fatal_perror("userv_phase_hook: pipe(c_stdin)");
+    }
+    if (pipe(c_stdout)!=0) {
+       fatal_perror("userv_phase_hook: pipe(c_stdout)");
+    }
+    st->txfd=c_stdin[1];
+    st->rxfd=c_stdout[0];
+
+    child=fork();
+    if (child==-1) {
+       fatal_perror("userv_phase_hook: fork()");
+    }
+    if (child==0) {
+       char **argv;
+
+       /* We are the child. Modify our stdin and stdout, then exec userv */
+       dup2(c_stdin[0],0);
+       dup2(c_stdout[1],1);
+       close(c_stdin[1]);
+       close(c_stdout[0]);
+
+       /* The arguments are:
+          userv
+          service-user
+          service-name
+          local-addr,secnet-addr,mtu,protocol
+          route1,route2,... */
+       argv=malloc(sizeof(*argv)*6);
+       argv[0]=st->userv_path;
+       argv[1]=st->service_user;
+       argv[2]=st->service_name;
+       argv[3]=addrs;
+       argv[4]=nets;
+       argv[5]=NULL;
+       execvp(st->userv_path,argv);
+       perror("netlink-userv-ipif: execvp");
+
+       exit(1);
+    }
+    /* We are the parent... */
+          
+    /* Register for poll() */
+    register_for_poll(st, userv_beforepoll, userv_afterpoll, 2, st->nl.name);
+}
+
+static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
+                          list_t *args)
+{
+    struct userv *st;
+    item_t *item;
+    dict_t *dict;
+
+    st=safe_malloc(sizeof(*st),"userv_apply");
+
+    /* First parameter must be a dict */
+    item=list_elem(args,0);
+    if (!item || item->type!=t_dict)
+       cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
+    
+    dict=item->data.dict;
+
+    st->netlink_to_tunnel=
+       netlink_init(&st->nl,st,loc,dict,
+                    "netlink-userv-ipif",NULL,userv_deliver_to_kernel);
+
+    st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
+                                   loc);
+    st->service_user=dict_read_string(dict,"service-user",False,
+                                     "userv-netlink",loc);
+    st->service_name=dict_read_string(dict,"service-name",False,
+                                     "userv-netlink",loc);
+    if (!st->userv_path) st->userv_path="userv";
+    if (!st->service_user) st->service_user="root";
+    if (!st->service_name) st->service_name="ipif";
+    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"userv-netlink",loc);
+    BUF_ALLOC(st->buff,"netlink:userv_apply");
+
+    st->rxfd=-1; st->txfd=-1;
+    add_hook(PHASE_DROPPRIV,userv_phase_hook,st);
+
+    return new_closure(&st->nl.cl);
+}
+
+init_module slip_module;
+void slip_module(dict_t *dict)
+{
+    add_closure(dict,"userv-ipif",userv_apply);
+#if 0
+    /* TODO */
+    add_closure(dict,"pty-slip",ptyslip_apply);
+    add_closure(dict,"slipd",slipd_apply);
+#endif /* 0 */
+}
diff --git a/tun.c b/tun.c
new file mode 100644 (file)
index 0000000..a36f444
--- /dev/null
+++ b/tun.c
@@ -0,0 +1,307 @@
+#include "secnet.h"
+#include "util.h"
+#include "netlink.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_LINUX_IF_H
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#endif
+
+/* XXX where do we find if_tun on other platforms? */
+
+/* Connection to the kernel through the universal TUN/TAP driver */
+
+struct tun {
+    struct netlink nl;
+    int fd;
+    string_t device_path;
+    string_t interface_name;
+    string_t ifconfig_path;
+    string_t route_path;
+    bool_t tun_old;
+    bool_t search_for_if; /* Applies to tun-old only */
+    struct buffer_if *buff; /* We receive packets into here
+                              and send them to the netlink code. */
+    netlink_deliver_fn *netlink_to_tunnel;
+};
+
+static int tun_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
+                         int *timeout_io, const struct timeval *tv_now,
+                         uint64_t *now)
+{
+    struct tun *st=sst;
+    *nfds_io=1;
+    fds[0].fd=st->fd;
+    fds[0].events=POLLIN|POLLERR|POLLHUP;
+    return 0;
+}
+
+static void tun_afterpoll(void *sst, struct pollfd *fds, int nfds,
+                           const struct timeval *tv_now, uint64_t *now)
+{
+    struct tun *st=sst;
+    int l;
+
+    if (fds[0].revents&POLLERR) {
+       printf("tun_afterpoll: hup!\n");
+    }
+    if (fds[0].revents&POLLIN) {
+       BUF_ALLOC(st->buff,"tun_afterpoll");
+       buffer_init(st->buff,st->nl.max_start_pad);
+       l=read(st->fd,st->buff->start,st->buff->len-st->nl.max_start_pad);
+       if (l<0) {
+           fatal_perror("tun_afterpoll: read()");
+       }
+       if (l==0) {
+           fatal("tun_afterpoll: read()=0; device gone away?\n");
+       }
+       if (l>0) {
+           st->buff->size=l;
+           st->netlink_to_tunnel(&st->nl,NULL,st->buff);
+           BUF_ASSERT_FREE(st->buff);
+       }
+    }
+}
+
+static void tun_deliver_to_kernel(void *sst, void *cid,
+                                 struct buffer_if *buf)
+{
+    struct tun *st=sst;
+
+    BUF_ASSERT_USED(buf);
+
+    /* No error checking, because we'd just throw the packet away anyway */
+    write(st->fd,buf->start,buf->size);
+    BUF_FREE(buf);
+}
+
+static bool_t tun_set_route(void *sst, struct netlink_route *route)
+{
+    struct tun *st=sst;
+    string_t network, mask, secnetaddr;
+
+    if (route->up != route->kup) {
+       network=ipaddr_to_string(route->net.prefix);
+       mask=ipaddr_to_string(route->net.mask);
+       secnetaddr=ipaddr_to_string(st->nl.secnet_address);
+       Message(M_INFO,"%s: %s route %s/%d %s kernel routing table\n",
+               st->nl.name,route->up?"adding":"deleting",network,
+               route->net.len,route->up?"to":"from");
+       sys_cmd(st->route_path,"route",route->up?"add":"del","-net",network,
+               "netmask",mask,"gw",secnetaddr,(char *)0);
+       free(network); free(mask); free(secnetaddr);
+       route->kup=route->up;
+       return True;
+    }
+    return False;
+}
+
+static void tun_phase_hook(void *sst, uint32_t newphase)
+{
+    struct tun *st=sst;
+    string_t hostaddr,secnetaddr;
+    uint8_t mtu[6];
+    string_t network,mask;
+    struct netlink_route *r;
+    int i;
+
+    if (st->tun_old) {
+       if (st->search_for_if) {
+           string_t dname;
+           int i;
+
+           /* ASSERT st->interface_name */
+           dname=safe_malloc(strlen(st->device_path)+4,"tun_old_apply");
+           st->interface_name=safe_malloc(8,"tun_phase_hook");
+       
+           for (i=0; i<255; i++) {
+               sprintf(dname,"%s%d",st->device_path,i);
+               if ((st->fd=open(dname,O_RDWR))>0) {
+                   sprintf(st->interface_name,"tun%d",i);
+                   Message(M_INFO,"%s: allocated network interface %s "
+                           "through %s\n",st->nl.name,st->interface_name,
+                           dname);
+                   break;
+               }
+           }
+           if (st->fd==-1) {
+               fatal("%s: unable to open any TUN device (%s...)\n",
+                     st->nl.name,st->device_path);
+           }
+       } else {
+           st->fd=open(st->device_path,O_RDWR);
+           if (st->fd==-1) {
+               fatal_perror("%s: unable to open TUN device file %s",
+                            st->nl.name,st->device_path);
+           }
+       }
+    } else {
+#ifdef HAVE_LINUX_IF_H
+       struct ifreq ifr;
+
+       /* New TUN interface: open the device, then do ioctl TUNSETIFF
+          to set or find out the network interface name. */
+       st->fd=open(st->device_path,O_RDWR);
+       if (st->fd==-1) {
+           fatal_perror("%s: can't open device file %s",st->nl.name,
+                        st->device_path);
+       }
+       memset(&ifr,0,sizeof(ifr));
+       ifr.ifr_flags = IFF_TUN | IFF_NO_PI; /* Just send/receive IP packets,
+                                               no extra headers */
+       if (st->interface_name)
+           strncpy(ifr.ifr_name,st->interface_name,IFNAMSIZ);
+       Message(M_INFO,"%s: about to ioctl(TUNSETIFF)...\n",st->nl.name);
+       if (ioctl(st->fd,TUNSETIFF,&ifr)<0) {
+           fatal_perror("%s: ioctl(TUNSETIFF)",st->nl.name);
+       }
+       if (!st->interface_name) {
+           st->interface_name=safe_malloc(strlen(ifr.ifr_name)+1,"tun_apply");
+           strcpy(st->interface_name,ifr.ifr_name);
+           Message(M_INFO,"%s: allocated network interface %s\n",st->nl.name,
+                   st->interface_name);
+       }
+#else
+       fatal("netlink.c:tun_phase_hook:!tun_old unexpected\n");
+#endif /* HAVE_LINUX_IF_H */
+    }
+    /* All the networks we'll be using have been registered. Invoke ifconfig
+       to set the TUN device's address, and route to add routes to all
+       our networks. */
+
+    hostaddr=ipaddr_to_string(st->nl.local_address);
+    secnetaddr=ipaddr_to_string(st->nl.secnet_address);
+    snprintf(mtu,6,"%d",st->nl.mtu);
+    mtu[5]=0;
+
+    sys_cmd(st->ifconfig_path,"ifconfig",st->interface_name,
+           hostaddr,"netmask","255.255.255.255","-broadcast",
+           "pointopoint",secnetaddr,"mtu",mtu,"up",(char *)0);
+
+    r=st->nl.routes;
+    for (i=0; i<st->nl.n_routes; i++) {
+       if (r[i].up && !r[i].kup) {
+           network=ipaddr_to_string(r[i].net.prefix);
+           mask=ipaddr_to_string(r[i].net.mask);
+           sys_cmd(st->route_path,"route","add","-net",network,
+                   "netmask",mask,"gw",secnetaddr,(char *)0);
+           r[i].kup=True;
+       }
+    }
+
+    /* Register for poll() */
+    register_for_poll(st, tun_beforepoll, tun_afterpoll, 1, st->nl.name);
+}
+
+#ifdef HAVE_LINUX_IF_H
+static list_t *tun_apply(closure_t *self, struct cloc loc, dict_t *context,
+                        list_t *args)
+{
+    struct tun *st;
+    item_t *item;
+    dict_t *dict;
+
+    st=safe_malloc(sizeof(*st),"tun_apply");
+
+    /* First parameter must be a dict */
+    item=list_elem(args,0);
+    if (!item || item->type!=t_dict)
+       cfgfatal(loc,"tun","parameter must be a dictionary\n");
+    
+    dict=item->data.dict;
+
+    st->netlink_to_tunnel=
+       netlink_init(&st->nl,st,loc,dict,
+                    "netlink-tun",tun_set_route,tun_deliver_to_kernel);
+
+    st->tun_old=False;
+    st->device_path=dict_read_string(dict,"device",False,"tun-netlink",loc);
+    st->interface_name=dict_read_string(dict,"interface",False,
+                                       "tun-netlink",loc);
+    st->ifconfig_path=dict_read_string(dict,"ifconfig-path",
+                                      False,"tun-netlink",loc);
+    st->route_path=dict_read_string(dict,"route-path",
+                                   False,"tun-netlink",loc);
+
+    if (!st->device_path) st->device_path="/dev/net/tun";
+    if (!st->ifconfig_path) st->ifconfig_path="ifconfig";
+    if (!st->route_path) st->route_path="route";
+    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc);
+
+    add_hook(PHASE_GETRESOURCES,tun_phase_hook,st);
+
+    return new_closure(&st->nl.cl);
+}
+#endif /* HAVE_LINUX_IF_H */
+
+static list_t *tun_old_apply(closure_t *self, struct cloc loc, dict_t *context,
+                            list_t *args)
+{
+    struct tun *st;
+    item_t *item;
+    dict_t *dict;
+
+    st=safe_malloc(sizeof(*st),"tun_old_apply");
+
+    Message(M_WARNING,"the tun-old code has never been tested. Please report "
+           "success or failure to steve@greenend.org.uk\n");
+
+    /* First parameter must be a dict */
+    item=list_elem(args,0);
+    if (!item || item->type!=t_dict)
+       cfgfatal(loc,"tun","parameter must be a dictionary\n");
+    
+    dict=item->data.dict;
+
+    st->netlink_to_tunnel=
+       netlink_init(&st->nl,st,loc,dict,
+                    "netlink-tun",NULL,tun_deliver_to_kernel);
+
+    st->tun_old=True;
+    st->device_path=dict_read_string(dict,"device",False,"tun-netlink",loc);
+    st->interface_name=dict_read_string(dict,"interface",False,
+                                       "tun-netlink",loc);
+    st->search_for_if=dict_read_bool(dict,"interface-search",False,
+                                    "tun-netlink",loc,st->device_path==NULL);
+    st->ifconfig_path=dict_read_string(dict,"ifconfig-path",False,
+                                      "tun-netlink",loc);
+    st->route_path=dict_read_string(dict,"route-path",False,"tun-netlink",loc);
+
+    if (!st->device_path) st->device_path="/dev/tun";
+    if (!st->ifconfig_path) st->ifconfig_path="ifconfig";
+    if (!st->route_path) st->route_path="route";
+    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc);
+
+    /* Old TUN interface: the network interface name depends on which
+       /dev/tunX file we open. If 'interface-search' is set to true, treat
+       'device' as the prefix and try numbers from 0--255. If it's set
+       to false, treat 'device' as the whole name, and require than an
+       appropriate interface name be specified. */
+    if (st->search_for_if && st->interface_name) {
+       cfgfatal(loc,"tun-old","you may not specify an interface name "
+                "in interface-search mode\n");
+    }
+    if (!st->search_for_if && !st->interface_name) {
+       cfgfatal(loc,"tun-old","you must specify an interface name "
+                "when you explicitly specify a TUN device file\n");
+    }
+
+
+    add_hook(PHASE_GETRESOURCES,tun_phase_hook,st);
+
+    return new_closure(&st->nl.cl);
+}
+
+init_module tun_module;
+void tun_module(dict_t *dict)
+{
+#ifdef HAVE_LINUX_IF_H
+    add_closure(dict,"tun",tun_apply);
+#endif
+    add_closure(dict,"tun-old",tun_old_apply);
+}