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_WGT 7u                    /* You sent message: who are you? */
+
+#define WGTLEN 17u
 
 /* --- 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;
+static ratelim wgt_limit;
 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;
+  buf b;
 
   /* --- 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) {
-    if (!q)
+    if (!q) {
       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);
@@ -325,6 +335,9 @@ static void p_read(int fd, unsigned mode, void *v)
   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];
@@ -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));
-           p_txend(p, 0);
+           p_txend(p, TXF_WGT);
          }
          break;
        case MISC_EPONG:
@@ -467,6 +480,31 @@ static void p_read(int fd, unsigned mode, void *v)
            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:
@@ -565,7 +603,16 @@ static int p_dotxend(peer *p)
 
 void p_txend(peer *p, unsigned f)
 {
+  size_t n;
+
   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);
@@ -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_txend(p, 0);
+      p_txend(p, TXF_WGT);
       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);
-    p_txend(p, 0);
+    p_txend(p, TXF_WGT);
   }
 }
 
@@ -932,6 +979,7 @@ void p_init(void)
 {
   sym_create(&byname);
   am_create(&byaddr);
+  ratelim_init(&wgt_limit, 5, 100);
 }
 
 /* --- @p_addtun@ --- *
@@ -1084,7 +1132,7 @@ peer *p_create(peerspec *spec)
   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;
@@ -1126,6 +1174,11 @@ peer *p_create(peerspec *spec)
     /* 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:
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])
+
+    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], [
index 3e7bd8e..ea2183c 100644 (file)
@@ -1365,6 +1365,16 @@ is no longer available.
 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"
@@ -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.
+.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
index a304e3e..6c5c827 100644 (file)
 
 #define T_WOBBLE (1.0/3.0)             /* Relative timer randomness */
 
+#define T_WGT 5                                /* Age for who-goes-there */
+
 /* --- 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 */
+#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;
@@ -1587,6 +1596,7 @@ extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
  * 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@ --- *
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:])
+  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]
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",
-             dissect = { dissect_misc_ciphertext } },
+             dissect = { dissect_misc_ciphertext },
+      [7] = { label = "MISC_WGT", info = "unexpected-sender notification",
+             dissect = { dissect_misc_payload } },
     }
   }
 }