chiark / gitweb /
Keepalives and pings.
authormdw <mdw>
Fri, 16 Sep 2005 13:08:42 +0000 (13:08 +0000)
committermdw <mdw>
Fri, 16 Sep 2005 13:08:42 +0000 (13:08 +0000)
admin.c
doc/tripe-admin.5
ethereal/packet-tripe.c
peer.c
tripe-protocol.h
tripe.h

diff --git a/admin.c b/admin.c
index bbfc0ed..e081bdf 100644 (file)
--- a/admin.c
+++ b/admin.c
@@ -71,6 +71,7 @@ static sig s_term, s_int, s_hup;
 #define F_INIT 2u
 
 #define T_RESOLVE SEC(30)
+#define T_PING SEC(5)
 
 static void a_destroy(admin */*a*/);
 static void a_lock(admin */*a*/);
@@ -467,6 +468,29 @@ static void a_sighup(int sig, void *v)
   a_warn("SERVER ignore signal SIGHUP");
 }
 
+/* --- @a_parsetime@ --- *
+ *
+ * Arguments;  @const char *p@ = time string to parse
+ *
+ * Returns:    Time in seconds, or @< 0@ on error.
+ */
+
+static long a_parsetime(const char *p)
+{
+  char *q;
+  long t = strtol(p, &q, 0);
+
+  switch (*q) {
+    case 'd': t *= 24;
+    case 'h': t *= 60;
+    case 'm': t *= 60;
+    case 's': if (q[1] != 0)
+    default:    t = -1;
+    case 0:   break;
+  }
+  return (t);    
+}
+
 /*----- Adding peers ------------------------------------------------------*/
  
 /* --- @a_resolve@ --- *
@@ -489,18 +513,18 @@ static void a_resolve(struct hostent *h, void *v)
   sel_rmtimer(&a->t);
   if (!h)
     a_fail(a, "resolve-error %s", a->paddr);
-  else if (p_find(a->pname))
-    a_fail(a, "peer-exists %s", a->pname);
+  else if (p_find(a->peer.name))
+    a_fail(a, "peer-exists %s", a->peer.name);
   else {
-    memcpy(&a->peer.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
-    if (!p_create(a->pname, a->tops, &a->peer.sa, a->sasz))
-      a_fail(a, "peer-create-fail %s", a->pname);
+    memcpy(&a->peer.sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
+    if (!p_create(&a->peer))
+      a_fail(a, "peer-create-fail %s", a->peer.name);
     else
       a_ok(a);
   }
-  xfree(a->pname);
+  xfree(a->peer.name);
   xfree(a->paddr);
-  a->pname = 0;
+  a->peer.name = 0;
   selbuf_enable(&a->b);
   a_unlock(a);
 }
@@ -523,9 +547,9 @@ static void a_timer(struct timeval *tv, void *v)
   T( trace(T_ADMIN, "admin: %u resolver timeout", a->seq); )
   bres_abort(&a->r);
   a_fail(a, "resolver-timeout %s\n", a->paddr);
-  xfree(a->pname);
+  xfree(a->peer.name);
   xfree(a->paddr);
-  a->pname = 0;
+  a->peer.name = 0;
   selbuf_enable(&a->b);
   a_unlock(a);
 }
@@ -546,7 +570,6 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
   unsigned long pt;
   struct timeval tv;
   unsigned i, j;
-  const tunnel_ops *tops = tun_default;
   char *p;
 
   /* --- Make sure someone's not got there already --- */
@@ -556,6 +579,12 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     return;
   }
 
+  /* --- Set stuff up --- */
+
+  a->peer.name = av[0];
+  a->peer.t_ka = 0;
+  a->peer.tops = tun_default;
+
   /* --- Parse options --- */
 
   i = 1;
@@ -563,37 +592,42 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     if (!av[i])
       goto bad_syntax;
     if (mystrieq(av[i], "-tunnel")) {
-      i++;
-      if (!av[i])
-       goto bad_syntax;
+      if (!av[++i]) goto bad_syntax;
       for (j = 0;; j++) {
        if (!tunnels[j]) {
          a_fail(a, "unknown-tunnel %s", av[i]);
          return;
        }
        if (mystrieq(av[i], tunnels[j]->name)) {
-         tops = tunnels[j];
+         a->peer.tops = tunnels[j];
          break;
        }
       }
-      i++;
+    } else if (mystrieq(av[i], "-keepalive")) {
+      long t;
+      if (!av[++i]) goto bad_syntax;
+      if ((t = a_parsetime(av[i])) < 0) {
+       a_fail(a, "bad-time-spec %s", av[i]);
+       return;
+      }
+      a->peer.t_ka = t;
     } else if (mystrieq(av[i], "--")) {
       i++;
       break;
     } else
       break;
+    i++;
   }
 
   /* --- Fill in the easy bits of address --- */
 
-  BURN(a->peer);
   if (mystrieq(av[i], "inet")) i++;
   if (ac - i != 2) {
-    a_fail(a, "bad-syntax -- add PEER [-tunnel TUN] [inet] ADDRESS PORT");
+    a_fail(a, "bad-syntax -- add PEER [OPTIONS] [inet] ADDRESS PORT");
     return;
   }
-  a->peer.sin.sin_family = AF_INET;
-  a->sasz = sizeof(a->peer.sin);
+  a->peer.sa.sin.sin_family = AF_INET;
+  a->peer.sasz = sizeof(a->peer.sa.sin);
   pt = strtoul(av[i + 1], &p, 0);
   if (*p) {
     struct servent *s = getservbyname(av[i + 1], "udp");
@@ -607,15 +641,16 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     a_fail(a, "invalid-port %lu", pt);
     return;
   }
-  a->peer.sin.sin_port = htons(pt);
+  a->peer.sa.sin.sin_port = htons(pt);
 
   /* --- If the name is numeric, do it the easy way --- */
   
-  if (inet_aton(av[i], &a->peer.sin.sin_addr)) {
-    if (!p_create(av[0], tops, &a->peer.sa, a->sasz))
+  if (inet_aton(av[i], &a->peer.sa.sin.sin_addr)) {
+    if (!p_create(&a->peer))
       a_fail(a, "peer-create-fail %s", av[0]);
     else
       a_ok(a);
+    a->peer.name = 0;
     return;
   }
 
@@ -626,9 +661,8 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
    * the system continues regardless), but makes life simpler for the client.
    */
 
-  a->pname = xstrdup(av[0]);
+  a->peer.name = xstrdup(av[0]);
   a->paddr = xstrdup(av[i]);
-  a->tops = tops;
   selbuf_disable(&a->b);
   gettimeofday(&tv, 0);
   tv.tv_sec += T_RESOLVE;
@@ -639,10 +673,108 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
   return;
 
 bad_syntax:
-  a_fail(a, "bad-syntax -- add PEER [-tunnel TUN] ADDR ...");
+  a_fail(a, "bad-syntax -- add PEER [OPTIONS] ADDR ...");
+  return;
+}
+
+/*----- Ping --------------------------------------------------------------*/
+
+/* --- @a_pong@ --- *
+ *
+ * Arguments:  @int rc@ = return code
+ *             @void *av@ = admin connection which requested the ping
+ *
+ * Returns:    ---
+ *
+ * Use:                Collects what happened to a ping message.
+ */
+
+static void a_pong(int rc, void *av)
+{
+  admin *a = av;
+  struct timeval tv;
+  double millis;
+
+  a_lock(a);
+  switch (rc) {
+    case PING_OK:
+      gettimeofday(&tv, 0);
+      tv_sub(&tv, &tv, &a->pingtime);
+      millis = (double)tv.tv_sec * 1000 + (double)tv.tv_usec/1000;
+      a_info(a, "ping-ok %.1f", millis);
+      a_ok(a);
+      break;
+    case PING_TIMEOUT:
+      a_info(a, "ping-timeout");
+      a_ok(a);
+      break;
+    case PING_PEERDIED:
+      a_info(a, "ping-peer-died");
+      a_ok(a);
+      break;
+    default:
+      abort();
+  }
+  a_unlock(a);
+}
+
+/* --- @acmd_ping@, @acmd_eping@ --- *
+ *
+ * Arguments:  @admin *a@ = connection which requested the ping
+ *             @unsigned ac@ = argument count
+ *             @char *av[]@ = pointer to the argument list
+ *
+ * Returns:    ---
+ *
+ * Use:                Pings a peer.
+ */
+
+static void a_ping(admin *a, unsigned ac, char *av[],
+                  const char *cmd, unsigned msg)
+{
+  long t = T_PING;
+  int i;
+  peer *p;
+
+  i = 0;
+  for (;;) {
+    if (!av[i])
+      goto bad_syntax;
+    if (mystrieq(av[i], "-timeout")) {
+      if (!av[++i]) goto bad_syntax;
+      if ((t = a_parsetime(av[i])) < 0) {
+       a_fail(a, "bad-time-spec %s", av[i]);
+       return;
+      }
+    } else if (mystrieq(av[i], "--")) {
+      i++;
+      break;
+    } else
+      break;
+    i++;
+  }
+
+  if (!av[i]) goto bad_syntax;
+  if ((p = p_find(av[i])) == 0) {
+    a_fail(a, "unknown-peer %s", av[i]);
+    return;
+  }
+  gettimeofday(&a->pingtime, 0);
+  if (p_pingsend(p, &a->ping, msg, t, a_pong, a))
+    a_fail(a, "ping-send-failed");
+  return;
+    
+bad_syntax:
+  a_fail(a, "bad-syntax -- %s [OPTIONS] PEER", cmd);
   return;
 }
 
+static void acmd_ping(admin *a, unsigned ac, char *av[])
+  { a_ping(a, ac, av, "ping", MISC_PING); }
+static void acmd_eping(admin *a, unsigned ac, char *av[])
+  { a_ping(a, ac, av, "eping", MISC_EPING); }
+  
+
 /*----- Administration commands -------------------------------------------*/
 
 /* --- Miscellaneous commands --- */
@@ -856,6 +988,17 @@ static void acmd_kill(admin *a, unsigned ac, char *av[])
   }
 }
 
+static void acmd_forcekx(admin *a, unsigned ac, char *av[])
+{
+  peer *p;
+  if ((p = p_find(av[0])) == 0)
+    a_fail(a, "unknown-peer %s", av[0]);
+  else {
+    kx_start(&p->kx);
+    a_ok(a);
+  }
+}
+
 static void acmd_quit(admin *a, unsigned ac, char *av[])
 {
   a_warn("SERVER quit admin-request");
@@ -903,8 +1046,11 @@ static const acmd acmdtab[] = {
   { "ifname",  "ifname PEER",          1,      1,      acmd_ifname },
   { "addr",    "addr PEER",            1,      1,      acmd_addr },
   { "stats",   "stats PEER",           1,      1,      acmd_stats },
+  { "ping",    "ping [OPTIONS] PEER",  1,      0xffff, acmd_ping },
+  { "eping",   "eping [OPTIONS] PEER", 1,      0xffff, acmd_eping },
   { "kill",    "kill PEER",            1,      1,      acmd_kill },
-  { "add",     "add PEER [-tunnel TUN] ADDR ...",
+  { "forcekx", "forcekx PEER",         1,      1,      acmd_forcekx },
+  { "add",     "add PEER [OPTIONS] ADDR ...",
                                        2,      0xffff, acmd_add },
   { "tunnels", "tunnels",              0,      0,      acmd_tunnels },
   { "quit",    "quit",                 0,      0,      acmd_quit },
@@ -955,12 +1101,14 @@ static void a_unlock(admin *a)
           a->seq); )
 
   selbuf_destroy(&a->b);
-  if (a->pname) {
-    xfree(a->pname);
+  if (a->peer.name) {
+    xfree(a->peer.name);
     xfree(a->paddr);
     bres_abort(&a->r);
     sel_rmtimer(&a->t);
   }
+  if (a->ping.p)
+    p_pingdone(&a->ping, PING_NONOTIFY);
   if (a->b.reader.fd != a->w.fd)
     close(a->b.reader.fd);
   close(a->w.fd);
@@ -1081,7 +1229,8 @@ void a_create(int fd_in, int fd_out, unsigned f)
   T( static unsigned seq = 0;
      a->seq = seq++; )
   T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); )
-  a->pname = 0;
+  a->peer.name = 0;
+  a->ping.p = 0;
   a->f = f;
   if (fd_in == STDIN_FILENO)
     a_stdin = a;
index 79874e9..327d1b5 100644 (file)
@@ -109,7 +109,22 @@ is the network address (see above for the format) at which the peer can
 be contacted.  The following options are recognised.
 .RS
 .TP
-.BI "-tunnel " tunnel
+.BI "\-keepalive " time
+Send a no-op packet if we've not sent a packet to the peer in the last
+.I time
+interval.  This is useful for persuading port-translating firewalls to
+believe that the `connection' is still active.  The
+.I time
+is expressed as a nonnegative integer followed optionally by
+.BR d ,
+.BR h ,
+.BR m ,
+or
+.BR s
+for days, hours, minutes, or seconds respectively; if no suffix is
+given, seconds are assumed.
+.TP
+.BI "\-tunnel " tunnel
 Use the named tunnel driver, rather than the default.
 .RE
 .TP
@@ -123,6 +138,14 @@ line reporting the IP address and port number stored for
 Causes the server to disassociate itself from its terminal and become a
 background task.  This only works once.  A warning is issued.
 .TP
+.BI "EPING \fR[" options "\fR] " peer
+Sends an encrypted ping to the peer, and expects an encrypted response.
+This checks that the peer is running (and not being impersonated), and
+that it can encrypt and decrypt packets correctly.  Options and
+responses are the same as for the
+.B PING
+command.
+.TP
 .B "HELP"
 Causes the server to emit an
 .B INFO
@@ -157,6 +180,44 @@ Issues a
 .B USER
 notification to all interested administration clients.
 .TP
+.BI "PING \fR[" options "\fR] " peer
+Send a transport-level ping to the peer.  The ping and its response are
+not encrypted or authenticated.  This command, possibly in conjunction
+with tracing, is useful for ensuring that UDP packets are actually
+flowing in both directions.  See also the
+.B EPING
+command.
+.IP
+An
+.B INFO
+line is printed describing the outcome:
+.RS
+.TP
+.BI "ping-ok " millis
+A response was received 
+.I millis
+after the ping was sent.
+.TP
+.BI "ping-timeout"
+No response was received within the time allowed.
+.TP
+.BI "ping-peer-died"
+The peer was killed (probably by another admin connection) before a
+response was received.
+.RE
+.IP
+Options recognized for this command are:
+.RS
+.TP
+.BI "\-timeout " time
+Wait for
+.I time
+seconds before giving up on a response.  The default is 5 seconds.  (The
+time format is the same as for the
+.B "ADD \-keepalive"
+option.)
+.RE
+.TP
 .B "PORT"
 Emits an
 .B INFO
@@ -316,6 +377,18 @@ server is already running as a daemon.
 (For any command.)  The command couldn't be understood: e.g., the number
 of arguments was wrong.
 .TP
+.BI "bad-time-spec " word
+The
+.I word
+is not a valid time interval specification.  Acceptable time
+specifications are nonnegative integers followed optionally by 
+.BR d ,
+.BR h ,
+.BR m ,
+or
+.BR s ,
+for days, hours, minutes, or seconds, respectively.
+.TP
 .BI "bad-trace-option " char
 (For
 .BR TRACE .)
@@ -351,6 +424,10 @@ why.
 There is already a peer named
 .IR peer .
 .TP
+.B "ping-send-failed"
+The attempt to send a ping packet failed, probably due to lack of
+encryption keys.
+.TP
 .BI "resolve-error " hostname
 (For
 .BR ADD .)
@@ -571,13 +648,6 @@ a peer, or
 .RB ` \- '
 if none is relevant.
 .TP
-.BI "PEER \- unexpected-source " address\fR...
-A packet arrived from
-.I address
-(a network address \(en see above), but no peer is known at that
-address.  This may indicate a misconfiguration, or simply be a result of
-one end of a connection being set up before the other.
-.TP
 .BI "PEER " peer " bad-packet no-type"
 An empty packet arrived.  This is very strange.
 .TP
@@ -593,6 +663,15 @@ The message type
 (in hex) isn't understood.  Probably a strange random packet from
 somewhere; could be an unlikely bug.
 .TP
+.BI "PEER " peer " corrupt-encrypted-ping"
+The peer sent a ping response which matches an outstanding ping, but its
+payload is wrong.  There's definitely a bug somewhere.
+.TP
+.BI "PEER " peer " corrupt-transport-ping"
+The peer (apparently) sent a ping response which matches an outstanding
+ping, but its payload is wrong.  Either there's a bug, or the bad guys
+are playing tricks on you.
+.TP
 .BI "PEER " peer " decrypt-failed"
 An encrypted IP packet failed to decrypt.  It may have been mangled in
 transit, or may be a very old packet from an expired previous session
@@ -600,6 +679,14 @@ key.  There is usually a considerable overlap in the validity periods of
 successive session keys, so this shouldn't occur unless the key exchange
 takes ages or fails.
 .TP
+.BI "PEER " peer " malformed-encrypted-ping"
+The peer sent a ping response which is hopelessly invalid.  There's
+definitely a bug somewhere.
+.TP
+.BI "PEER " peer " malformed-transport-ping"
+The peer (apparently) sent a ping response which is hopelessly invalid.
+Either there's a bug, or the bad guys are playing tricks on you.
+.TP
 .BI "PEER " peer " packet-build-failed"
 There wasn't enough space in our buffer to put the packet we wanted to
 send.  Shouldn't happen.
@@ -610,6 +697,24 @@ An error occurred trying to read an incoming packet.
 .BI "PEER " peer " socket-write-error \-\- " message
 An error occurred attempting to send a network packet.  We lost that
 one.
+.TP
+.BI "PEER " peer " unexpected-encrypted-ping 0x" id
+The peer sent an encrypted ping response whose id doesn't match any
+outstanding ping.  Maybe it was delayed for longer than the server was
+willing to wait, or maybe the peer has gone mad.
+.TP
+.BI "PEER \- unexpected-source " address\fR...
+A packet arrived from
+.I address
+(a network address \(en see above), but no peer is known at that
+address.  This may indicate a misconfiguration, or simply be a result of
+one end of a connection being set up before the other.
+.TP
+.BI "PEER " peer " unexpected-transport-ping 0x" id
+The peer (apparently) sent a transport ping response whose id doesn't
+match any outstanding ping.  Maybe it was delayed for longer than the
+server was willing to wait, or maybe the peer has gone mad; or maybe
+there are bad people trying to confuse you.
 .SS "SERVER warnings"
 These indicate problems concerning the server process as a whole.
 .TP
index aaad0ec..44c74aa 100644 (file)
@@ -64,6 +64,8 @@ static int hf_tripe_ct_seq = -1;
 static int hf_tripe_ct_iv = -1;
 static int hf_tripe_ct_ct = -1;
 static int hf_tripe_ct_tag = -1;
+static int hf_tripe_misc_type = -1;
+static int hf_tripe_misc_payload = -1;
 static int hf_tripe_kx_type = -1;
 static hfge hf_tripe_kx_mychal = { -1, -1, -1, -1, -1, -1, -1, -1 };
 static int hf_tripe_kx_mycookie = -1;
@@ -178,6 +180,32 @@ static void dissect_tripe(tvbuff_t *b, packet_info *p, proto_tree *t)
            break;
        }
        break;
+      case MSG_MISC:
+       switch (ty & MSG_TYPEMASK) {
+         case MISC_NOP:
+           col_set_str(p->cinfo, COL_INFO, "Miscellaneous, no-operation");
+           break;
+         case MISC_PING:
+           col_set_str(p->cinfo, COL_INFO, "Miscellaneous, transport ping");
+           break;
+         case MISC_PONG:
+           col_set_str(p->cinfo, COL_INFO,
+                       "Miscellaneous, transport ping reply");
+           break;
+         case MISC_EPING:
+           col_set_str(p->cinfo, COL_INFO, "Miscellaneous, encrypted ping");
+           break;
+         case MISC_EPONG:
+           col_set_str(p->cinfo, COL_INFO,
+                       "Miscellaneous, encrypted ping reply");
+           break;
+         default:
+           col_add_fstr(p->cinfo, COL_INFO,
+                        "Miscellaneous, unknown type code %u",
+                        ty & MSG_TYPEMASK);
+           break;
+       }
+       break;
       default:
        col_add_fstr(p->cinfo, COL_INFO,
                     "Unknown category code %u, unknown type code %u",
@@ -235,6 +263,22 @@ static void dissect_tripe(tvbuff_t *b, packet_info *p, proto_tree *t)
            goto huh;
        }
        break;
+      case MSG_MISC:
+       proto_tree_add_item(tt, hf_tripe_misc_type, b, 0, 1, FALSE);
+       switch (ty & MSG_TYPEMASK) {
+         case MISC_NOP:
+         case MISC_PING:
+         case MISC_PONG:
+           proto_tree_add_item(tt, hf_tripe_misc_payload,
+                               b, off, -1, FALSE);
+           goto done;
+         case MISC_EPING:
+         case MISC_EPONG:
+           goto ct;
+         default:
+           goto huh;
+       }
+       break;
       default:
        goto huh;
     }
@@ -316,6 +360,16 @@ void proto_register_tripe(void)
       FT_BYTES, BASE_NONE, 0, 0,
       "This is the message authentication code tag for the ciphertext."
     } },
+    { &hf_tripe_misc_type, {
+      "Miscellaneous message type", "tripe.misc.type",
+      FT_UINT8, BASE_HEX, 0, MSG_TYPEMASK,
+      "This is the TrIPE miscellaneous message type subcode."
+    } },
+    { &hf_tripe_misc_payload, {
+      "Miscellaneous message type", "tripe.misc.payload",
+      FT_BYTES, BASE_NONE, 0, 0,
+      "This is the miscellaneous message payload."
+    } },
     { &hf_tripe_kx_type, {
       "Key-exchange message type", "tripe.kx.type",
       FT_UINT8, BASE_HEX, vs_kxtype, MSG_TYPEMASK,
diff --git a/peer.c b/peer.c
index 402d08b..15a12b7 100644 (file)
--- a/peer.c
+++ b/peer.c
@@ -53,6 +53,73 @@ const tunnel_ops *tunnels[] = {
 
 /*----- Main code ---------------------------------------------------------*/
 
+/* --- @p_pingtype@ --- *
+ *
+ * Arguments:  @unsigned msg@ = message type
+ *
+ * Returns:    String to describe the message.
+ */
+
+static const char *p_pingtype(unsigned msg)
+{
+  switch (msg & MSG_TYPEMASK) {
+    case MISC_PING:
+    case MISC_PONG:
+      return "transport-ping";
+    case MISC_EPING:
+    case MISC_EPONG:
+      return "encrypted-ping";
+    default:
+      abort();
+  }
+}
+
+/* --- @p_ponged@ --- *
+ *
+ * Arguments:  @peer *p@ = peer packet arrived from
+ *             @unsigned msg@ = message type
+ *             @buf *b@ = buffer containing payload
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes a ping response.
+ */
+
+static void p_ponged(peer *p, unsigned msg, buf *b)
+{
+  uint32 id;
+  const octet *magic;
+  ping *pg;
+  
+  IF_TRACING(T_PEER, {
+    trace(T_PEER, "peer: received %s reply from %s",
+         p_pingtype(msg), p->spec.name);
+    trace_block(T_PACKET, "peer: ping contents", BBASE(b), BSZ(b));
+  })
+
+  if (buf_getu32(b, &id) ||
+      (magic = buf_get(b, sizeof(pg->magic))) == 0 ||
+      BLEFT(b)) {
+    a_warn("PEER %s malformed-%s", p->spec.name, p_pingtype(msg));
+    return;
+  }
+
+  for (pg = p->pings; pg; pg = pg->next) {
+    if (pg->id == id)
+      goto found;
+  }
+  a_warn("PEER %s unexpected-%s 0x%08lx",
+        p->spec.name, p_pingtype(msg), (unsigned long)id);
+  return;
+
+found:
+  if (memcmp(magic, pg->magic, sizeof(pg->magic)) != 0) {
+    a_warn("PEER %s corrupt-%s", p->spec.name, p_pingtype(msg));
+    return;
+  }
+  p_pingdone(pg, PING_OK);
+}
+
 /* --- @p_read@ --- *
  *
  * Arguments:  @int fd@ = file descriptor to read from
@@ -87,8 +154,8 @@ static void p_read(int fd, unsigned mode, void *v)
 
   assert(a.sa.sa_family == AF_INET);
   for (p = peers; p; p = p->next) {
-    if (p->peer.sin.sin_addr.s_addr == a.sin.sin_addr.s_addr &&
-       p->peer.sin.sin_port == a.sin.sin_port)
+    if (p->spec.sa.sin.sin_addr.s_addr == a.sin.sin_addr.s_addr &&
+       p->spec.sa.sin.sin_port == a.sin.sin_port)
       goto found;
   }
   a_warn("PEER - unexpected-source INET %s %u",
@@ -97,7 +164,7 @@ static void p_read(int fd, unsigned mode, void *v)
 
 found:
   IF_TRACING(T_PEER, {
-    trace(T_PEER, "peer: packet received from `%s'", p->name);
+    trace(T_PEER, "peer: packet received from `%s'", p->spec.name);
     trace_block(T_PACKET, "peer: packet contents", buf_i, n);
   })
 
@@ -108,20 +175,20 @@ found:
   p->st.sz_in += n;
   buf_init(&b, buf_i, n);
   if ((ch = buf_getbyte(&b)) < 0) {
-    a_warn("PEER %s bad-packet no-type", p->name);
+    a_warn("PEER %s bad-packet no-type", p->spec.name);
     return;
   }
   switch (ch & MSG_CATMASK) {
     case MSG_PACKET:
       if (ch & MSG_TYPEMASK) {
-       a_warn("PEER %s bad-packet unknown-type 0x%02x", p->name, ch);
+       a_warn("PEER %s bad-packet unknown-type 0x%02x", p->spec.name, ch);
        p->st.n_reject++;
        return;
       }
       buf_init(&bb, buf_o, sizeof(buf_o));
       if (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) {
        p->st.n_reject++;
-       a_warn("PEER %s decrypt-failed", p->name);
+       a_warn("PEER %s decrypt-failed", p->spec.name);
        return;
       }
       if (BOK(&bb)) {
@@ -130,15 +197,56 @@ found:
        p->t->ops->inject(p->t, &bb);
       } else {
        p->st.n_reject++;
-       a_warn("PEER %s packet-build-failed", p->name);
+       a_warn("PEER %s packet-build-failed", p->spec.name);
       }
       break;
     case MSG_KEYEXCH:
       kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
       break;
+    case MSG_MISC:
+      switch (ch & MSG_TYPEMASK) {
+       case MISC_NOP:
+         T( trace(T_PEER, "peer: received NOP packet"); )
+         break;
+       case MISC_PING:
+         buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b));
+         p_txend(p);
+         break;        
+       case MISC_PONG:
+         p_ponged(p, MISC_PONG, &b);
+         break;
+       case MISC_EPING:
+         buf_init(&bb, buf_t, sizeof(buf_t));
+         if (ksl_decrypt(&p->ks, ch, &b, &bb)) {
+           p->st.n_reject++;
+           a_warn("PEER %s decrypt-failed", p->spec.name);
+           return;
+         }
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPONG, &bb,
+                           p_txstart(p, MSG_MISC | MISC_EPONG)))
+             kx_start(&p->kx);
+           p_txend(p);
+         }
+         break;
+       case MISC_EPONG:
+         buf_init(&bb, buf_t, sizeof(buf_t));
+         if (ksl_decrypt(&p->ks, ch, &b, &bb)) {
+           p->st.n_reject++;
+           a_warn("PEER %s decrypt-failed", p->spec.name);
+           return;
+         }
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           p_ponged(p, MISC_EPONG, &bb);
+         }
+         break;
+      }
+      break;
     default:
       p->st.n_reject++;
-      a_warn("PEER %s bad-packet unknown-category 0x%02x", p->name, ch);
+      a_warn("PEER %s bad-packet unknown-category 0x%02x", p->spec.name, ch);
       break;
   }
 }
@@ -170,23 +278,157 @@ buf *p_txstart(peer *p, unsigned msg)
  * Use:                Sends a packet to the peer.
  */
 
-void p_txend(peer *p)
+static void p_setkatimer(peer *);
+
+static int p_dotxend(peer *p)
 {
   if (!BOK(&p->b)) {
-    a_warn("PEER %s packet-build-failed", p->name);
-    return;
+    a_warn("PEER %s packet-build-failed", p->spec.name);
+    return (0);
   }
   IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet",
                                 BBASE(&p->b), BLEN(&p->b)); )
   if (sendto(sock.fd, BBASE(&p->b), BLEN(&p->b),
-            0, &p->peer.sa, p->sasz) < 0)
-    a_warn("PEER %s socket-write-error -- %s", p->name, strerror(errno));
-  else {
+            0, &p->spec.sa.sa, p->spec.sasz) < 0) {
+    a_warn("PEER %s socket-write-error -- %s",
+          p->spec.name, strerror(errno));
+    return (0);
+  } else {
     p->st.n_out++;
     p->st.sz_out += BLEN(&p->b);
+    return (1);
   }
 }
 
+void p_txend(peer *p)
+{
+  if (p_dotxend(p) && p->spec.t_ka) {
+    sel_rmtimer(&p->tka);
+    p_setkatimer(p);
+  }
+}
+
+/* --- @p_pingwrite@ --- *
+ *
+ * Arguments:  @ping *p@ = ping structure
+ *             @buf *b@ = buffer to write in
+ *
+ * Returns:    ---
+ *
+ * Use:                Fills in a ping structure and writes the packet payload.
+ */
+
+static void p_pingwrite(ping *p, buf *b)
+{
+  static uint32 seq = 0;
+
+  p->id = U32(seq++);
+  GR_FILL(&rand_global, p->magic, sizeof(p->magic));
+  buf_putu32(b, p->id);
+  buf_put(b, p->magic, sizeof(p->magic));
+}
+
+/* --- @p_pingdone@ --- *
+ *
+ * Arguments:  @ping *p@ = ping structure
+ *             @int rc@ = return code to pass on
+ *
+ * Returns:    ---
+ *
+ * Use:                Disposes of a ping structure, maybe sending a notification.
+ */
+
+void p_pingdone(ping *p, int rc)
+{
+  if (!p->p) return;
+  if (p->prev) p->prev->next = p->next;
+  else p->p->pings = p->next;
+  if (p->next) p->next->prev = p->prev;
+  if (rc != PING_TIMEOUT) sel_rmtimer(&p->t);
+  p->p = 0;
+  if (rc >= 0) p->func(rc, p->arg);
+}
+
+/* --- @p_pingtimeout@ --- *
+ *
+ * Arguments:  @struct timeval *now@ = the time now
+ *             @void *pv@ = pointer to ping block
+ *
+ * Returns:    ---
+ *
+ * Use:                Called when a ping times out.
+ */
+
+static void p_pingtimeout(struct timeval *now, void *pv)
+{
+  ping *p = pv;
+
+  T( trace(T_PEER, "peer: ping 0x%08lx timed out", (unsigned long)p->id); )
+  p_pingdone(p, PING_TIMEOUT);
+}
+
+/* --- @p_pingsend@ --- *
+ *
+ * Arguments:  @peer *p@ = destination peer
+ *             @ping *pg@ = structure to fill in
+ *             @unsigned type@ = message type
+ *             @unsigned long timeout@ = how long to wait before giving up
+ *             @void (*func)(int, void *)@ = callback function
+ *             @void *arg@ = argument for callback
+ *
+ * Returns:    Zero if successful, nonzero if it failed.
+ *
+ * Use:                Sends a ping to a peer.  Call @func@ with a nonzero argument
+ *             if we get an answer within the timeout, or zero if no answer.
+ */
+
+int p_pingsend(peer *p, ping *pg, unsigned type,
+              unsigned long timeout,
+              void (*func)(int, void *), void *arg)
+{
+  buf *b, bb;
+  struct timeval tv;
+
+  assert(!pg->p);
+
+  switch (type) {
+    case MISC_PING:
+      pg->msg = MISC_PONG;
+      b = p_txstart(p, MSG_MISC | MISC_PING);
+      p_pingwrite(pg, b);
+      p_txend(p);
+      break;
+    case MISC_EPING:
+      pg->msg = MISC_EPONG;
+      b = p_txstart(p, MSG_MISC | MISC_EPING);
+      buf_init(&bb, buf_t, sizeof(buf_t));
+      p_pingwrite(pg, &bb);
+      buf_flip(&bb);
+      if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPING, &bb, b))
+       kx_start(&p->kx);
+      if (!BOK(b))
+       return (-1);
+      p_txend(p);
+      break;
+    default:
+      abort();
+      break;
+  }
+
+  pg->next = p->pings;
+  pg->prev = 0;
+  pg->p = p;
+  pg->func = func;
+  pg->arg = arg;
+  p->pings = pg;
+  gettimeofday(&tv, 0);
+  tv.tv_sec += timeout;
+  sel_addtimer(&sel, &pg->t, &tv, p_pingtimeout, pg);
+  T( trace(T_PEER, "peer: send %s 0x%08lx to %s",
+          p_pingtype(type), (unsigned long)pg->id, p->spec.name); )
+  return (0);
+}
+
 /* --- @p_tun@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -259,7 +501,7 @@ const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); }
  * Returns:    A pointer to the peer's address.
  */
 
-const addr *p_addr(peer *p) { return (&p->peer); }
+const addr *p_addr(peer *p) { return (&p->spec.sa); }
 
 /* --- @p_init@ --- *
  *
@@ -322,12 +564,47 @@ unsigned p_port(void)
   return (ntohs(a.sin.sin_port));
 }
 
+/* --- @p_keepalive@ --- *
+ *
+ * Arguments:  @struct timeval *now@ = the current time
+ *             @void *pv@ = peer to wake up
+ *
+ * Returns:    ---
+ *
+ * Use:                Sends a keepalive ping message to its peer.
+ */
+
+static void p_keepalive(struct timeval *now, void *pv)
+{
+  peer *p = pv;
+  p_txstart(p, MSG_MISC | MISC_NOP); p_dotxend(p);
+  T( trace(T_PEER, "peer: sent keepalive to %s", p->spec.name); )
+  p_setkatimer(p);
+}
+
+/* --- @p_setkatimer@ --- *
+ *
+ * Arguments:  @peer *p@ = peer to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Resets the keepalive timer thing.
+ */
+
+static void p_setkatimer(peer *p)
+{
+  struct timeval tv;
+
+  if (!p->spec.t_ka)
+    return;
+  gettimeofday(&tv, 0);
+  tv.tv_sec += p->spec.t_ka;
+  sel_addtimer(&sel, &p->tka, &tv, p_keepalive, p);
+}
+
 /* --- @p_create@ --- *
  *
- * Arguments:  @const char *name@ = name for this peer
- *             @const tunnel_ops *tops@ = tunnel to use
- *             @struct sockaddr *sa@ = socket address of peer
- *             @size_t sz@ = size of socket address
+ * Arguments:  @peerspec *spec@ = information about this peer
  *
  * Returns:    Pointer to the peer block, or null if it failed.
  *
@@ -335,46 +612,47 @@ unsigned p_port(void)
  *             by this point.
  */
 
-peer *p_create(const char *name, const tunnel_ops *tops,
-              struct sockaddr *sa, size_t sz)
+peer *p_create(peerspec *spec)
 {
   peer *p = CREATE(peer);
 
-  T( trace(T_PEER, "peer: creating new peer `%s'", name); )
-  p->name = xstrdup(name);
+  T( trace(T_PEER, "peer: creating new peer `%s'", spec->name); )
+  p->spec = *spec;
+  p->spec.name = xstrdup(spec->name);
   p->ks = 0;
   p->prev = 0;
-  memcpy(&p->peer.sa, sa, sz);
-  p->sasz = sz;
   memset(&p->st, 0, sizeof(stats));
   p->st.t_start = time(0);
-  if (kx_init(&p->kx, p, &p->ks))
+  if ((p->t = spec->tops->create(p)) == 0)
     goto tidy_0;
-  if ((p->t = tops->create(p)) == 0)
+  p_setkatimer(p);
+  if (kx_init(&p->kx, p, &p->ks))
     goto tidy_1;
   p->next = peers;
   if (peers)
     peers->prev = p;
   peers = p;
-  switch (p->peer.sa.sa_family) {
+  switch (p->spec.sa.sa.sa_family) {
     case AF_INET:
       a_notify("ADD %s %s INET %s %u",
-              name,
+              spec->name,
               p->t->ops->ifname(p->t),
-              inet_ntoa(p->peer.sin.sin_addr),
-              (unsigned)ntohs(p->peer.sin.sin_port));
+              inet_ntoa(p->spec.sa.sin.sin_addr),
+              (unsigned)ntohs(p->spec.sa.sin.sin_port));
       break;
     default:
-      a_notify("ADD %s %s UNKNOWN", name, p->t->ops->ifname(p->t));
+      a_notify("ADD %s %s UNKNOWN", spec->name, p->t->ops->ifname(p->t));
       break;
   }
-  a_notify("KXSTART %s", name);                /* Couldn't tell anyone before */
+  a_notify("KXSTART %s", spec->name);  /* Couldn't tell anyone before */
   return (p);
 
 tidy_1:
-  kx_free(&p->kx);
+  if (spec->t_ka)
+    sel_rmtimer(&p->tka);
+  p->t->ops->destroy(p->t);
 tidy_0:
-  xfree(p->name);
+  xfree(p->spec.name);
   DESTROY(p);
   return (0);
 }
@@ -386,7 +664,7 @@ tidy_0:
  * Returns:    A pointer to the peer's name.
  */
 
-const char *p_name(peer *p) { return (p->name); }
+const char *p_name(peer *p) { return (p->spec.name); }
 
 /* --- @p_find@ --- *
  *
@@ -401,7 +679,7 @@ peer *p_find(const char *name)
 {
   peer *p;
   for (p = peers; p; p = p->next) {
-    if (strcmp(name, p->name) == 0)
+    if (strcmp(name, p->spec.name) == 0)
       return (p);
   }
   return (0);  
@@ -418,12 +696,20 @@ peer *p_find(const char *name)
 
 void p_destroy(peer *p)
 {
-  T( trace(T_PEER, "peer: destroying peer `%s'", p->name); )
-  a_notify("KILL %s", p->name);
+  ping *pg, *ppg;
+
+  T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+  a_notify("KILL %s", p->spec.name);
   ksl_free(&p->ks);
   kx_free(&p->kx);
   p->t->ops->destroy(p->t);
-  xfree(p->name);
+  if (p->spec.t_ka)
+    sel_rmtimer(&p->tka);
+  xfree(p->spec.name);
+  for (pg = p->pings; pg; pg = ppg) {
+    ppg = pg->next;
+    p_pingdone(pg, PING_PEERDIED);
+  }
   if (p->next)
     p->next->prev = p->prev;
   if (p->prev)
index 1961785..ebf7a71 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: tripe-protocol.h,v 1.2 2004/04/08 01:36:17 mdw Exp $
+ * $Id$
  *
  * Protocol definition for TrIPE
  *
 #define KX_SWITCHOK 5u
 #define KX_NMSG 6u
 
+/* --- Miscellaneous packets --- */
+
+#define MSG_MISC 0x20
+
+#define MISC_NOP 0u                    /* Do nothing; ignore me */
+#define MISC_PING 1u                   /* Transport-level ping */
+#define MISC_PONG 2u                   /* Transport-level ping response */
+#define MISC_EPING 3u                  /* Encrypted ping */
+#define MISC_EPONG 4u                  /* Encrypted ping response */
+
 /* --- Symmetric encryption and keysets --- *
  *
  * Packets consist of an 80-bit MAC, a 32-bit sequence number, and the
  * encrypted payload.
  *
  * The plaintext is encrypted using Blowfish in CBC mode with ciphertext
- * stealing (as described in [Schneier].  The initialization vector is
+ * stealing (as described in [Schneier]).  The initialization vector is
  * selected randomly, and prepended to the actual ciphertext.
  *
  * The MAC is computed using the HMAC construction with RIPEMD160 over the
diff --git a/tripe.h b/tripe.h
index 9cf7abf..002b373 100644 (file)
--- a/tripe.h
+++ b/tripe.h
@@ -80,6 +80,7 @@
 #include <mLib/str.h>
 #include <mLib/sub.h>
 #include <mLib/trace.h>
+#include <mLib/tv.h>
 
 #include <catacomb/buf.h>
 
@@ -292,18 +293,45 @@ typedef struct stats {
  * The main structure which glues everything else together.
  */
 
+typedef struct peerspec {
+  char *name;                          /* Peer's name */
+  const tunnel_ops *tops;              /* Tunnel operations */
+  unsigned long t_ka;                  /* Keep alive interval */
+  addr sa;                             /* Socket address to speak to */
+  size_t sasz;                         /* Socket address size */
+} peerspec;
+
 typedef struct peer {
   struct peer *next, *prev;            /* Links to next and previous */
-  char *name;                          /* Name of this peer */
+  struct ping *pings;                  /* Pings we're waiting for */
+  peerspec spec;                       /* Specifications for this peer */
   tunnel *t;                           /* Tunnel for local packets */
   keyset *ks;                          /* List head for keysets */
   buf b;                               /* Buffer for sending packets */
-  addr peer;                           /* Peer socket address */
-  size_t sasz;                         /* Socket address size */
   stats st;                            /* Statistics */
   keyexch kx;                          /* Key exchange protocol block */
+  sel_timer tka;                       /* Timer for keepalives */
 } peer;
 
+typedef struct ping {
+  struct ping *next, *prev;            /* Links to next and previous */
+  peer *p;                             /* Peer so we can free it */
+  unsigned msg;                                /* Kind of response expected */
+  uint32 id;                           /* Id so we can recognize response */
+  octet magic[32];                     /* Some random data */
+  sel_timer t;                         /* Timeout for ping */
+  void (*func)(int /*rc*/, void */*arg*/); /* Function to call when done */
+  void *arg;                           /* Argument for callback */
+} ping;
+
+enum {
+  PING_NONOTIFY = -1,
+  PING_OK = 0,
+  PING_TIMEOUT,
+  PING_PEERDIED,
+  PING_MAX
+};
+
 /* --- Admin structure --- */
 
 #define OBUFSZ 16384u
@@ -320,16 +348,15 @@ typedef struct admin {
 #ifndef NTRACE
   unsigned seq;                                /* Sequence number for tracing */
 #endif
-  char *pname;                         /* Peer name to create */
-  char *paddr;                         /* Address string to resolve */
   obuf *o_head, *o_tail;               /* Output buffer list */
   selbuf b;                            /* Line buffer for commands */
   sel_file w;                          /* Selector for write buffering */
+  peerspec peer;                       /* Peer pending creation */
+  char *paddr;                         /* Hostname to be resolved */
   bres_client r;                       /* Background resolver task */
   sel_timer t;                         /* Timer for resolver */
-  const tunnel_ops *tops;              /* Tunnel to use */
-  addr peer;                           /* Address to set */
-  size_t sasz;                         /* Size of the address */
+  ping ping;                           /* Ping pending response */
+  struct timeval pingtime;             /* Time last ping was sent */
 } admin;
 
 #define AF_DEAD 1u                     /* Destroy this admin block */
@@ -736,6 +763,37 @@ extern buf *p_txstart(peer */*p*/, unsigned /*msg*/);
 
 extern void p_txend(peer */*p*/);
 
+/* --- @p_pingsend@ --- *
+ *
+ * Arguments:  @peer *p@ = destination peer
+ *             @ping *pg@ = structure to fill in
+ *             @unsigned type@ = message type
+ *             @unsigned long timeout@ = how long to wait before giving up
+ *             @void (*func)(int, void *)@ = callback function
+ *             @void *arg@ = argument for callback
+ *
+ * Returns:    Zero if successful, nonzero if it failed.
+ *
+ * Use:                Sends a ping to a peer.  Call @func@ with a nonzero argument
+ *             if we get an answer within the timeout, or zero if no answer.
+ */
+
+extern int p_pingsend(peer */*p*/, ping */*pg*/, unsigned /*type*/,
+                     unsigned long /*timeout*/,
+                     void (*/*func*/)(int, void *), void */*arg*/);
+
+/* --- @p_pingdone@ --- *
+ *
+ * Arguments:  @ping *p@ = ping structure
+ *             @int rc@ = return code to pass on
+ *
+ * Returns:    ---
+ *
+ * Use:                Disposes of a ping structure, maybe sending a notification.
+ */
+
+extern void p_pingdone(ping */*p*/, int /*rc*/);
+
 /* --- @p_tun@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -809,10 +867,7 @@ unsigned p_port(void);
 
 /* --- @p_create@ --- *
  *
- * Arguments:  @const char *name@ = name for this peer
- *             @const tunnel_ops *tops@ = tunnel to use
- *             @struct sockaddr *sa@ = socket address of peer
- *             @size_t sz@ = size of socket address
+ * Arguments:  @peerspec *spec@ = information about this peer
  *
  * Returns:    Pointer to the peer block, or null if it failed.
  *
@@ -820,8 +875,7 @@ unsigned p_port(void);
  *             by this point.
  */
 
-extern peer *p_create(const char */*name*/, const tunnel_ops */*tops*/,
-                     struct sockaddr */*sa*/, size_t /*sz*/);
+extern peer *p_create(peerspec */*spec*/);
 
 /* --- @p_name@ --- *
  *