chiark / gitweb /
uslip: New program providing a fake SLIP interface.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 8 Dec 2008 12:11:20 +0000 (12:11 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 8 Dec 2008 20:11:32 +0000 (20:11 +0000)
This is useful for doing testing on a single machine.  We'll work out
how to test the system-specific tunnel drivers later, but that really
will need multiple machines.

Makefile.am
common/Makefile.am
configure.ac
debian/.gitignore
debian/control
debian/tripe-uslip.install [new file with mode: 0644]
uslip/Makefile.am [new file with mode: 0644]
uslip/tripe-uslip.1.in [new file with mode: 0644]
uslip/uslip.c [new file with mode: 0644]

index 2141215..504ca56 100644 (file)
@@ -32,6 +32,7 @@ SUBDIRS                        =
 
 ## Core components.
 SUBDIRS                        += common
+SUBDIRS                        += uslip
 SUBDIRS                        += client
 SUBDIRS                        += server
 SUBDIRS                        += proxy
@@ -109,6 +110,9 @@ EXTRA_DIST          += debian/tripe.install
 EXTRA_DIST             += debian/tripe.postinst
 EXTRA_DIST             += debian/tripe.logrotate
 
+## uslip
+EXTRA_DIST             += debian/tripe-uslip.install
+
 ## keys
 EXTRA_DIST             += debian/tripe-keys.install
 
index e329db1..27ba694 100644 (file)
@@ -34,6 +34,7 @@ libtripe_a_SOURCES     =
 
 ## Protocol definitions.
 libtripe_a_SOURCES     += protocol.h
+libtripe_a_SOURCES     += slip.h
 
 ## Miscellaneous utilties.
 libtripe_a_SOURCES     += util.c util.h
index 58ee877..0eeb1ec 100644 (file)
@@ -290,6 +290,7 @@ AC_CONFIG_TESTDIR([t])
 AC_CONFIG_FILES(
   [Makefile]
   [common/Makefile]
+  [uslip/Makefile]
   [client/Makefile]
   [server/Makefile]
   [proxy/Makefile]
index 97c0724..87f0ea2 100644 (file)
@@ -17,3 +17,4 @@ tripe-wireshark
 tripemon
 tripe-keys
 tripe-ethereal
+tripe-uslip
index 568c9f5..d59c262 100644 (file)
@@ -29,6 +29,17 @@ Description: Forward UDP packets over a stream
  packets on standard input and output; it also natively understands TCP
  sockets.  Anything else can probably be fudged up with a port forwarder.
 
+Package: tripe-uslip
+Architecture: any
+Depends: ${shlibs:Depends}
+Recommends: tripe
+Description: Trivial IP Encryption: a simple virtual private network
+ TrIPE is a simple VPN protocol.  It uses cryptography to ensure secrecy
+ and authenticity of packets it sends and receives.
+ .
+ The tripe-uslip tool provides a fake SLIP tunnel which can be driven from
+ scripts and is useful for testing.  `If in doubt, say N here.'
+
 Package: tripe-wireshark
 Architecture: any
 Depends: wireshark-common (= ${tripe:Wireshark-Version})
diff --git a/debian/tripe-uslip.install b/debian/tripe-uslip.install
new file mode 100644 (file)
index 0000000..9115b0f
--- /dev/null
@@ -0,0 +1,2 @@
+debian/tmp/usr/bin/tripe-uslip
+debian/tmp/usr/share/man/man1/tripe-uslip.1
diff --git a/uslip/Makefile.am b/uslip/Makefile.am
new file mode 100644 (file)
index 0000000..182f839
--- /dev/null
@@ -0,0 +1,43 @@
+### -*-makefile-*-
+###
+### Build script for uslip
+###
+### (c) 2008 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Trivial IP Encryption (TrIPE).
+###
+### TrIPE is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### TrIPE is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with TrIPE; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+bin_PROGRAMS            =
+man_MANS                =
+
+###--------------------------------------------------------------------------
+### Fake slip interface.
+
+## The program.
+bin_PROGRAMS           += tripe-uslip
+tripe_uslip_SOURCES     = uslip.c
+
+## Docuemntation.
+man_MANS               += tripe-uslip.1
+CLEANFILES             += tripe-uslip.1
+EXTRA_DIST             += tripe-uslip.1.in
+
+###----- That's all, folks --------------------------------------------------
diff --git a/uslip/tripe-uslip.1.in b/uslip/tripe-uslip.1.in
new file mode 100644 (file)
index 0000000..16b0a7e
--- /dev/null
@@ -0,0 +1,212 @@
+.\" -*-nroff-*-
+.\"
+.\" Documentation for uslip
+.\"
+.\" (c) 2008 Straylight/Edgeware.
+.\"
+
+.\"----- Licensing notice ---------------------------------------------------
+.\"
+.\" This file is part of Trivial IP Encryption (TrIPE).
+.\"
+.\" TrIPE is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 2 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" TrIPE is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+.\" GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with TrIPE; if not, write to the Free Software Foundation,
+.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.
+.\"--------------------------------------------------------------------------
+.so ../defs.man.in \" @@@PRE@@@
+.
+.\"--------------------------------------------------------------------------
+.TH tripe-uslip 1 "7 April 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
+.
+.\"--------------------------------------------------------------------------
+.SH "NAME"
+.
+tripe-uslip \- fake SLIP interface for testing tripe
+.
+.\"--------------------------------------------------------------------------
+.SH "SYNOPSIS"
+.
+.B tripe-uslip
+.RB [ \-gp ]
+.I socket
+.
+.\"--------------------------------------------------------------------------
+.SH "DESCRIPTION"
+.
+The
+.B tripe-uslip
+provides a mechanism for pushing packets of data into a
+.BR tripe (8)
+server, and extracting them.  This is useful for testing the server; it
+isn't useful in a production environment.
+.SS "Overview and theory of operation"
+Testing the
+.BR tripe (8)
+server is difficult: configuring network interfaces and creating tunnels
+requires root privileges (undesirable for a program under development!)
+and testing that it successfully transports network packets needs two
+separate instances running on separate machines.  (If both ends of a
+tunnel are on the same host then the packets won't actually go over the
+tunnel: the kernel will just loop them back internally.)
+.PP
+The
+.B tripe-uslip
+program implements the interface required of a dynamic allocation script
+(see the
+.BR tripe (8)
+manual for details).  However, it doesn't actually make a network
+interface.
+.PP
+You use it by setting
+.IP
+.BI TRIPE_SLIPIF= dir /tripe-uslip
+.PP
+in the environment passed to the
+.BR tripe (8)
+server (and, typically, passing it the
+.B \-tslip
+command-line option).  When you add a new peer with the
+.B ADD
+.I peer
+.IR address ...
+administration command, the server runs
+.IB dir /tripe-uslip
+.IR peer ,
+which in turn creates a Unix-domain socket called
+.I peer
+in the server's current directory.  If you run
+.IP
+.B tripe-uslip
+.B \-p
+.I peer
+.BI < file
+.PP
+in this directory, then the contents of
+.I file
+are sent to
+.B tripe
+as if they were a network packet to be encrypted and transmitted over
+its tunnel.  (Any method of providing the data on standard input is
+acceptable: it doesn't have to be a regular file.  In particular, pipes
+are fine.  Note also that
+.B tripe
+doesn't actually care that the data it receives is actually network
+packets: it can be absolutely anything you like.)
+.PP
+If you run
+.IP
+.B tripe-uslip
+.B \-g
+.I peer
+.BI > file
+.PP
+then the contents of the next network packet the server decrypts will be
+written to the
+.IR file .
+(Again, you can use pipes or whatever.)
+.PP
+The
+.B tripe-uslip
+program is fully nonblocking.  This means that you won't deadlock the
+server by attaching duff scripts to it via
+.BR tripe-uslip .
+.SS "Technical details"
+Without options,
+.B tripe-uslip
+performs the following actions.
+.hP \*o
+It creates a Unix-domain socket with name
+.I socket
+(which will typically be the name of the peer that
+.BR tripe (8)
+created this interface for).
+.hP \*o
+It writes the string
+.BI tripe-uslip- socket
+to its standard output, followed by a newline.
+.hP \*o
+It reads and discards up to two bytes with value 192 (SLIP
+.BR END )
+on stdin.
+.hP \*o
+It enters its main loop, during which it accepts and processes client
+connections, and reads and writes SLIP-encoded packets on standard input
+and output.  Unless a fatal error occurs, the main loop continues until
+it (a) has no connected clients, (b) has no packets queued for output to
+clients or to standard output, and (c) has seen end-of-file on its
+standard input.
+.PP
+The main loop works as follows.  When a SLIP-encoded packet arrives on
+standard input, it is decoded and placed on a queue waiting to be read
+from a client.  If a client connects and writes a packet, the packet is
+SLIP-encoded and written to standard output.
+.PP
+Clients connecting to the
+Unix-domain socket send an initial character
+.RB ` < '
+to read a packet or
+.RB ` > '
+to write.  Packets, as far as clients are concerned, consist of
+uninterpreted strings of octets and continue until end-of-file.  It is
+not possible to read or write more than one packet in a single connection.
+.PP
+The command-line options allow
+.B tripe-uslip
+to be used from scripts to inject or collect packets.  They are as follows.
+.TP
+.B \-g, \-\-get
+Connect to
+.IR socket ,
+read a packet from the socket and write it to standard output.
+.TP
+.B \-p, \-\-put
+Connect to
+.IR socket ,
+read a packet from standard input and write it to the socket.
+.
+.\"--------------------------------------------------------------------------
+.SH "BUGS"
+.
+The
+.B tripe-uslip
+program is intended as a tool for testing the
+.BR tripe (8)
+server.  It is not expected to be useful in production environments.  In
+particular, it intentionally imposes no limits on queue lengths or
+packet sizes, and its internals and interface (one packet per client
+connection) are not well-suited for high performance.
+.PP
+If
+.B tripe-uslip
+turns out to be useful in other contexts then it might be improved.
+Patches are, of course, welcome.
+.PP
+The initial ignoring of
+.B END
+bytes is unpleasant but necessary to cope with the
+.BR tripe (8)
+server, which sends this sequence in order to ensure that it's properly
+synchronized with the SLIP interface.
+.
+.\"--------------------------------------------------------------------------
+.SH "SEE ALSO"
+.
+.BR tripe (8).
+.
+.\"--------------------------------------------------------------------------
+.SH "AUTHOR"
+.
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
diff --git a/uslip/uslip.c b/uslip/uslip.c
new file mode 100644 (file)
index 0000000..a552ba9
--- /dev/null
@@ -0,0 +1,727 @@
+/* -*-c-*-
+ *
+ * A simple SLIP implementation drivable from the command-line
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/sub.h>
+
+#include "slip.h"
+
+#undef sun
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct pkq_node {
+  struct pkq_node *next;
+  unsigned char *buf;
+  size_t n, sz;
+} pkq_node;
+
+typedef struct pkq {
+  pkq_node *head, **tail;
+} pkq;
+
+enum { START, OK, ESC, BAD, SYNC1, SYNC2 };
+
+typedef struct client {
+  sel_file f;
+  dstr d;
+  int mode;
+} client;
+
+typedef struct gobbler {
+  sel_file f;
+} gobbler;
+
+typedef struct dribbler {
+  sel_file f;
+  pkq q;
+  void (*done)(struct dribbler *, int, void *);
+  void *p;
+} dribbler;
+
+typedef struct waiter {
+  struct waiter *next;
+  int fd;
+  gobbler *g;
+} waiter;
+
+/*----- Static variables --------------------------------------------------*/
+
+static pkq q_in;
+static dribbler *dribble_out;
+static dstr slipbuf = DSTR_INIT;
+static int slipstate = SYNC1;
+static sel_file slip_in, listener;
+static sel_state sel;
+static unsigned reasons;
+static waiter *wait_head, **wait_tail = &wait_head;
+static unsigned char buf[16384];
+static const char *name;
+
+/*----- Utilities ---------------------------------------------------------*/
+
+static void socketaddr(struct sockaddr_un *sun, size_t *sz)
+{
+  size_t n = strlen(name) + 1;
+  if (n + offsetof(struct sockaddr_un, sun_path) > sizeof(*sun))
+    die(EXIT_FAILURE, "name too long: `%s'", name);
+  sun->sun_family = AF_UNIX;
+  memcpy(sun->sun_path, name, n);
+  if (sz)
+    *sz = n + offsetof(struct sockaddr_un, sun_path);
+}
+
+/*------ Packet queue -----------------------------------------------------*
+ *
+ * A packet queue contains a sequence of octet strings.  Packets can be added
+ * to the end and read off the front.
+ */
+
+static void initqueue(pkq *q) { q->head = 0; q->tail = &q->head; }
+
+static pkq_node *make_pkqnode(void *p, size_t n)
+{
+  pkq_node *pn;
+
+  if (!n)
+    return (0);
+  pn = CREATE(pkq_node);
+  pn->next = 0;
+  pn->buf = xmalloc(n);
+  memcpy(pn->buf, p, n);
+  pn->sz = n;
+  pn->n = 0;
+  return (pn);
+}
+
+static int enqueue(pkq *q, pkq_node *pn)
+{
+  int rc = 0;
+
+  if (!pn)
+    return (0);
+  rc = !q->head;
+  pn->next = 0;
+  *q->tail = pn;
+  q->tail = &pn->next;
+  return (rc);
+}
+
+static void destroy_pkqnode(pkq_node *pn) { xfree(pn->buf); DESTROY(pn); }
+
+static int dequeue(pkq *q, int freep)
+{
+  pkq_node *pn = q->head;
+  assert(pn);
+  q->head = pn->next;
+  if (freep)
+    destroy_pkqnode(pn);
+  if (!q->head) {
+    q->tail = &q->head;
+    return (1);
+  }
+  return (0);
+}
+
+static void destroy_pkq(pkq *q)
+{
+  pkq_node *pn, *pnn;
+
+  for (pn = q->head; pn; pn = pnn) {
+    pnn = pn->next;
+    destroy_pkqnode(pn);
+  }
+  q->head = 0; q->tail = &q->head;
+}
+
+/*----- Gobblers ----------------------------------------------------------*
+ *
+ * A gobbler just eats everything it sees on its input descriptor.
+ * Eventually, when it sees end-of-file, it closes the input descriptor and
+ * quits.
+ */
+
+static void gobbler_close(gobbler *g)
+  { if (g->f.fd != -1) { sel_rmfile(&g->f); g->f.fd = -1; } }
+
+static void gobbler_destroy(gobbler *g) { gobbler_close(g); DESTROY(g); }
+
+static void do_gobble_in(int fd, unsigned mode, void *p)
+{
+  gobbler *g = p;
+  ssize_t n;
+
+  for (;;) {
+    n = read(fd, buf, sizeof(buf));
+    if (n < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+       break;
+      else {
+       moan("read (gobble): %s", strerror(errno));
+       gobbler_close(g);
+       break;
+      }
+    } else if (n == 0) {
+      gobbler_close(g);
+      break;
+    }
+  }
+}
+
+static gobbler *make_gobbler(int fd)
+{
+  gobbler *g;
+
+  g = CREATE(gobbler);
+  sel_initfile(&sel, &g->f, fd, SEL_READ, do_gobble_in, g);
+  sel_addfile(&g->f);
+  do_gobble_in(fd, SEL_READ, g);
+  return (g);
+}
+
+/*----- Dribbler ----------------------------------------------------------*
+ *
+ * A dribbler hands out data from a packet queue to a file descriptor.  It
+ * makes no attempt to preserve the record boundaries inherent in the packet
+ * queue structure.  If the dribbler reaches the end of its queue, it invokes
+ * a user-supplied `done' function and stops selecting its output descriptor
+ * for writing.
+ */
+
+static void cripple_dribbler(dribbler *d) { close(d->f.fd); d->f.fd = -1; }
+
+static void destroy_dribbler(dribbler *d)
+  { cripple_dribbler(d); DESTROY(d); }
+
+static void dribble_done(dribbler *d, int err)
+{
+  if (d->q.head) {
+    sel_rmfile(&d->f);
+    destroy_pkq(&d->q);
+  }
+  d->done(d, err, d->p);
+}
+
+static void do_dribble_out(int fd, unsigned mode, void *p)
+{
+  dribbler *d = p;
+  ssize_t n;
+  pkq_node *pn;
+
+  for (;;) {
+    pn = d->q.head;
+    n = write(fd, pn->buf + pn->n, pn->sz - pn->n);
+    if (n < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+       break;
+      else {
+       dribble_done(d, errno);
+       break;
+      }
+    }
+    pn->n += n;
+    if (pn->n == pn->sz && dequeue(&d->q, 1)) {
+      sel_rmfile(&d->f);
+      dribble_done(d, 0);
+      break;
+    }
+  }
+}
+
+static int enqueue_dribble(dribbler *d, pkq_node *pn)
+{
+  if (d->f.fd == -1) {
+    destroy_pkqnode(pn);
+    return (0);
+  }
+  if (enqueue(&d->q, pn)) {
+    sel_addfile(&d->f);
+    do_dribble_out(d->f.fd, SEL_WRITE, d);
+    return (1);
+  }
+  return (0);
+}
+
+static dribbler *make_dribbler(int fd,
+                              void (*done)(dribbler *, int, void *),
+                              void *p)
+{
+  dribbler *d = CREATE(dribbler);
+  sel_initfile(&sel, &d->f, fd, SEL_WRITE, do_dribble_out, d);
+  initqueue(&d->q);
+  d->done = done;
+  d->p = p;
+  return (d);
+}
+
+/*----- Clients -----------------------------------------------------------*/
+
+static void done_client_dribble(dribbler *d, int err, void *p)
+{
+  if (err)
+    moan("write (client): %s", strerror(err));
+  gobbler_destroy(p);
+  destroy_dribbler(d);
+  reasons--;
+}
+
+static void dequeue_to_waiter(void)
+{
+  waiter *w;
+  dribbler *d;
+  pkq_node *pn;
+
+  while (q_in.head && wait_head) {
+    w = wait_head;
+    wait_head = w->next;
+    if (!wait_head)
+      wait_tail = &wait_head;
+    d = make_dribbler(w->fd, done_client_dribble, w->g);
+    DESTROY(w);
+    pn = q_in.head;
+    if (dequeue(&q_in, 0))
+      reasons--;
+    enqueue_dribble(d, pn);
+  }
+}
+
+static void client_destroy(client *c)
+{
+  sel_rmfile(&c->f);
+  close(c->f.fd);
+  dstr_destroy(&c->d);
+  reasons--;
+  DESTROY(c);
+}
+
+static void do_client_in(int fd, unsigned mode, void *p)
+{
+  client *c = p;
+  ssize_t n, i, i0;
+  waiter *w;
+
+  /* --- Attention --- *
+   *
+   * The queue for outbound packets is SLIP-encoded; we need to encode it
+   * here. The queue for inbound packets is raw.
+   */
+
+  for (;;) {
+    n = read(fd, buf, sizeof(buf));
+    i0 = 0;
+    if (n < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+       break;
+      else {
+       moan("read (client): %s", strerror(errno));
+       client_destroy(c);
+       return;
+      }
+    } else if (n == 0) {
+      if (c->mode == '>') {
+       DPUTC(&c->d, SL_END);
+       if (enqueue_dribble(dribble_out, make_pkqnode(c->d.buf, c->d.len)))
+         reasons++;
+      }
+      client_destroy(c);
+      return;
+    }
+    if (c->mode == '?') {
+      switch (buf[0]) {
+       case '>':
+         i0 = 1;
+         c->mode = '>';
+         break;
+       case '<':
+         w = CREATE(waiter);
+         w->g = make_gobbler(fd);
+         w->next = 0;
+         w->fd = fd;
+         *wait_tail = w;
+         wait_tail = &w->next;
+         sel_rmfile(&c->f);
+         DESTROY(c);
+         dequeue_to_waiter();
+         return;
+       default:
+         moan("bad client mode `%c'", buf[0]);
+         client_destroy(c);
+         return;
+      }
+    }
+    for (i = i0; i < n; i++) {
+      switch (buf[i]) {
+       case SL_ESC:
+         DPUTC(&c->d, SL_ESC);
+         DPUTC(&c->d, SL_ESCESC);
+         break;
+       case SL_END:
+         DPUTC(&c->d, SL_ESC);
+         DPUTC(&c->d, SL_ESCEND);
+         break;
+       default:
+         DPUTC(&c->d, buf[i]);
+         break;
+      }
+    }
+  }
+}
+
+static void do_accept(int fd, unsigned mode, void *hunoz)
+{
+  client *c;
+  struct sockaddr_un sun;
+  socklen_t n = sizeof(sun);
+  int nfd;
+
+  if ((nfd = accept(fd, (struct sockaddr *)&sun, &n)) < 0) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+      return;
+    else
+      die(EXIT_FAILURE, "accept: %s", strerror(errno));
+  }
+  c = CREATE(client);
+  c->mode = '?';
+  dstr_create(&c->d);
+  fdflags(nfd, O_NONBLOCK, O_NONBLOCK, 0, 0);
+  sel_initfile(&sel, &c->f, nfd, SEL_READ, do_client_in, c);
+  sel_addfile(&c->f);
+  reasons++;
+}
+
+/*----- Main daemon -------------------------------------------------------*/
+
+static void done_slip_dribble(dribbler *d, int err, void *p)
+{
+  if (!err)
+    reasons--;
+  else if (err != EPIPE)
+    die(EXIT_FAILURE, "write (slip): %s", strerror(errno));
+  else
+    cripple_dribbler(d);
+}
+
+static void do_slip_in(int fd, unsigned mode, void *hunoz)
+{
+  ssize_t i, n;
+
+  /* --- Attention --- *
+   *
+   * The queue for inbound packets contains raw data; we need to decode it
+   * here.  The queue for outbound packets is SLIP-encoded.
+   *
+   * TrIPE sends two empty packets on start-up, in order to resynchronize the
+   * target.  We don't need this and it messes us up.
+   */
+
+  for (;;) {
+    n = read(fd, buf, sizeof(buf));
+    if (n == 0) {
+      switch (slipstate) {
+       case SYNC1:
+       case SYNC2:
+       case START:
+       case BAD:
+         break;
+       default:
+         moan("eof found while processing packet (discarding)");
+         break;
+      }
+      close(fd);
+      sel_rmfile(&slip_in);
+      reasons--;
+      return;
+    } else if (n < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+       break;
+      die(EXIT_FAILURE, "read (slip in): %s", strerror(errno));
+    }
+    for (i = 0; i < n; i++) {
+      switch (slipstate) {
+       case SYNC1:
+         switch (buf[i]) {
+           case SL_END:
+             slipstate = SYNC2;
+             break;
+           default:
+             goto start;
+         }
+         break;
+       case SYNC2:
+         switch (buf[i]) {
+           case SL_END:
+             slipstate = START;
+             break;
+           default:
+             goto start;
+         }
+         break;
+       case BAD:
+         switch (buf[i]) {
+           case SL_END:
+             DRESET(&slipbuf);
+             slipstate = OK;
+             break;
+           default:
+             break;
+         }
+         break;
+       case ESC:
+         switch (buf[i]) {
+           case SL_ESCEND:
+             DPUTC(&slipbuf, SL_END);
+             break;
+           case SL_ESCESC:
+             DPUTC(&slipbuf, SL_ESC);
+             break;
+           case SL_END:
+             moan("found escaped end byte (discard packet and resync");
+             DRESET(&slipbuf);
+             slipstate = OK;
+             break;
+           default:
+             moan("unspected escape char 0x%02x", buf[i]);
+             slipstate = BAD;
+             break;
+         }
+         break;
+       case START:
+       case OK:
+       start:
+         switch (buf[i]) {
+           case SL_ESC:
+             slipstate = ESC;
+             break;
+           case SL_END:
+             if (enqueue(&q_in, make_pkqnode(slipbuf.buf, slipbuf.len)))
+               reasons++;
+             DRESET(&slipbuf);
+             dequeue_to_waiter();
+             slipstate = START;
+             break;
+           default:
+             DPUTC(&slipbuf, buf[i]);
+             slipstate = OK;
+             break;
+         }
+         break;
+      }
+    }
+  }
+}
+
+static void slipif(void)
+{
+  int fd;
+  dstr d = DSTR_INIT;
+  struct sockaddr_un sun;
+  size_t sz;
+
+  /* --- Make the socket --- */
+
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+    die(EXIT_FAILURE, "socket: %s", strerror(errno));
+  socketaddr(&sun, &sz);
+  if (bind(fd, (struct sockaddr *)&sun, sz))
+    die(EXIT_FAILURE, "bind: %s", strerror(errno));
+  if (listen(fd, 5))
+    die(EXIT_FAILURE, "listen: %s", strerror(errno));
+
+  /* --- Set up listeners for things --- */
+
+  fdflags(fd, O_NONBLOCK, O_NONBLOCK, 0, 0);
+  sel_initfile(&sel, &listener, fd, SEL_READ, do_accept, 0);
+  sel_addfile(&listener);
+
+  fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
+  fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
+  sel_initfile(&sel, &slip_in, STDIN_FILENO, SEL_READ, do_slip_in, 0);
+  dribble_out = make_dribbler(STDOUT_FILENO, done_slip_dribble, 0);
+  sel_addfile(&slip_in);
+
+  initqueue(&q_in);
+  reasons++;
+
+  /* --- Write the interface name --- */
+
+  dstr_putf(&d, "%s-%s\n", QUIS, name);
+  if (enqueue_dribble(dribble_out, make_pkqnode(d.buf, d.len)))
+    reasons++;
+  dstr_destroy(&d);
+
+  /* --- Main loop --- */
+
+  while (reasons) {
+    if (sel_select(&sel))
+      die(EXIT_FAILURE, "select: %s", strerror(errno));
+  }
+
+  /* --- Done --- */
+
+  unlink(name);
+}
+
+/*----- Putting and getting -----------------------------------------------*/
+
+static int make_sock(int mode)
+{
+  struct sockaddr_un sun;
+  size_t sz;
+  int fd;
+  char ch;
+
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+    die(EXIT_FAILURE, "socket: %s", strerror(errno));
+  socketaddr(&sun, &sz);
+  if (connect(fd, (struct sockaddr *)&sun, sz))
+    die(EXIT_FAILURE, "connect: %s", strerror(errno));
+  ch = mode;
+  if (write(fd, &ch, 1) < 0)
+    die(EXIT_FAILURE, "write (mode): %s", strerror(errno));
+  return (fd);
+}
+
+static void shovel(int from, int to)
+{
+  ssize_t n;
+  size_t sz;
+  unsigned char *p;
+
+  for (;;) {
+    n = read(from, buf, sizeof(buf));
+    if (n < 0) {
+      if (errno == EINTR)
+       continue;
+      else
+       die(EXIT_FAILURE, "read (shovel): %s", strerror(errno));
+    } else if (n == 0)
+      break;
+
+    sz = n;
+    p = buf;
+    while (sz) {
+      n = write(to, p, sz);
+      if (n < 0) {
+       if (errno == EINTR)
+         continue;
+       else
+         die(EXIT_FAILURE, "write (shovel): %s", strerror(errno));
+      }
+      p += n;
+      sz -= n;
+    }
+  }
+  close(from);
+  close(to);
+}
+
+static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
+static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp) { pquis(fp, "Usage: $ [-pg] SOCKET\n"); }
+
+static void version(void)
+  { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
+
+static void help(void)
+{
+  version();
+  fputc('\n', stdout);
+  usage(stdout);
+  puts("\n\
+With no options, provides a SLIP interface for TrIPE.\n\
+\n\
+Options:\n\
+  -p, --put    Send packet on stdin to TrIPE.\n\
+  -g, --get    Receive packet from TrIPE and write to stdout.");
+}
+
+int main(int argc, char *argv[])
+{
+  int mode = 'd';
+  int i;
+
+  ego(argv[0]);
+  for (;;) {
+    const struct option opt[] = {
+      { "help",                        0,              0,              'h' },
+      { "version",             0,              0,              'v' },
+      { "put",                 0,              0,              'p' },
+      { "get",                 0,              0,              'g' },
+      { 0,                     0,              0,              0 }
+    };
+    i = mdwopt(argc, argv, "hvpg", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h': help(); return (0);
+      case 'v': version(); return (0);
+      case 'p': case 'g': mode = i; break;
+      default: usage(stderr); exit(EXIT_FAILURE); break;
+    }
+  }
+  if (argc - optind != 1) { usage(stderr); exit(EXIT_FAILURE); }
+  name = argv[optind];
+  signal(SIGPIPE, SIG_IGN);
+  switch (mode) {
+    case 'd': slipif(); break;
+    case 'p': put(); break;
+    case 'g': get(); break;
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/