chiark / gitweb /
Import release 0.1.7 v0.1.7
authorStephen Early <steve@greenend.org.uk>
Mon, 15 Oct 2001 00:37:00 +0000 (01:37 +0100)
committerStephen Early <steve@greenend.org.uk>
Wed, 18 May 2011 12:37:39 +0000 (13:37 +0100)
Makefile.in
README
TODO
debian/changelog
netlink.c
netlink.h
process.c
process.h
secnet.c
slip.c

index 85a770b34ed9f4bf9ec71ec85f06621b75b26a06..abc8528a54b308e6ba912ba3a9cc937e1bc1a9a1 100644 (file)
@@ -18,7 +18,7 @@
 .PHONY:        all clean realclean dist install
 
 PACKAGE:=secnet
-VERSION:=0.1.6
+VERSION:=0.1.7
 
 @SET_MAKE@
 
@@ -96,7 +96,9 @@ config.status: configure
 # Manual dependencies section - XXX use autodep eventually
 $(OBJECTS): config.h secnet.h util.h
 conffile.o conffile.tab.o conffile.yy.o: conffile.h conffile_internal.h
-secnet.c: conffile.h
+secnet.o: conffile.h process.h
+process.o: process.h
+log.o: process.h
 md5.o: md5.h
 serpent.o transform.o: serpent.h
 serpent.o: serpentsboxes.h
diff --git a/README b/README
index 957fe0658a9cee7a63ce3e3cfe0f31ff0989a7f6..a21a2e7797e63bf36f9f258ce28889f3aaa7a204 100644 (file)
--- a/README
+++ b/README
@@ -167,12 +167,11 @@ udp: dict argument
   buffer (buffer closure): buffer for incoming packets
   authbind (string): optional, path to authbind-helper program
 
-** util
+** log
 
 Defines:
   logfile (closure => log closure)
   syslog (closure => log closure)
-  sysbuffer (closure => buffer closure)
 
 logfile: dict argument
   filename (string): where to log to
@@ -191,6 +190,8 @@ logfile: dict argument
     { "verbose", M_INFO|M_NOTICE|M_WARNING|M_ERROR|M_SECURITY|M_FATAL },
     { "quiet", M_FATAL }
 
+logfile will close and reopen its file upon receipt of SIGHUP.
+
 syslog: dict argument
   ident (string): include this string in every log message
   facility (string): facility to log as
@@ -213,6 +214,11 @@ syslog: dict argument
     { "user", LOG_USER },
     { "uucp", LOG_UUCP }
 
+** util
+
+Defines:
+  sysbuffer (closure => buffer closure)
+
 sysbuffer: integer[,dict]
   arg1: buffer length
   arg2: options:
@@ -287,6 +293,9 @@ null-netlink: dict argument
   secnet-address (string): IP address of this netlink device
   mtu (integer): MTU of host's tunnel interface
 
+Netlink will dump its current routing table to the system/log on
+receipt of SIGUSR1.
+
 ** slip
 
 Defines:
diff --git a/TODO b/TODO
index af3a4686efd4ad5be8ee2a6f870b4f78aaa48323..d0e8db78b0a826ef1905b2c24e38c2d9139b721c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -6,8 +6,8 @@ endianness problems)
 netlink.c: investigate why 'default' routes don't appear to work
 (reported by JDA).
 
-slip.c: detect failure of userv-ipif to start. Restart userv-ipif to
-cope with soft routes? Restart it if it fails in use?
+slip.c: restart userv-ipif to cope with soft routes? Restart it if it
+fails in use?
 
 tun.c: jdamery reports tun-old code works on Linux-2.2.
 Unresolved problem with ioctl(TUNSETIFF) sometimes returning EINVAL, seems
@@ -24,14 +24,10 @@ cleanly using a table. There's still quite a lot of redundancy in this
 file. Abandon key exchanges when a bad packet is received. Modify
 protocol to include version fields, as described in the NOTES file.
 
-transform.c: see below
+transform.c: separate the transforms 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.
 
 sha1.c: test
 
-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.
-
-Signal handling! Really just cope with SIGCHLD and SIGTERM. Possibly
-use SIGUSR1/2 for prodding things. Manage child processes properly.
index 8a69a5e28521cfa319f63cc5da841837e0fa2ad9..4c42dd4bc110269c5f5e88995bf79179320695c5 100644 (file)
@@ -1,4 +1,4 @@
-secnet (0.1.5-1) unstable; urgency=low
+secnet (0.1.7-1) unstable; urgency=low
 
   * New upstream version.
 
index e09eacf95c4466015bf393eaf1fa75cdcd634073..bfdb19b77fce78fad454cefdc20070880739fada 100644 (file)
--- a/netlink.c
+++ b/netlink.c
@@ -8,10 +8,18 @@
    packet to the kernel we check that the tunnel it came over could
    reasonably have produced it. */
 
+/* XXX new feature: "point-to-point" mode.  Instead of specifying a
+   secnet-address in the configuration dictionary, the user specifies
+   the address of the machine at the other end of the (one and only)
+   tunnel.  We bypass all IP packet processing code.  This mode is
+   useful for leafnodes like laptops, which don't require a secnet
+   router address. */
+
 #include "secnet.h"
 #include "util.h"
 #include "ipaddr.h"
 #include "netlink.h"
+#include "process.h"
 
 /* Generic IP checksum routine */
 static inline uint16_t ip_csum(uint8_t *iph,uint32_t count)
@@ -484,15 +492,18 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
 }
 
 static void netlink_set_softlinks(struct netlink *st, struct netlink_client *c,
-                                 bool_t up)
+                                 bool_t up, uint32_t quality)
 {
     uint32_t i;
 
     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]);
+       if (st->routes[i].c==c) {
+           st->routes[i].quality=quality;
+           if (!st->routes[i].hard) {
+               st->routes[i].up=up;
+               st->set_route(st->dst,&st->routes[i]);
+           }
        }
     }
 }
@@ -504,9 +515,9 @@ static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
 
     c->link_quality=quality;
     if (c->link_quality==LINK_QUALITY_DOWN) {
-       netlink_set_softlinks(st,c,False);
+       netlink_set_softlinks(st,c,False,c->link_quality);
     } else {
-       netlink_set_softlinks(st,c,True);
+       netlink_set_softlinks(st,c,True,c->link_quality);
     }
 }
 
@@ -560,26 +571,29 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
     return c;
 }
 
-static void netlink_dump_routes(struct netlink *st)
+static void netlink_dump_routes(struct netlink *st, bool_t requested)
 {
     int i;
     string_t net;
+    uint32_t c=M_INFO;
 
-    Message(M_INFO,"%s: routing table:\n",st->name);
+    if (requested) c=M_WARNING;
+    Message(c,"%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 route,%s)\n",net,
+       Message(c,"%s -> tunnel %s (%s,%s route,%s,quality %d)\n",net,
                st->routes[i].c->name,
                st->routes[i].hard?"hard":"soft",
                st->routes[i].allow_route?"free":"restricted",
-               st->routes[i].up?"up":"down");
+               st->routes[i].up?"up":"down",
+               st->routes[i].quality);
        free(net);
     }
-    Message(M_INFO,"%s/32 -> netlink \"%s\"\n",
+    Message(c,"%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);
+       Message(c,"%s -> host\n",net);
        free(net);
     }
 }
@@ -618,6 +632,7 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
            st->routes[i].hard=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
            st->routes[i].allow_route=c->options&NETLINK_OPTION_ALLOW_ROUTE?
                True:False;
+           st->routes[i].quality=c->link_quality;
            i++;
        }
     }
@@ -630,7 +645,14 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
     qsort(st->routes,st->n_routes,sizeof(*st->routes),
          netlink_compare_route_specificity);
 
-    netlink_dump_routes(st);
+    netlink_dump_routes(st,False);
+}
+
+static void netlink_signal_handler(void *sst, int signum)
+{
+    struct netlink *st=sst;
+    Message(M_INFO,"%s: route dump requested by SIGUSR1\n",st->name);
+    netlink_dump_routes(st,True);
 }
 
 netlink_deliver_fn *netlink_init(struct netlink *st,
@@ -671,6 +693,7 @@ netlink_deliver_fn *netlink_init(struct netlink *st,
     st->routes=NULL;
 
     add_hook(PHASE_SETUP,netlink_phase_hook,st);
+    request_signal_notification(SIGUSR1, netlink_signal_handler, st);
 
     return netlink_incoming;
 }
index 3f735f2558db0e5b091bb7b0503a812ba4244af7..f3f43d5f5f3b564a108e005afe838a300b27b768 100644 (file)
--- a/netlink.h
+++ b/netlink.h
@@ -23,6 +23,7 @@ struct netlink_route {
     bool_t allow_route;
     bool_t up;
     bool_t kup;
+    uint32_t quality; /* provided by client */
     struct netlink_client *c;
 };
 
index b40801b17a593fc5ce5875687c64220af6eab0b9..d9d01cec2c1abc090e8c4911482c071664f3d514 100644 (file)
--- a/process.c
+++ b/process.c
@@ -39,11 +39,10 @@ static void set_default_signals(void);
    signal processing so that we can catch SIGCHLD for them and report
    their exit status using the callback function.  We block SIGCHLD
    until signal processing has begun. */
-extern void makesubproc(process_entry_fn *entry, process_callback_fn *cb,
-                       void *est, void *cst, string_t desc)
+pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
+                void *est, void *cst, string_t desc)
 {
     struct child *c;
-    sigset_t sigchld;
     pid_t p;
 
     c=safe_malloc(sizeof(*c),"makesubproc");
@@ -52,9 +51,7 @@ extern void makesubproc(process_entry_fn *entry, process_callback_fn *cb,
     c->cst=cst;
 
     if (!signal_handling) {
-       sigemptyset(&sigchld);
-       sigaddset(&sigchld,SIGCHLD);
-       sigprocmask(SIG_BLOCK,&sigchld,NULL);
+       fatal("makesubproc called before signal handling started\n");
     }
     p=fork();
     if (p==0) {
@@ -70,6 +67,7 @@ extern void makesubproc(process_entry_fn *entry, process_callback_fn *cb,
     c->finished=False;
     c->next=children;
     children=c;
+    return p;
 }
 
 static signal_notify_fn sigchld_handler;
index 986fa38e901f1cb7e0a438e5375b0d37f2adb90a..35f5318ce46150bc39a27dcc5ff6b575cbaa2d46 100644 (file)
--- a/process.h
+++ b/process.h
@@ -2,12 +2,13 @@
 #define process_h
 
 #include <signal.h>
+#include <sys/wait.h>
 
 typedef void process_callback_fn(void *cst, pid_t pid, int status);
 typedef void process_entry_fn(void *cst);
 typedef void signal_notify_fn(void *cst, int signum);
 
-extern void makesubproc(process_entry_fn *entry, process_callback_fn *cb,
+extern pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
                        void *est, void *cbst, string_t desc);
 
 extern void request_signal_notification(int signum, signal_notify_fn *notify,
index c7ce2b8a459b6707431b11e2bd182e637035a6c1..489e0bf381e3686acb28433d4077dab953c78e66 100644 (file)
--- a/secnet.c
+++ b/secnet.c
@@ -388,11 +388,11 @@ int main(int argc, char **argv)
     enter_phase(PHASE_DROPPRIV);
     droppriv();
 
-    enter_phase(PHASE_RUN);
     start_signal_handling();
     request_signal_notification(SIGTERM,finish,"SIGTERM");
     if (!background) request_signal_notification(SIGINT,finish,"SIGINT");
     request_signal_notification(SIGHUP,ignore_hup,NULL);
+    enter_phase(PHASE_RUN);
     run();
 
     enter_phase(PHASE_SHUTDOWN);
diff --git a/slip.c b/slip.c
index afc0528e238ccefff6125923e783203c6b2095ce..cff8f63df63788c966428b4c0dc6b43facdc8d37 100644 (file)
--- a/slip.c
+++ b/slip.c
 #include "secnet.h"
 #include "util.h"
 #include "netlink.h"
+#include "process.h"
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
 
 #define SLIP_END    192
 #define SLIP_ESC    219
 #define SLIP_ESCEND 220
 #define SLIP_ESCESC 221
 
+struct slip {
+    struct netlink nl;
+    struct buffer_if *buff; /* We unstuff received packets into here
+                              and send them to the netlink code. */
+    bool_t pending_esc;
+    netlink_deliver_fn *netlink_to_tunnel;
+    uint32_t local_address;
+};
+
+/* Generic SLIP mangling code */
+
+static void slip_stuff(struct slip *st, struct buffer_if *buf, int fd)
+{
+    uint8_t txbuf[DEFAULT_BUFSIZE];
+    uint8_t *i;
+    uint32_t j=0;
+
+    BUF_ASSERT_USED(buf);
+
+    /* XXX crunchy bytestuff code */
+    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;
+       }
+       if ((j+2)>DEFAULT_BUFSIZE) {
+           if (write(fd,txbuf,j)<0) {
+               fatal_perror("slip_stuff: write()");
+           }
+           j=0;
+       }
+    }
+    txbuf[j++]=SLIP_END;
+    if (write(fd,txbuf,j)<0) {
+       fatal_perror("slip_stuff: write()");
+    }
+    BUF_FREE(buf);
+}
+
+static void slip_unstuff(struct slip *st, uint8_t *buf, uint32_t l)
+{
+    uint32_t i;
+
+    /* 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(buf[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 (buf[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)=buf[i];
+               break;
+           }
+       }
+    }
+}
+
+static void slip_init(struct slip *st, struct cloc loc, dict_t *dict,
+                     string_t name, netlink_deliver_fn *to_host)
+{
+    st->netlink_to_tunnel=
+       netlink_init(&st->nl,st,loc,dict,
+                    "netlink-userv-ipif",NULL,to_host);
+    st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"name",loc);
+    st->local_address=string_to_ipaddr(
+       dict_find_item(dict,"local-address", True, name, loc),"netlink");
+    BUF_ALLOC(st->buff,"slip_init");
+    st->pending_esc=False;
+}
+
 /* Connection to the kernel through userv-ipif */
 
 struct userv {
-    struct netlink nl;
+    struct slip slip;
     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;
-    uint32_t local_address; /* host interface address */
+    pid_t pid;
+    bool_t expecting_userv_exit;
 };
 
 static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
@@ -37,11 +139,16 @@ static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
                            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;
+
+    if (st->rxfd!=-1) {
+       *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;
+    } else {
+       *nfds_io=0;
+    }
     return 0;
 }
 
@@ -50,55 +157,23 @@ static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds,
 {
     struct userv *st=sst;
     uint8_t rxbuf[DEFAULT_BUFSIZE];
-    int l,i;
+    int l;
+
+    if (nfds==0) return;
 
     if (fds[1].revents&POLLERR) {
-       Message(M_ERROR,"%s: userv_afterpoll: hup!\n",st->nl.name);
+       Message(M_ERROR,"%s: userv_afterpoll: POLLERR!\n",st->slip.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) {
+           if (errno!=EINTR)
+               fatal_perror("%s: userv_afterpoll: read(rxfd)",
+                            st->slip.nl.name);
+       } else 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;
-               }
-           }
-       }
+                 st->slip.nl.name);
+       } else slip_unstuff(&st->slip,rxbuf,l);
     }
 }
 
@@ -107,43 +182,60 @@ 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);
+    slip_stuff(&st->slip,buf,st->txfd);
+}
 
-    /* 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;
-       }
+static void userv_userv_callback(void *sst, pid_t pid, int status)
+{
+    struct userv *st=sst;
+
+    if (pid!=st->pid) {
+       Message(M_WARNING,"userv_callback called unexpectedly with pid %d "
+               "(expected %d)\n",pid,st->pid);
+       return;
     }
-    txbuf[j++]=SLIP_END;
-    if (write(st->txfd,txbuf,j)<0) {
-       fatal_perror("userv_deliver_to_kernel: write()");
+    if (!st->expecting_userv_exit) {
+       if (WIFEXITED(status)) {
+           fatal("%s: userv exited unexpectedly with status %d\n",
+                 st->slip.nl.name,WEXITSTATUS(status));
+       } else if (WIFSIGNALED(status)) {
+           fatal("%s: userv exited unexpectedly: uncaught signal %d\n",
+                 st->slip.nl.name,WTERMSIG(status));
+       } else {
+           fatal("%s: userv stopped unexpectedly\n");
+       }
     }
-    BUF_FREE(buf);
+    Message(M_WARNING,"%s: userv subprocess died with status %d\n",
+           st->slip.nl.name,WEXITSTATUS(status));
+    st->pid=0;
 }
 
-static void userv_phase_hook(void *sst, uint32_t newphase)
+struct userv_entry_rec {
+    string_t path;
+    char **argv;
+    int stdin;
+    int stdout;
+    /* XXX perhaps we should collect and log stderr? */
+};
+
+static void userv_entry(void *sst)
 {
-    struct userv *st=sst;
-    pid_t child;
+    struct userv_entry_rec *st=sst;
+
+    dup2(st->stdin,0);
+    dup2(st->stdout,1);
+
+    /* XXX close all other fds */
+    setsid();
+    execvp(st->path,st->argv);
+    perror("userv-entry: execvp()");
+    exit(1);
+}
+
+static void userv_invoke_userv(struct userv *st)
+{
+    struct userv_entry_rec *er;
     int c_stdin[2];
     int c_stdout[2];
     string_t addrs;
@@ -151,18 +243,24 @@ static void userv_phase_hook(void *sst, uint32_t newphase)
     string_t s;
     struct netlink_route *r;
     int i;
+    uint8_t confirm;
+
+    if (st->pid) {
+       fatal("userv_invoke_userv: already running\n");
+    }
 
     /* 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->local_address),
-            ipaddr_to_string(st->nl.secnet_address),st->nl.mtu);
+    addrs=safe_malloc(512,"userv_invoke_userv:addrs");
+    snprintf(addrs,512,"%s,%s,%d,slip",
+            ipaddr_to_string(st->slip.local_address),
+            ipaddr_to_string(st->slip.nl.secnet_address),st->slip.nl.mtu);
 
-    nets=safe_malloc(1024,"userv_phase_hook:nets");
+    nets=safe_malloc(1024,"userv_invoke_userv:nets");
     *nets=0;
-    r=st->nl.routes;
-    for (i=0; i<st->nl.n_routes; i++) {
+    r=st->slip.nl.routes;
+    for (i=0; i<st->slip.nl.n_routes; i++) {
        if (r[i].up) {
            r[i].kup=True;
            s=subnet_to_string(&r[i].net);
@@ -173,61 +271,92 @@ static void userv_phase_hook(void *sst, uint32_t newphase)
     }
     nets[strlen(nets)-1]=0;
 
-    Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->nl.name,
+    Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->slip.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;
+    st->slip.pending_esc=False;
 
     /* Invoke userv */
     if (pipe(c_stdin)!=0) {
-       fatal_perror("userv_phase_hook: pipe(c_stdin)");
+       fatal_perror("userv_invoke_userv: pipe(c_stdin)");
     }
     if (pipe(c_stdout)!=0) {
-       fatal_perror("userv_phase_hook: pipe(c_stdout)");
+       fatal_perror("userv_invoke_userv: pipe(c_stdout)");
     }
     st->txfd=c_stdin[1];
     st->rxfd=c_stdout[0];
 
-    child=fork();
-    if (child==-1) {
-       fatal_perror("userv_phase_hook: fork()");
+    er=safe_malloc(sizeof(*r),"userv_invoke_userv: er");
+
+    er->stdin=c_stdin[0];
+    er->stdout=c_stdout[1];
+    /* The arguments are:
+       userv
+       service-user
+       service-name
+       local-addr,secnet-addr,mtu,protocol
+       route1,route2,... */
+    er->argv=safe_malloc(sizeof(*er->argv)*6,"userv_invoke_userv:argv");
+    er->argv[0]=st->userv_path;
+    er->argv[1]=st->service_user;
+    er->argv[2]=st->service_name;
+    er->argv[3]=addrs;
+    er->argv[4]=nets;
+    er->argv[5]=NULL;
+    er->path=st->userv_path;
+
+    st->pid=makesubproc(userv_entry, userv_userv_callback,
+                       er, st, st->slip.nl.name);
+    close(er->stdin);
+    close(er->stdout);
+    free(er->argv);
+    free(er);
+    free(addrs);
+    free(nets);
+    Message(M_INFO,"%s: userv-ipif pid is %d\n",st->slip.nl.name,st->pid);
+    /* Read a single character from the pipe to confirm userv-ipif is
+       running. If we get a SIGCHLD at this point then we'll get EINTR. */
+    if (read(st->rxfd,&confirm,1)!=1) {
+       if (errno==EINTR) {
+           Message(M_WARNING,"%s: read of confirmation byte was "
+                   "interrupted\n",st->slip.nl.name);
+       } else {
+           fatal_perror("%s: read() of confirmation byte",st->slip.nl.name);
+       }
+    } else {
+       if (confirm!=SLIP_END) {
+           fatal("%s: bad confirmation byte %d from userv-ipif\n",
+                 st->slip.nl.name,confirm);
+       }
     }
-    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);
+    /* Mark rxfd non-blocking */
+    if (fcntl(st->rxfd, F_SETFL, fcntl(st->rxfd, F_GETFL)|O_NONBLOCK)==-1) {
+       fatal_perror("%s: fcntl(O_NONBLOCK)",st->slip.nl.name);
+    }
+}
+
+static void userv_kill_userv(struct userv *st)
+{
+    if (st->pid) {
+       kill(-st->pid,SIGTERM);
+       st->expecting_userv_exit=True;
+    }
+}
+
+static void userv_phase_hook(void *sst, uint32_t newphase)
+{
+    struct userv *st=sst;
+    /* We must wait until signal processing has started before forking
+       userv */
+    if (newphase==PHASE_RUN) {
+       userv_invoke_userv(st);
+       /* Register for poll() */
+       register_for_poll(st, userv_beforepoll, userv_afterpoll, 2,
+                         st->slip.nl.name);
+    }
+    if (newphase==PHASE_SHUTDOWN) {
+       userv_kill_userv(st);
     }
-    /* 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,
@@ -246,9 +375,8 @@ static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
     
     dict=item->data.dict;
 
-    st->netlink_to_tunnel=
-       netlink_init(&st->nl,st,loc,dict,
-                    "netlink-userv-ipif",NULL,userv_deliver_to_kernel);
+    slip_init(&st->slip,loc,dict,"netlink-userv-ipif",
+             userv_deliver_to_kernel);
 
     st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
                                    loc);
@@ -259,15 +387,13 @@ static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
     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);
-    st->local_address=string_to_ipaddr(
-       dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
-    BUF_ALLOC(st->buff,"netlink:userv_apply");
-
     st->rxfd=-1; st->txfd=-1;
-    add_hook(PHASE_DROPPRIV,userv_phase_hook,st);
+    st->pid=0;
+    st->expecting_userv_exit=False;
+    add_hook(PHASE_RUN,userv_phase_hook,st);
+    add_hook(PHASE_SHUTDOWN,userv_phase_hook,st);
 
-    return new_closure(&st->nl.cl);
+    return new_closure(&st->slip.nl.cl);
 }
 
 init_module slip_module;