From 693588c0c399828e61728376aaebecd837dac2b2 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 20 Sep 2021 21:13:27 +0100 Subject: [PATCH] server/peer.c, etc.: Introduce who-goes-there protocol. 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 | 3 +++ server/peer.c | 65 ++++++++++++++++++++++++++++++++++++++++++++----- server/tests.at | 12 +++++++++ server/tripe-admin.5.in | 21 ++++++++++++++++ server/tripe.h | 10 ++++++++ svc/connect.in | 4 +++ wireshark/tripe.lua | 4 ++- 7 files changed, 112 insertions(+), 7 deletions(-) diff --git a/common/protocol.h b/common/protocol.h index e40cf177..be761914 100644 --- a/common/protocol.h +++ b/common/protocol.h @@ -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 --- * * diff --git a/server/peer.c b/server/peer.c index 0b5a6c22..b20f2fd3 100644 --- a/server/peer.c +++ b/server/peer.c @@ -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: diff --git a/server/tests.at b/server/tests.at index 582503a9..fc9c5375 100644 --- a/server/tests.at +++ b/server/tests.at @@ -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], [ diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index 3e7bd8ef..ea2183c3 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -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 diff --git a/server/tripe.h b/server/tripe.h index a304e3ef..6c5c8276 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -165,6 +165,8 @@ #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@ --- * diff --git a/svc/connect.in b/svc/connect.in index fa5a22c2..9bb582cb 100644 --- a/svc/connect.in +++ b/svc/connect.in @@ -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] diff --git a/wireshark/tripe.lua b/wireshark/tripe.lua index a3544e9d..031720c8 100644 --- a/wireshark/tripe.lua +++ b/wireshark/tripe.lua @@ -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 } }, } } } -- 2.11.0