server/peer.c, etc.: Introduce who-goes-there protocol. mdw/wgt
authorMark Wooding <mdw@distorted.org.uk>
Mon, 20 Sep 2021 20:13:27 +0000 (21:13 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 30 Apr 2022 00:51:38 +0000 (01:51 +0100)
If a laptop (say) has been asleep for a short while, then its peer has
likely forgotten about it, but it will wait for a ping-timeout cycle
before attempting to reconnect.

Introduce a new protocol to speed this up.

  * When a packet which contains a ciphertext (e.g., `MSG_PACKET',
    `MISC_EPING') is received from an unknown sender, then send back a
    `MISC_WGT' (`who-goes-there') message quoting a prefix of the
    offending packet.  These are rate-limited so that TrIPE can't be
    used as an amplifier.  (Under adverse circumstances, we fall back to
    the old timeout mechanisms.)

  * When a `MISC_WGT' packet is received, we check to see whether the
    peer is configured with a `knock' string, and the quoted prefix
    matches a message we sent to that peer recently.  If so, we try to
    restart the key-exchange protocol.  Hence, an adversary who can't
    read the wire has very little chance of proviking a pointless
    key-exchange.  The 20 s cooling-off period still applies, so even an
    adversary who /can/ read the wire can't do too much harm.

common/protocol.h
server/peer.c
server/tests.at
server/tripe-admin.5.in
server/tripe.h
svc/connect.in
wireshark/tripe.lua

index e40cf17..be76191 100644 (file)
@@ -80,6 +80,9 @@
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
 #define MISC_BYE 6u                    /* Departure notification */
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
 #define MISC_BYE 6u                    /* Departure notification */
+#define MISC_WGT 7u                    /* You sent message: who are you? */
+
+#define WGTLEN 17u
 
 /* --- Symmetric encryption and keysets --- *
  *
 
 /* --- Symmetric encryption and keysets --- *
  *
index 0b5a6c2..b20f2fd 100644 (file)
@@ -40,6 +40,7 @@ static struct tunnel_node {
   struct tunnel_node *next;
   const tunnel_ops *tops;
 } *tunnels, **tunnels_tail = &tunnels;
   struct tunnel_node *next;
   const tunnel_ops *tops;
 } *tunnels, **tunnels_tail = &tunnels;
+static ratelim wgt_limit;
 const tunnel_ops *dflttun;
 
 /*----- Main code ---------------------------------------------------------*/
 const tunnel_ops *dflttun;
 
 /*----- Main code ---------------------------------------------------------*/
@@ -229,6 +230,7 @@ static int p_decrypt(peer **pp, addr *a, size_t n,
 {
   peer *p, *q;
   int err = KSERR_DECRYPT;
 {
   peer *p, *q;
   int err = KSERR_DECRYPT;
+  buf b;
 
   /* --- If we have a match on the source address then try that first --- */
 
 
   /* --- If we have a match on the source address then try that first --- */
 
@@ -276,9 +278,17 @@ static int p_decrypt(peer **pp, addr *a, size_t n,
 
 searched:
   if (!p) {
 
 searched:
   if (!p) {
-    if (!q)
+    if (!q) {
       a_warn("PEER", "-", "unexpected-source", "?ADDR", a, A_END);
       a_warn("PEER", "-", "unexpected-source", "?ADDR", a, A_END);
-    else {
+      if (!ratelim_withdraw(&wgt_limit, 1)) {
+       buf_init(&b, buf_t, sizeof(buf_t));
+       buf_putbyte(&b, MSG_MISC | MISC_WGT);
+       if (n > WGTLEN) n = WGTLEN;
+       buf_put(&b, buf_i, n);
+       T( trace(T_PEER, "peer: sending who-goes-there message"); )
+       assert(BOK(&b)); p_txaddr(a, BBASE(&b), BLEN(&b));
+      }
+    } else {
       a_warn("PEER", "?PEER", p, "decrypt-failed",
             "error-code", "%d", err, A_END);
       p_rxupdstats(q, n);
       a_warn("PEER", "?PEER", p, "decrypt-failed",
             "error-code", "%d", err, A_END);
       p_rxupdstats(q, n);
@@ -325,6 +335,9 @@ static void p_read(int fd, unsigned mode, void *v)
   ssize_t n;
   int ch;
   buf b, bb;
   ssize_t n;
   int ch;
   buf b, bb;
+  const octet *q;
+  unsigned i;
+  time_t now;
 #ifndef NTRACE
   int ix = -1;
   char name[NI_MAXHOST], svc[NI_MAXSERV];
 #ifndef NTRACE
   int ix = -1;
   char name[NI_MAXHOST], svc[NI_MAXSERV];
@@ -445,7 +458,7 @@ static void p_read(int fd, unsigned mode, void *v)
            buf_flip(&bb);
            p_encrypt(p, MSG_MISC | MISC_EPONG, &bb,
                      p_txstart(p, MSG_MISC | MISC_EPONG));
            buf_flip(&bb);
            p_encrypt(p, MSG_MISC | MISC_EPONG, &bb,
                      p_txstart(p, MSG_MISC | MISC_EPONG));
-           p_txend(p, 0);
+           p_txend(p, TXF_WGT);
          }
          break;
        case MISC_EPONG:
          }
          break;
        case MISC_EPONG:
@@ -467,6 +480,31 @@ static void p_read(int fd, unsigned mode, void *v)
            p_destroy(p, 0);
          }
          break;
            p_destroy(p, 0);
          }
          break;
+       case MISC_WGT:
+         if (!p) goto unexp;
+         if (!(p->spec.f&PSF_EPHEM))
+           { a_warn("PEER", "?PEER", p, "unexpected-wgt", A_END); break; }
+         n = BLEFT(&b); if (n > WGTLEN) n = WGTLEN;
+         now = time(0);
+         q = buf_get(&b, n); assert(q);
+         for (i = 0; i < NWGT; i++) {
+           if (p->wgt[i].when != (time_t)-1 &&
+               now - p->wgt[i].when <= T_WGT &&
+               p->wgt[i].sz == n &&
+               MEMCMP(p->wgt[i].msg, ==, q, n))
+             goto found_wgt;
+         }
+         a_warn("PEER", "?PEER", p, "unrecognized-wgt", A_END);
+         break;
+       found_wgt:
+         if (p->spec.knock) {
+           T( trace(T_PEER, "peer: who-goes-there from peer: knocking"); )
+           kx_start(&p->kx, 0);
+         } else {
+           T( trace(T_PEER, "peer: who-goes-there from peer: notifying"); )
+           a_notify("WGT", "?PEER", p, A_END);
+         }
+         break;
       }
       break;
     default:
       }
       break;
     default:
@@ -565,7 +603,16 @@ static int p_dotxend(peer *p)
 
 void p_txend(peer *p, unsigned f)
 {
 
 void p_txend(peer *p, unsigned f)
 {
+  size_t n;
+
   if (p_dotxend(p)) {
   if (p_dotxend(p)) {
+    if ((f&TXF_WGT) && (p->spec.f&PSF_EPHEM)) {
+      n = BLEN(&p->b); if (n > WGTLEN) n = WGTLEN;
+      memcpy(p->wgt[p->wgtix].msg, BBASE(&p->b), n);
+      p->wgt[p->wgtix].sz = n;
+      p->wgt[p->wgtix].when = time(0);
+      p->wgtix++; if (p->wgtix >= NWGT) p->wgtix = 0;
+    }
     if (p->spec.t_ka) {
       sel_rmtimer(&p->tka);
       p_setkatimer(p);
     if (p->spec.t_ka) {
       sel_rmtimer(&p->tka);
       p_setkatimer(p);
@@ -670,7 +717,7 @@ int p_pingsend(peer *p, ping *pg, unsigned type,
       p_encrypt(p, MSG_MISC | MISC_EPING, &bb, b);
       if (!BOK(b))
        return (-1);
       p_encrypt(p, MSG_MISC | MISC_EPING, &bb, b);
       if (!BOK(b))
        return (-1);
-      p_txend(p, 0);
+      p_txend(p, TXF_WGT);
       break;
     default:
       abort();
       break;
     default:
       abort();
@@ -729,7 +776,7 @@ void p_tun(peer *p, buf *b)
   if (BOK(bb) && BLEN(bb)) {
     p->st.n_ipout++;
     p->st.sz_ipout += BLEN(bb);
   if (BOK(bb) && BLEN(bb)) {
     p->st.n_ipout++;
     p->st.sz_ipout += BLEN(bb);
-    p_txend(p, 0);
+    p_txend(p, TXF_WGT);
   }
 }
 
   }
 }
 
@@ -932,6 +979,7 @@ void p_init(void)
 {
   sym_create(&byname);
   am_create(&byaddr);
 {
   sym_create(&byname);
   am_create(&byaddr);
+  ratelim_init(&wgt_limit, 5, 100);
 }
 
 /* --- @p_addtun@ --- *
 }
 
 /* --- @p_addtun@ --- *
@@ -1084,7 +1132,7 @@ peer *p_create(peerspec *spec)
   peer *p = CREATE(peer);
   const tunnel_ops *tops = spec->tops;
   int fd;
   peer *p = CREATE(peer);
   const tunnel_ops *tops = spec->tops;
   int fd;
-  unsigned f;
+  unsigned f, i;
 
   p->byname = sym_find(&byname, spec->name, -1, sizeof(peer_byname), &f);
   if (f) goto tidy_0;
 
   p->byname = sym_find(&byname, spec->name, -1, sizeof(peer_byname), &f);
   if (f) goto tidy_0;
@@ -1126,6 +1174,11 @@ peer *p_create(peerspec *spec)
     /* Couldn't tell anyone before */
   }
   if (p->spec.f & PSF_MOBILE) nmobile++;
     /* Couldn't tell anyone before */
   }
   if (p->spec.f & PSF_MOBILE) nmobile++;
+  for (i = 0; i < NWGT; i++) {
+    p->wgt[i].sz = 0;
+    p->wgt[i].when = (time_t)-1;
+  }
+  p->wgtix = 0;
   return (p);
 
 tidy_4:
   return (p);
 
 tidy_4:
index 582503a..fc9c537 100644 (file)
@@ -813,6 +813,18 @@ m4_define([WAIT_KNOCK], [
 
     COMMS_EPING([alice], [alice], [bob], [bob])
     COMMS_SLIP([alice], [alice], [bob], [bob])
 
     COMMS_EPING([alice], [alice], [bob], [bob])
     COMMS_SLIP([alice], [alice], [bob], [bob])
+
+    AT_CHECK([TRIPECTL -dalice KILL -quiet bob])
+    AT_CHECK([TRIPECTL -dbob FORCEKX -quiet alice])
+    echo "WARN PEER - unexpected-source INET 127.0.0.1 $bob_from_alice" >>alice/expected-server-output
+    WAIT_KNOCK([alice], [bob], [
+      AT_CHECK([TRIPECTL -dbob EPING alice],, [ping-timeout[]nl])
+    ])
+    AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+      AT_CHECK([TRIPECTL -dalice ADD -ephemeral bob INET 127.0.0.1 $bob_from_alice])
+    ])
+    COMMS_EPING([alice], [alice], [bob], [bob])
+    COMMS_SLIP([alice], [alice], [bob], [bob])
   ])
 
   WITH_MITM([alice], [=new_bob_from_alice], [bob], [$alice_from_bob], [
   ])
 
   WITH_MITM([alice], [=new_bob_from_alice], [bob], [$alice_from_bob], [
index 3e7bd8e..ea2183c 100644 (file)
@@ -1365,6 +1365,16 @@ is no longer available.
 An administration client issued a notification using the
 .B NOTIFY
 command.
 An administration client issued a notification using the
 .B NOTIFY
 command.
+.SP
+.BI "WGT " peer
+A who-goes-there message was received from an ephemeral
+.IR peer ,
+implying that it has forgotten about us.  If a service knows how to
+inform the peer of our existence, it should do so.  This notification is
+not sent for peers which have a
+.B knock
+string configured, because the server automatically tries knocking again
+in this case.
 .
 .\"--------------------------------------------------------------------------
 .SH "WARNINGS"
 .
 .\"--------------------------------------------------------------------------
 .SH "WARNINGS"
@@ -1837,6 +1847,17 @@ 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.
 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.
+.SP
+.BI "PEER " peer " unexpected-wgt"
+A `who-goes-there' message from received from
+.IR peer ,
+but the peer isn't ephemeral.
+.SP
+.BI "PEER " peer " unrecognized-wgt"
+A `who-goes-there' message from received from
+.IR peer ,
+but it doesn't quote the start of a message which we recently sent to
+it.
 .SS "PRIVSEP warnings"
 These indicate problems with the privilege-separation helper process.
 (The server tries to drop its privileges when it starts up, leaving a
 .SS "PRIVSEP warnings"
 These indicate problems with the privilege-separation helper process.
 (The server tries to drop its privileges when it starts up, leaving a
index a304e3e..6c5c827 100644 (file)
 
 #define T_WOBBLE (1.0/3.0)             /* Relative timer randomness */
 
 
 #define T_WOBBLE (1.0/3.0)             /* Relative timer randomness */
 
+#define T_WGT 5                                /* Age for who-goes-there */
+
 /* --- Other things --- */
 
 #define PKBUFSZ 65536
 /* --- Other things --- */
 
 #define PKBUFSZ 65536
@@ -678,6 +680,13 @@ typedef struct peer {
   stats st;                            /* Statistics */
   keyexch kx;                          /* Key exchange protocol block */
   sel_timer tka;                       /* Timer for keepalives */
   stats st;                            /* Statistics */
   keyexch kx;                          /* Key exchange protocol block */
   sel_timer tka;                       /* Timer for keepalives */
+#define NWGT 16
+  struct {
+    octet msg[WGTLEN];                 /* Message prefix */
+    unsigned sz;                       /* Length of prefix */
+    time_t when;                       /* Time it was transmitted */
+  } wgt[NWGT];                         /* Recently sent messages */
+  unsigned wgtix;                      /* Next index to transmit */
 } peer;
 
 typedef struct peer_iter { sym_iter i; } peer_iter;
 } peer;
 
 typedef struct peer_iter { sym_iter i; } peer_iter;
@@ -1587,6 +1596,7 @@ extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
  * Use:                Sends a packet to the peer.
  */
 
  * Use:                Sends a packet to the peer.
  */
 
+#define TXF_WGT 1u                     /* Include in who-goes-there table */
 extern void p_txend(peer */*p*/, unsigned /*f*/);
 
 /* --- @p_pingsend@ --- *
 extern void p_txend(peer */*p*/, unsigned /*f*/);
 
 /* --- @p_pingsend@ --- *
index fa5a22c..9bb582c 100644 (file)
@@ -778,6 +778,10 @@ def notify(_, code, *rest):
     try: p = Peer(rest[0])
     except KeyError: pass
     else: disownpeer(p, *rest[1:])
     try: p = Peer(rest[0])
     except KeyError: pass
     else: disownpeer(p, *rest[1:])
+  elif code == 'WGT':
+    try: p = pinger.find(rest[0])
+    except KeyError: pass
+    else: p.reconnect()
   elif code == 'GREET':
     chal = rest[0]
     try: cr = chalmap[chal]
   elif code == 'GREET':
     chal = rest[0]
     try: cr = chalmap[chal]
index a3544e9..031720c 100644 (file)
@@ -414,7 +414,9 @@ local PKTINFO = {
       [5] = { label = "MISC_GREET", info = "greeting",
              dissect = { dissect_misc_payload } },
       [6] = { label = "MISC_BYE", info = "disconnect notification",
       [5] = { label = "MISC_GREET", info = "greeting",
              dissect = { dissect_misc_payload } },
       [6] = { label = "MISC_BYE", info = "disconnect notification",
-             dissect = { dissect_misc_ciphertext } },
+             dissect = { dissect_misc_ciphertext },
+      [7] = { label = "MISC_WGT", info = "unexpected-sender notification",
+             dissect = { dissect_misc_payload } },
     }
   }
 }
     }
   }
 }