chiark / gitweb /
tripe-admin manpage: Generate a command and message summary.
[tripe] / peer.c
diff --git a/peer.c b/peer.c
index 83123d1..50dec93 100644 (file)
--- a/peer.c
+++ b/peer.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: peer.c,v 1.1 2001/02/03 20:26:37 mdw Exp $
+ * $Id$
  *
  * Communication with the peer
  *
  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------* 
- *
- * $Log: peer.c,v $
- * Revision 1.1  2001/02/03 20:26:37  mdw
- * Initial checkin.
- *
- */
-
 /*----- Header files ------------------------------------------------------*/
 
 #include "tripe.h"
 static peer *peers = 0;
 static sel_file sock;
 
+/*----- Tunnel table ------------------------------------------------------*/
+
+const tunnel_ops *tunnels[] = {
+#ifdef TUN_LINUX
+  &tun_linux,
+#endif
+#ifdef TUN_BSD
+  &tun_bsd,
+#endif
+#ifdef TUN_UNET
+  &tun_unet,
+#endif
+  &tun_slip,
+  0
+}, *tun_default;
+
 /*----- 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", "?PEER", p, "malformed-%s", p_pingtype(msg), A_END);
+    return;
+  }
+
+  for (pg = p->pings; pg; pg = pg->next) {
+    if (pg->id == id)
+      goto found;
+  }
+  a_warn("PEER",
+        "?PEER", p,
+        "unexpected-%s", p_pingtype(msg),
+        "0x%08lx", (unsigned long)id,
+        A_END);
+  return;
+
+found:
+  if (memcmp(magic, pg->magic, sizeof(pg->magic)) != 0) {
+    a_warn("PEER", "?PEER", p, "corrupt-%s", p_pingtype(msg), A_END);
+    return;
+  }
+  p_pingdone(pg, PING_OK);
+}
+
 /* --- @p_read@ --- *
  *
  * Arguments:  @int fd@ = file descriptor to read from
@@ -58,7 +136,7 @@ static sel_file sock;
 
 static void p_read(int fd, unsigned mode, void *v)
 {
-  peer *p;
+  peer *p = 0;
   addr a;
   size_t sz;
   ssize_t n;
@@ -67,63 +145,140 @@ static void p_read(int fd, unsigned mode, void *v)
 
   /* --- Read the data --- */
 
+  TIMER;
   sz = sizeof(addr);
   n = recvfrom(fd, buf_i, sizeof(buf_i), 0, &a.sa, &sz);
   if (n < 0) {
-    a_warn("error reading socket: %s", strerror(errno));
+    a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END);
+    return;
+  }
+
+  /* --- If the packet is a greeting, don't check peers --- */
+
+  if (n && buf_i[0] == (MSG_MISC | MISC_GREET)) {
+    IF_TRACING(T_PEER, {
+      trace(T_PEER, "peer: greeting received from INET %s %u",
+           inet_ntoa(a.sin.sin_addr),
+           (unsigned)ntohs(a.sin.sin_port));
+      trace_block(T_PACKET, "peer: greeting contents", buf_i, n);
+    })
+    buf_init(&b, buf_i, n);
+    buf_getbyte(&b);
+    if (c_check(&b) || BLEFT(&b)) {
+      a_warn("PEER", "-", "invalid-greeting", A_END);
+      return;
+    }
+    a_notify("GREET",
+            "?B64", buf_i + 1, (size_t)(n - 1),
+            "?ADDR", &a,
+            A_END);
     return;
   }
 
   /* --- Find the appropriate peer --- */
 
   assert(a.sa.sa_family == AF_INET);
-  T( trace(T_PEER, "packet from %s:%u", 
-          inet_ntoa(a.sin.sin_addr), (unsigned)ntohs(a.sin.sin_port)); )
   for (p = peers; p; p = p->next) {
-    T( trace(T_PEER, "trying %s:%u", 
-            inet_ntoa(p->peer.sin.sin_addr), (unsigned)ntohs(p->peer.sin.sin_port)); )
-    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("packet from unexpected peer: %s:%u",
-       inet_ntoa(a.sin.sin_addr), (unsigned)ntohs(a.sin.sin_port));
+  a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END);
   return;
 
 found:
-  T( trace(T_PEER, "peer: packet received from `%s'", p->name);
-     trace_block(T_PACKET, "peer: packet contents", buf_i, n); )
+  IF_TRACING(T_PEER, {
+    trace(T_PEER, "peer: packet received from `%s'", p->spec.name);
+    trace_block(T_PACKET, "peer: packet contents", buf_i, n);
+  })
 
   /* --- Pick the packet apart --- */
 
+  p->st.t_last = time(0);
+  p->st.n_in++;
+  p->st.sz_in += n;
   buf_init(&b, buf_i, n);
   if ((ch = buf_getbyte(&b)) < 0) {
-    a_warn("bad packet from `%s': no type byte", p->name);
+    a_warn("PEER", "?PEER", p, "bad-packet", "no-type", A_END);
     return;
   }
-  switch (ch) {
+  switch (ch & MSG_CATMASK) {
     case MSG_PACKET:
+      if (ch & MSG_TYPEMASK) {
+       a_warn("PEER",
+              "?PEER", p,
+              "bad-packet",
+              "unknown-type", "0x%02x", ch,
+              A_END);
+       p->st.n_reject++;
+       return;
+      }
       buf_init(&bb, buf_o, sizeof(buf_o));
-      if (ks_decrypt(&p->ks, &b, &bb)) {
-       a_warn("couldn't decrypt inbound packet");
+      if (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) {
+       p->st.n_reject++;
+       a_warn("PEER", "?PEER", p, "decrypt-failed", A_END);
        return;
       }
-      if (BOK(&bb))
-       tun_inject(&p->t, &bb);
-      else
-       a_warn("packet build failed");
-      break;
-    case MSG_PRECHALLENGE:
-      kx_prechallenge(&p->kx, &b);
+      if (BOK(&bb)) {
+       p->st.n_ipin++;
+       p->st.sz_ipin += BSZ(&b);
+       p->t->ops->inject(p->t, &bb);
+      } else {
+       p->st.n_reject++;
+       a_warn("PEER", "?PEER", p, "packet-build-failed", A_END);
+      }
       break;
-    case MSG_CHALLENGE:
-      kx_challenge(&p->kx, &b);
+    case MSG_KEYEXCH:
+      kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
       break;
-    case MSG_RESPONSE:
-      kx_response(&p->kx, &b);
+    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", "?PEER", "decrypt-failed", A_END);
+           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, 0);
+           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", "?PEER", p, "decrypt-failed", A_END);
+           return;
+         }
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           p_ponged(p, MISC_EPONG, &bb);
+         }
+         break;
+      }
       break;
     default:
-      a_warn("bad packet from `%s': unknown packet type", p->name);
+      p->st.n_reject++;
+      a_warn("PEER",
+            "?PEER", p,
+            "bad-packet",
+            "unknown-category" "0x%02x", ch,
+            A_END);
       break;
   }
 }
@@ -155,17 +310,171 @@ 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("packet build failed");
-    return;
+    a_warn("PEER", "?PEER", p, "packet-build-failed", A_END);
+    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("packet send to `%s' failed: %s", p->name, strerror(errno));
+            0, &p->spec.sa.sa, p->spec.sasz) < 0) {
+    a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END);
+    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->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);
+  T( trace(T_PEER, "peer: ping 0x%08lx done (rc = %d)",
+          (unsigned long)p->id, rc); )
+  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;
+
+  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, 0);
+      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;
+  if (p->pings) p->pings->prev = pg;
+  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_greet@ --- *
+ *
+ * Arguments:  @peer *p@ = peer to send to
+ *             @const void *c@ = pointer to challenge
+ *             @size_t sz@ = size of challenge
+ *
+ * Returns:    ---
+ *
+ * Use:                Sends a greeting packet.
+ */
+
+void p_greet(peer *p, const void *c, size_t sz)
+{
+  buf *b = p_txstart(p, MSG_MISC | MISC_GREET);
+  buf_put(b, c, sz);
+  p_txend(p);
 }
 
 /* --- @p_tun@ --- *
@@ -181,10 +490,34 @@ void p_txend(peer *p)
 void p_tun(peer *p, buf *b)
 {
   buf *bb = p_txstart(p, MSG_PACKET);
-  if (ks_encrypt(&p->ks, b, bb))
-    kx_start(&p->kx);
-  if (BCUR(bb) > BBASE(bb))
+
+  TIMER;
+  if (ksl_encrypt(&p->ks, MSG_PACKET, b, bb))
+    kx_start(&p->kx, 0);
+  if (BOK(bb) && BLEN(bb)) {
+    p->st.n_ipout++;
+    p->st.sz_ipout += BLEN(bb);
     p_txend(p);
+  }
+}
+
+/* --- @p_keyreload@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Forces a check of the daemon's keyring files.
+ */
+
+void p_keyreload(void)
+{
+  peer *p;
+
+  if (km_reload()) {
+    for (p = peers; p; p = p->next)
+      kx_newkeys(&p->kx);
+  }
 }
 
 /* --- @p_interval@ --- *
@@ -198,18 +531,22 @@ void p_tun(peer *p, buf *b)
 
 void p_interval(void)
 {
-  peer *p, *pp;
-  int reload;
+  peer *p;
 
-  reload = km_interval();
-  for (p = peers; p; p = pp) {
-    pp = p->next;
-    if (reload)
-      kx_newkeys(&p->kx);
-    ks_prune(&p->ks);
-  }
+  p_keyreload();
+  for (p = peers; p; p = p->next)
+    ksl_prune(&p->ks);
 }
 
+/* --- @p_stats@ --- *
+ *
+ * Arguments:  @peer *p@ = pointer to a peer block
+ *
+ * Returns:    A pointer to the peer's statistics.
+ */
+
+stats *p_stats(peer *p) { return (&p->st); }
+
 /* --- @p_ifname@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer block
@@ -217,7 +554,7 @@ void p_interval(void)
  * Returns:    A pointer to the peer's interface name.
  */
 
-const char *p_ifname(peer *p) { return (tun_ifname(&p->t)); }
+const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); }
 
 /* --- @p_addr@ --- *
  *
@@ -226,30 +563,46 @@ const char *p_ifname(peer *p) { return (tun_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@ --- *
  *
- * Arguments:  @unsigned port@ = port number to listen to
+ * Arguments:  @struct in_addr addr@ = address to bind to
+ *             @unsigned port@ = port number to listen to
  *
  * Returns:    ---
  *
  * Use:                Initializes the peer system; creates the socket.
  */
 
-void p_init(unsigned port)
+void p_init(struct in_addr addr, unsigned port)
 {
   int fd;
   struct sockaddr_in sin;
+  int len = PKBUFSZ;
+
+  /* --- Note on socket buffer sizes --- *
+   *
+   * For some bizarre reason, Linux 2.2 (at least) doubles the socket buffer
+   * sizes I pass to @setsockopt@.  I'm not putting special-case code here
+   * for Linux: BSD (at least TCPv2) does what I tell it rather than second-
+   * guessing me.
+   */
 
   if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
     die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
   BURN(sin);
   sin.sin_family = AF_INET;
-  sin.sin_addr.s_addr = INADDR_ANY;
+  sin.sin_addr = addr;
   sin.sin_port = htons(port);
   if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
     die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
+  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
+      setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
+    die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
+       strerror(errno));
+  }
+  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
   sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0);
   sel_addfile(&sock);
   T( trace(T_PEER, "peer: created socket"); )
@@ -273,11 +626,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
- *             @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.
  *
@@ -285,29 +674,42 @@ unsigned p_port(void)
  *             by this point.
  */
 
-peer *p_create(const char *name, 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;
-  if (kx_init(&p->kx, p, &p->ks))
+  p->pings = 0;
+  memset(&p->st, 0, sizeof(stats));
+  p->st.t_start = time(0);
+  if ((p->t = spec->tops->create(p)) == 0)
     goto tidy_0;
-  if (tun_create(&p->t, p))
+  p_setkatimer(p);
+  if (kx_init(&p->kx, p, &p->ks))
     goto tidy_1;
   p->next = peers;
   if (peers)
     peers->prev = p;
   peers = p;
+  a_notify("ADD",
+           "?PEER", p,
+           "%s", p->t->ops->ifname(p->t),
+           "?ADDR", &p->spec.sa,
+           A_END);
+  a_notify("KXSTART", "?PEER", p, A_END);
+    /* 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);
 }
@@ -319,7 +721,16 @@ 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_spec@ --- *
+ *
+ * Arguments:  @peer *p@ = pointer to a peer block
+ *
+ * Returns:    Pointer to the peer's specification
+ */
+
+const peerspec *p_spec(peer *p) { return (&p->spec); }
 
 /* --- @p_find@ --- *
  *
@@ -334,7 +745,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);  
@@ -351,11 +762,20 @@ peer *p_find(const char *name)
 
 void p_destroy(peer *p)
 {
-  T( trace(T_PEER, "peer: destroying peer `%s'", p->name); )
-  ks_free(&p->ks);
+  ping *pg, *ppg;
+
+  T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+  a_notify("KILL", "%s", p->spec.name, A_END);
+  ksl_free(&p->ks);
   kx_free(&p->kx);
-  tun_destroy(&p->t);
-  xfree(p->name);
+  p->t->ops->destroy(p->t);
+  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)