Add notion of `ephemeral' associations and a goodbye protocol.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 5 Sep 2017 21:26:51 +0000 (22:26 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 25 Jan 2019 12:07:16 +0000 (12:07 +0000)
When TrIPE kills an ephemeral peer, it sends a `bye' message to the
peer.  When TrIPE receives `bye' from an ephemeral peer, it kills the
peer (without sending `bye' back).

Augment the `connect' service to set appropriate flags when adding
peers, and the Wireshark dissector to understand the new message.

13 files changed:
common/protocol.h
mon/tripemon.in
peerdb/peers.in
peerdb/peers.in.5.in
py/tripe.py.in
server/admin.c
server/peer.c
server/tests.at
server/tripe-admin.5.in
server/tripe.h
svc/connect.8.in
svc/connect.in
wireshark/tripe.lua

index 2416afc..e40cf17 100644 (file)
@@ -79,6 +79,7 @@
 #define MISC_EPING 3u                  /* Encrypted ping */
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
 #define MISC_EPING 3u                  /* Encrypted ping */
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
+#define MISC_BYE 6u                    /* Departure notification */
 
 /* --- Symmetric encryption and keysets --- *
  *
 
 /* --- Symmetric encryption and keysets --- *
  *
index 0d15474..8666575 100644 (file)
@@ -1093,6 +1093,9 @@ class AddPeerDialog (MyDialog):
     me.c_mobile = G.CheckButton('Mobile')
     table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
 
     me.c_mobile = G.CheckButton('Mobile')
     table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
 
+    me.c_ephem = G.CheckButton('Ephemeral')
+    table.pack(me.c_ephem, newlinep = True, width = 4, xopt = G.FILL)
+
     me.c_peerkey, me.e_peerkey = \
       optional_entry('Peer key tag', r'^[^.:\s]+$', 16)
     me.c_privkey, me.e_privkey = \
     me.c_peerkey, me.e_peerkey = \
       optional_entry('Peer key tag', r'^[^.:\s]+$', 16)
     me.c_privkey, me.e_privkey = \
@@ -1115,6 +1118,7 @@ class AddPeerDialog (MyDialog):
                   tunnel = t and me.tuns[t] or None,
                   cork = me.c_cork.get_active() or None,
                   mobile = me.c_mobile.get_active() or None,
                   tunnel = t and me.tuns[t] or None,
                   cork = me.c_cork.get_active() or None,
                   mobile = me.c_mobile.get_active() or None,
+                  ephemeral = me.c_ephem.get_active() or None,
                   key = (me.c_peerkey.get_active() and
                          me.e_peerkey.get_text() or None),
                   priv = (me.c_privkey.get_active() and
                   key = (me.c_peerkey.get_active() and
                          me.e_peerkey.get_text() or None),
                   priv = (me.c_privkey.get_active() and
index 3fb2380..75c039d 100644 (file)
@@ -57,6 +57,10 @@ host = override-me
 ;; the remote peer.
 peer = INET $[$(host)] $(port)
 
 ;; the remote peer.
 peer = INET $[$(host)] $(port)
 
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = nil
+
 ;;;--------------------------------------------------------------------------
 ;;; Temporary association defaults.
 ;;;
 ;;;--------------------------------------------------------------------------
 ;;; Temporary association defaults.
 ;;;
@@ -81,21 +85,28 @@ retries = 5
 ;;; The parameters here affect peers to whom dynamic connections are made.
 ;;; The user and connect parameters probably need customizing.
 
 ;;; The parameters here affect peers to whom dynamic connections are made.
 ;;; The user and connect parameters probably need customizing.
 
-[@KNOCK]
+[@EPHEMERAL]
 @inherit = @ACTIVE, @WATCH
 
 @inherit = @ACTIVE, @WATCH
 
-;; keepalive: how often to send NOP packets to keep the connection alive, at
-;; least in the minds of intermediate stateful firewalls and NAT routers.
-keepalive = 2m
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = t
 
 ;; every: interval for checking that this connection is alive.
 every = 30s
 
 
 ;; every: interval for checking that this connection is alive.
 every = 30s
 
+[@KNOCK]
+@inherit = @EPHEMERAL
+
+;; keepalive: how often to send NOP packets to keep the connection alive, at
+;; least in the minds of intermediate stateful firewalls and NAT routers.
+keepalive = 2m
+
 ;; knock: peer-name string to send to the peer.
 knock = $(myhost)
 
 [@DYNAMIC]
 ;; knock: peer-name string to send to the peer.
 knock = $(myhost)
 
 [@DYNAMIC]
-@inherit = @ACTIVE, @WATCH
+@inherit = @EPHEMERAL
 
 ;; cork: whether to wait for a key-exchange packet from the peer before
 ;; sending one of our own.
 
 ;; cork: whether to wait for a key-exchange packet from the peer before
 ;; sending one of our own.
@@ -115,9 +126,6 @@ disconnect = ssh -q $(ssh-user)@$[$(host)] goodbye
 ;; least in the minds of intermediate stateful firewalls and NAT routers.
 keepalive = 2m
 
 ;; least in the minds of intermediate stateful firewalls and NAT routers.
 keepalive = 2m
 
-;; every: interval for checking that this connection is alive.
-every = 30s
-
 ;;;--------------------------------------------------------------------------
 ;;; Passive-peers defaults.
 ;;;
 ;;;--------------------------------------------------------------------------
 ;;; Passive-peers defaults.
 ;;;
index 89eb777..ac27b49 100644 (file)
@@ -175,6 +175,12 @@ Don't initiate immediate key exchange.  Used by
 Shell command for closing down connection to this peer.  Used by
 .BR connect (8).
 .TP
 Shell command for closing down connection to this peer.  Used by
 .BR connect (8).
 .TP
+.B ephemeral
+Mark the peer as ephemeral: see
+.BR tripe-admin (5)
+for what this means.  Used by
+.BR connect (8).
+.TP
 .B every
 Interval for checking that the peer is still alive and well.  Used by
 .BR connect (8).
 .B every
 Interval for checking that the peer is still alive and well.  Used by
 .BR connect (8).
index 63ae0af..0126dc5 100644 (file)
@@ -838,7 +838,8 @@ class TripeCommandDispatcher (TripeConnection):
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
-                                           'mobile', 'knock']) +
+                                           'mobile', 'knock',
+                                           'ephemeral']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
                               [peer] +
                               list(addr)))
   def addr(me, peer):
index 886c672..54883af 100644 (file)
@@ -551,7 +551,7 @@ void a_quit(void)
 {
   close(sock.fd);
   unlink(sockname);
 {
   close(sock.fd);
   unlink(sockname);
-  FOREACH_PEER(p, { p_destroy(p); });
+  FOREACH_PEER(p, { p_destroy(p, 1); });
   ps_quit();
   exit(0);
 }
   ps_quit();
   exit(0);
 }
@@ -1285,11 +1285,12 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     })
     OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
     OPT("-cork", { add->peer.f |= KXF_CORK; })
     })
     OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
     OPT("-cork", { add->peer.f |= KXF_CORK; })
+    OPT("-ephemeral", { add->peer.f |= PSF_EPHEM; })
     OPTARG("-key", arg, {
       if (add->peer.tag) xfree(add->peer.tag);
       add->peer.tag = xstrdup(arg);
     })
     OPTARG("-key", arg, {
       if (add->peer.tag) xfree(add->peer.tag);
       add->peer.tag = xstrdup(arg);
     })
-    OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
+    OPT("-mobile", { add->peer.f |= PSF_MOBILE | PSF_EPHEM; })
     OPTARG("-priv", arg, {
       if (add->peer.privtag) xfree(add->peer.privtag);
       add->peer.privtag = xstrdup(arg);
     OPTARG("-priv", arg, {
       if (add->peer.privtag) xfree(add->peer.privtag);
       add->peer.privtag = xstrdup(arg);
@@ -1297,6 +1298,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     OPTARG("-knock", arg, {
       if (add->peer.knock) xfree(add->peer.knock);
       add->peer.knock = xstrdup(arg);
     OPTARG("-knock", arg, {
       if (add->peer.knock) xfree(add->peer.knock);
       add->peer.knock = xstrdup(arg);
+      add->peer.f |= PSF_EPHEM;
     })
   });
 
     })
   });
 
@@ -1860,6 +1862,7 @@ static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
     a_info(a, "keepalive=%lu", ps->t_ka, A_END);
     a_info(a, "corked=%s", BOOL(p->kx.f&KXF_CORK),
           "mobile=%s", BOOL(ps->f&PSF_MOBILE),
     a_info(a, "keepalive=%lu", ps->t_ka, A_END);
     a_info(a, "corked=%s", BOOL(p->kx.f&KXF_CORK),
           "mobile=%s", BOOL(ps->f&PSF_MOBILE),
+          "ephemeral=%s", BOOL(ps->f&PSF_EPHEM),
           A_END);
     a_ok(a);
   }
           A_END);
     a_ok(a);
   }
@@ -1915,7 +1918,7 @@ static void acmd_kill(admin *a, unsigned ac, char *av[])
   peer *p;
 
   if ((p = a_findpeer(a, av[0])) != 0) {
   peer *p;
 
   if ((p = a_findpeer(a, av[0])) != 0) {
-    p_destroy(p);
+    p_destroy(p, 1);
     a_ok(a);
   }
 }
     a_ok(a);
   }
 }
index f600f99..606b203 100644 (file)
@@ -454,6 +454,16 @@ static void p_read(int fd, unsigned mode, void *v)
            p_ponged(p, MISC_EPONG, &bb);
          }
          break;
            p_ponged(p, MISC_EPONG, &bb);
          }
          break;
+       case MISC_BYE:
+         buf_init(&bb, buf_t, sizeof(buf_t));
+         if (p_decrypt(&p, &a, n, ch, &b, &bb)) return;
+         if (!(p->spec.f&PSF_EPHEM)) return;
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           if (BSZ(&bb)) return;
+           p_destroy(p, 0);
+         }
+         break;
       }
       break;
     default:
       }
       break;
     default:
@@ -1044,17 +1054,28 @@ peer *p_find(const char *name)
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-void p_destroy(peer *p)
+void p_destroy(peer *p, int bye)
 {
   ping *pg, *ppg;
 {
   ping *pg, *ppg;
+  buf *b, bb;
 
   T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
 
   T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+
+  if (bye && (p->spec.f&PSF_EPHEM)) {
+    b = p_txstart(p, MSG_MISC | MISC_BYE);
+    buf_init(&bb, buf_t, sizeof(buf_t));
+    assert(BOK(&bb)); buf_flip(&bb);
+    p_encrypt(p, MSG_MISC | MISC_BYE, &bb, b);
+    p_txend(p);
+  }
+
   a_notify("KILL", "%s", p->spec.name, A_END);
   ksl_free(&p->ks);
   kx_free(&p->kx);
   a_notify("KILL", "%s", p->spec.name, A_END);
   ksl_free(&p->ks);
   kx_free(&p->kx);
index 8bceb14..08bfb13 100644 (file)
@@ -752,7 +752,7 @@ WITH_TRIPE(, [
 AT_CLEANUP
 
 ###--------------------------------------------------------------------------
 AT_CLEANUP
 
 ###--------------------------------------------------------------------------
-### Knock.
+### Knock and bye.
 
 AT_SETUP([server knock])
 AT_KEYWORDS([knock])
 
 AT_SETUP([server knock])
 AT_KEYWORDS([knock])
@@ -786,7 +786,7 @@ WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
     AT_CHECK([cat knock-addr],, [INET 127.0.0.1 5311[]nl])
 
     AWAIT_KXDONE([alice], [alice], [bob], [bob], [
     AT_CHECK([cat knock-addr],, [INET 127.0.0.1 5311[]nl])
 
     AWAIT_KXDONE([alice], [alice], [bob], [bob], [
-      AT_CHECK([TRIPECTL -dalice ADD bob INET 127.0.0.1 5311])
+      AT_CHECK([TRIPECTL -dalice ADD -ephemeral bob INET 127.0.0.1 5311])
     ])
 
     COMMS_EPING([alice], [alice], [bob], [bob])
     ])
 
     COMMS_EPING([alice], [alice], [bob], [bob])
@@ -798,6 +798,7 @@ WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
       AT_CHECK([TRIPECTL -dalice FORCEKX bob])
       AT_CHECK([TRIPECTL -dbob FORCEKX alice])
     ])
       AT_CHECK([TRIPECTL -dalice FORCEKX bob])
       AT_CHECK([TRIPECTL -dbob FORCEKX alice])
     ])
+
     AT_CHECK([TRIPECTL -dbob KILL alice])
     AT_CHECK([TRIPECTL -dalice LIST],, [])
   ])
     AT_CHECK([TRIPECTL -dbob KILL alice])
     AT_CHECK([TRIPECTL -dalice LIST],, [])
   ])
index a340f57..c81dc11 100644 (file)
@@ -332,6 +332,21 @@ Run the command in the background, using the given
 Don't send an immediate challenge to the peer; instead, wait until it
 sends us something before responding.
 .TP
 Don't send an immediate challenge to the peer; instead, wait until it
 sends us something before responding.
 .TP
+.B "\-ephemeral"
+The association with the peer is not intended to persist indefinitely.
+If a peer marked as ephemeral is killed, or the
+.BR tripe (8)
+daemon is shut down, send a
+.B bye
+packet to the peer so that it forgets about us; if a peer marked as
+ephemeral sends us a
+.B bye
+packet then it is killed (but in this case no further
+.B bye
+packet is sent).  Peers not marked as ephemeral exhibit neither of these
+behaviours; each peer must have the other marked as ephemeral for the
+association to be fully torn down if either end kills the other.
+.TP
 .BI "\-keepalive " time
 Send a no-op packet if we've not sent a packet to the peer in the last
 .I time
 .BI "\-keepalive " time
 Send a no-op packet if we've not sent a packet to the peer in the last
 .I time
@@ -370,7 +385,8 @@ emits a
 .B KNOCK
 notification stating the peer's (claimed) name and address.  The server
 will already have verified that the sender is using the peer's private
 .B KNOCK
 notification stating the peer's (claimed) name and address.  The server
 will already have verified that the sender is using the peer's private
-key by this point.
+key by this point.  This option implies
+.BR \-ephemeral .
 .TP
 .B "\-mobile"
 The peer is a mobile device, and is likely to change address rapidly.
 .TP
 .B "\-mobile"
 The peer is a mobile device, and is likely to change address rapidly.
@@ -380,7 +396,8 @@ peers, however, it will attempt to decrypt the packet using their keys,
 and if one succeeds, the server will update its idea of the peer's
 address and emit an
 .B NEWADDR
 and if one succeeds, the server will update its idea of the peer's
 address and emit an
 .B NEWADDR
-notification.
+notification.  This option implies
+.BR \-ephemeral .
 .TP
 .BI "\-priv " tag
 Use the private key
 .TP
 .BI "\-priv " tag
 Use the private key
@@ -639,6 +656,14 @@ or
 .B nil
 depending on whether or not (respectively) the peer is expected to
 change its address unpredictably.
 .B nil
 depending on whether or not (respectively) the peer is expected to
 change its address unpredictably.
+.TP
+.B ephemeral
+Either
+.B t
+or
+.B nil
+depending on whether the association with the peer is expected to be
+temporary or persistent (respectively).
 .RE
 .SP
 .BI "PING \fR[" options "\fR] " peer
 .RE
 .SP
 .BI "PING \fR[" options "\fR] " peer
index 2024627..a2907be 100644 (file)
@@ -617,6 +617,7 @@ typedef struct peerspec {
   unsigned f;                          /* Flags for the peer */
 #define PSF_KXMASK 255u                        /*   Key-exchange flags to set */
 #define PSF_MOBILE 256u                        /*   Address may change rapidly */
   unsigned f;                          /* Flags for the peer */
 #define PSF_KXMASK 255u                        /*   Key-exchange flags to set */
 #define PSF_MOBILE 256u                        /*   Address may change rapidly */
+#define PSF_EPHEM 512u                 /*   Association is ephemeral */
 } peerspec;
 
 typedef struct peer_byname {
 } peerspec;
 
 typedef struct peer_byname {
@@ -1693,13 +1694,14 @@ extern peer *p_find(const char */*name*/);
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-extern void p_destroy(peer */*p*/);
+extern void p_destroy(peer */*p*/, int /*bye*/);
 
 /* --- @FOREACH_PEER@ --- *
  *
 
 /* --- @FOREACH_PEER@ --- *
  *
index 5a11164..1e4e2d2 100644 (file)
@@ -467,6 +467,7 @@ The service will submit the command
 .RB [ \-priv
 .IR tag ]
 .RB [ \-mobile ]
 .RB [ \-priv
 .IR tag ]
 .RB [ \-mobile ]
+.RB [ \-ephemeral ]
 .RB [ \-tunnel
 .IR driver ]
 .I address
 .RB [ \-tunnel
 .IR driver ]
 .I address
@@ -533,6 +534,19 @@ to the
 .B tunnel
 key.
 .hP \*o
 .B tunnel
 key.
 .hP \*o
+The option
+.B \-ephemeral
+is provided if the peer's database record assigns the
+.B ephemeral
+key one of the values
+.BR t ,
+.BR true ,
+.BR y ,
+.BR yes,
+or
+.BR on ;
+or if it is absent.
+.hP \*o
 The
 .I address
 is the value assigned to the
 The
 .I address
 is the value assigned to the
index fe8d73b..99dbd8a 100644 (file)
@@ -717,11 +717,12 @@ def disownpeer(peer):
     T.Coroutine(run_ifupdown, name = 'ifdown %s' % peer.name) \
         .switch('ifdown', peer)
 
     T.Coroutine(run_ifupdown, name = 'ifdown %s' % peer.name) \
         .switch('ifdown', peer)
 
-def addpeer(peer, addr):
+def addpeer(peer, addr, ephemp):
   """
   Process a connect request from a new peer PEER on address ADDR.
 
   """
   Process a connect request from a new peer PEER on address ADDR.
 
-  Any existing peer with this name is disconnected from the server.
+  Any existing peer with this name is disconnected from the server.  EPHEMP
+  is the default ephemeral-ness state for the new peer.
   """
   if peer.name in S.list():
     S.kill(peer.name)
   """
   if peer.name in S.list():
     S.kill(peer.name)
@@ -734,6 +735,8 @@ def addpeer(peer, addr):
           mobile = peer.get('mobile', filter = boolean, default = False),
           knock = peer.get('knock', default = None),
           cork = peer.get('cork', filter = boolean, default = False),
           mobile = peer.get('mobile', filter = boolean, default = False),
           knock = peer.get('knock', default = None),
           cork = peer.get('cork', filter = boolean, default = False),
+          ephemeral = peer.get('ephemeral', filter = boolean,
+                               default = ephemp),
           *addr)
   except T.TripeError, exc:
     raise T.TripeJobError(*exc.args)
           *addr)
   except T.TripeError, exc:
     raise T.TripeJobError(*exc.args)
@@ -778,7 +781,7 @@ def notify(_, code, *rest):
       S.warn(['connect', 'knock-tag-mismatch',
               'peer', pname, 'public-key-tag', ktag])
       return
       S.warn(['connect', 'knock-tag-mismatch',
               'peer', pname, 'public-key-tag', ktag])
       return
-    T.spawn(addpeer, p, rest[1:])
+    T.spawn(addpeer, p, rest[1:], True)
 
 ###--------------------------------------------------------------------------
 ### Command implementation.
 
 ###--------------------------------------------------------------------------
 ### Command implementation.
@@ -813,7 +816,7 @@ def cmd_active(name):
   addr = peer.get('peer')
   if addr == 'PASSIVE':
     raise T.TripeJobError('passive-peer', name)
   addr = peer.get('peer')
   if addr == 'PASSIVE':
     raise T.TripeJobError('passive-peer', name)
-  addpeer(peer, M.split(addr, quotep = True)[0])
+  addpeer(peer, M.split(addr, quotep = True)[0], True)
 
 def cmd_listactive():
   """
 
 def cmd_listactive():
   """
@@ -875,7 +878,7 @@ def cmd_passive(*args):
     addr = cr.parent.switch()
     if addr is None:
       raise T.TripeJobError('connect-timeout')
     addr = cr.parent.switch()
     if addr is None:
       raise T.TripeJobError('connect-timeout')
-    addpeer(peer, addr)
+    addpeer(peer, addr, True)
   finally:
     del chalmap[chal]
 
   finally:
     del chalmap[chal]
 
@@ -911,7 +914,7 @@ def setup():
     for name in M.split(autos)[0]:
       try:
         peer = Peer(name, cdb)
     for name in M.split(autos)[0]:
       try:
         peer = Peer(name, cdb)
-        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0])
+        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0], False)
       except T.TripeJobError, err:
         S.warn('connect', 'auto-add-failed', name, *err.args)
 
       except T.TripeJobError, err:
         S.warn('connect', 'auto-add-failed', name, *err.args)
 
index aab8e79..5c6d7ba 100644 (file)
@@ -408,6 +408,8 @@ local PKTINFO = {
              dissect = { dissect_misc_ciphertext } },
       [5] = { label = "MISC_GREET", info = "greeting",
              dissect = { dissect_misc_payload } },
              dissect = { dissect_misc_ciphertext } },
       [5] = { label = "MISC_GREET", info = "greeting",
              dissect = { dissect_misc_payload } },
+      [6] = { label = "MISC_BYE", info = "disconnect notification",
+             dissect = { dissect_misc_ciphertext } },
     }
   }
 }
     }
   }
 }