From 6411163d48434575eaf49463e056e971a2fba16a Mon Sep 17 00:00:00 2001 Message-Id: <6411163d48434575eaf49463e056e971a2fba16a.1714177171.git.mdw@distorted.org.uk> From: Mark Wooding Date: Mon, 27 Jun 2011 03:26:00 +0100 Subject: [PATCH] New peer option `-mobile': follow rapid IP address and port changes. Organization: Straylight/Edgeware From: Mark Wooding Mobile devices on 3G networks can change apparent IP address and port numbers rapidly. If any peer has the `-mobile' flag (PSF_MOBILE) set, and a MSG_PACKET message arrives from an unexpected source, see if any of the `-mobile' peers can decrypt it: if so, update the peer's address rather than rejecting the packet. This is inefficient because it involves a linear search of the peer list. If this turns out to be a problem, we can make some protocol changes (e.g., inserting a peer hint into the packet) later. Note that an adversary can deny service by capturing legitimate packets from a mobile source and sending them on with a different address. The peer will assume that the target's address has changed. But to make this work effectively, the adversary's packet has to reach the target before (or instead of) the legitimate one, or else the bogus packet will be rejected for having a duplicate sequence number. The next packet received unmodified from the source will switch the target's idea of the source's address back again anyway. An adversary who can consistently prevent packets from being delivered can trivially deny service anyway, so this isn't actually much of a concern. --- mon/tripemon.in | 19 +++++- peerdb/peers.in.5.in | 6 +- py/tripe.py.in | 2 +- server/admin.c | 1 + server/peer.c | 134 +++++++++++++++++++++++++++++++++------- server/tripe-admin.5.in | 16 +++++ server/tripe.h | 3 +- svc/connect.8.in | 13 ++++ svc/connect.in | 4 +- 9 files changed, 170 insertions(+), 28 deletions(-) diff --git a/mon/tripemon.in b/mon/tripemon.in index 413c8abb..4b8dd24f 100644 --- a/mon/tripemon.in +++ b/mon/tripemon.in @@ -293,7 +293,13 @@ class Peer (MonitorObject): def update(me, hunoz = None): """Update the peer, fetching information about it from the server.""" - addr = conn.addr(me.name) + me._setaddr(me, conn.addr(me.name)) + me.ifname = conn.ifname(me.name) + me.__dict__.update(conn.peerinfo(me.name)) + me.changehook.run() + + def _setaddr(me, addr): + """Set the peer's address.""" if addr[0] == 'INET': ipaddr, port = addr[1:] try: @@ -303,8 +309,10 @@ class Peer (MonitorObject): me.addr = 'INET %s:%s' % (ipaddr, port) else: me.addr = ' '.join(addr) - me.ifname = conn.ifname(me.name) - me.__dict__.update(conn.peerinfo(me.name)) + + def setaddr(me, addr): + """Informs the object of a change to its address to ADDR.""" + me._setaddr(addr) me.changehook.run() def setifname(me, newname): @@ -476,6 +484,11 @@ class Monitor (HookClient): me.peers[rest[0]].setifname(rest[2]) except KeyError: pass + elif code == 'NEWADDR': + try: + me.peers[rest[0]].setaddr(rest[1:]) + except KeyError: + pass elif code == 'SVCCLAIM': T.aside(me.services.add, rest[0], rest[1]) if rest[0] == 'connect': diff --git a/peerdb/peers.in.5.in b/peerdb/peers.in.5.in index ad7faeda..c8763656 100644 --- a/peerdb/peers.in.5.in +++ b/peerdb/peers.in.5.in @@ -135,7 +135,7 @@ Shell command for initiating connection to this peer. Used by .BR watch (8). .TP .B cork -Don't initiate immediate key exchange.. Used by +Don't initiate immediate key exchange. Used by .BR connect (8). .TP .B every @@ -170,6 +170,10 @@ Interval for sending keepalive pings. Used by Key tag to use to authenticate the peer. Used by .BR connect (8). .TP +.B mobile +Peer's IP address is highly volatile. Used by +.BR connect (8). +.TP .B mtu Maximum transmission unit for the tunnel interface. Used by .BR tripe-ifup (8). diff --git a/py/tripe.py.in b/py/tripe.py.in index 37de5a40..cefb667f 100644 --- a/py/tripe.py.in +++ b/py/tripe.py.in @@ -831,7 +831,7 @@ class TripeCommandDispatcher (TripeConnection): return _simple(me.command(bg = True, *['ADD'] + _kwopts(kw, ['tunnel', 'keepalive', - 'key', 'cork']) + + 'key', 'cork', 'mobile']) + [peer] + list(addr))) def addr(me, peer): diff --git a/server/admin.c b/server/admin.c index df8af92b..7fa3f0f8 100644 --- a/server/admin.c +++ b/server/admin.c @@ -1258,6 +1258,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) xfree(add->peer.tag); add->peer.tag = xstrdup(arg); }) + OPT("-mobile", { add->peer.f |= PSF_MOBILE; }) }); /* --- Make sure someone's not got there already --- */ diff --git a/server/peer.c b/server/peer.c index ec89f771..72287cdd 100644 --- a/server/peer.c +++ b/server/peer.c @@ -33,6 +33,7 @@ static sym_table byname; static addrmap byaddr; static sel_file sock; +static unsigned nmobile; /*----- Tunnel table ------------------------------------------------------*/ @@ -122,6 +123,23 @@ found: p_pingdone(pg, PING_OK); } +/* --- @p_rxupdstats@ --- * + * + * Arguments: @peer *p@ = peer to update + * @size_t n@ = size of incoming packet + * + * Returns: --- + * + * Use: Updates the peer's incoming packet statistics. + */ + +static void p_rxupdstats(peer *p, size_t n) +{ + p->st.t_last = time(0); + p->st.n_in++; + p->st.sz_in += n; +} + /* --- @p_encrypt@ --- * * * Arguments: @peer *p@ = peer to encrypt message to @@ -151,7 +169,9 @@ static int p_encrypt(peer *p, int ty, buf *bin, buf *bout) /* --- @p_decrypt@ --- * * - * Arguments: @peer *p@ = peer to decrypt message from + * Arguments: @peer **pp@ = pointer to peer to decrypt message from + * @addr *a@ = address the packet arrived on + * @size_t n@ = size of original incoming packet * @int ty@ = message type to expect * @buf *bin, *bout@ = input and output buffers * @@ -159,13 +179,67 @@ static int p_encrypt(peer *p, int ty, buf *bin, buf *bout) * * Use: Convenience function for packet decryption. Reports errors * and updates statistics appropriately. + * + * If @*pp@ is null on entry and there are mobile peers then we + * see if any of them can decrypt the packet. If so, we record + * @*a@ as the peer's new address and send a notification. */ -static int p_decrypt(peer *p, int ty, buf *bin, buf *bout) +static int p_decrypt(peer **pp, addr *a, size_t n, + int ty, buf *bin, buf *bout) { - if (ksl_decrypt(&p->ks, ty, bin, bout)) { - p->st.n_reject++; - a_warn("PEER", "?PEER", p, "decrypt-failed", A_END); + peer *p; + peer_byaddr *pa; + int err = KSERR_DECRYPT; + unsigned f; + + if (*pp) { + p = *pp; + T( trace(T_PEER, "peer: decrypting packet from known peer `%s'", + p_name(p)); ) + err = ksl_decrypt(&p->ks, ty, bin, bout); + } else { + p = 0; + if (nmobile) { + T( trace(T_PEER, "peer: unknown source: trying mobile peers..."); ) + FOREACH_PEER(q, { + if ((err = ksl_decrypt(&q->ks, ty, bin, bout)) == KSERR_DECRYPT) { + T( trace(T_PEER, "peer: peer `%s' failed to decrypt", + p_name(q)); ) + continue; + } else { + p = *pp = q; + IF_TRACING(T_PEER, { + if (!err) + trace(T_PEER, "peer: peer `%s' reports success", p_name(p)); + else { + trace(T_PEER, "peer: peer `%s' reports decryption error %d", + p_name(p), err); + } + }) + break; + } + }); + } + if (!p) { + a_warn("PEER", "-", "unexpected-source", "?ADDR", a, A_END); + return (-1); + } + if (!err) { + T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); ) + p_rxupdstats(p, n); + pa = am_find(&byaddr, a, sizeof(peer_byaddr), &f); assert(!f); + am_remove(&byaddr, p->byaddr); + p->byaddr = pa; + pa->p = p; + p->spec.sa = *a; + a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END); + } + } + if (err) { + if (p) p->st.n_reject++; + a_warn("PEER", "?PEER", p, "decrypt-failed", + "error-code", "%d", err, A_END); return (-1); } if (!BOK(bout)) @@ -225,23 +299,28 @@ static void p_read(int fd, unsigned mode, void *v) return; } - /* --- Find the appropriate peer --- */ + /* --- Find the appropriate peer --- * + * + * At this stage, don't worry too much about whether we actually found it. + */ - if ((p = p_findbyaddr(&a)) == 0) { - a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END); - return; - } + p = p_findbyaddr(&a); IF_TRACING(T_PEER, { - trace(T_PEER, "peer: packet received from `%s'", p->spec.name); + if (p) { + trace(T_PEER, + "peer: packet received from `%s' from address INET %s %d", + p_name(p), inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port)); + } else { + trace(T_PEER, "peer: packet received from unknown address INET %s %d", + inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port)); + } 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; + if (p) p_rxupdstats(p, n); buf_init(&b, buf_i, n); if ((ch = buf_getbyte(&b)) < 0) { a_warn("PEER", "?PEER", p, "bad-packet", "no-type", A_END); @@ -255,11 +334,11 @@ static void p_read(int fd, unsigned mode, void *v) "bad-packet", "unknown-type", "0x%02x", ch, A_END); - p->st.n_reject++; + if (p) p->st.n_reject++; return; } buf_init(&bb, buf_o, sizeof(buf_o)); - if (p_decrypt(p, MSG_PACKET, &b, &bb)) + if (p_decrypt(&p, &a, n, MSG_PACKET, &b, &bb)) return; if (BOK(&bb)) { p->st.n_ipin++; @@ -271,23 +350,27 @@ static void p_read(int fd, unsigned mode, void *v) } break; case MSG_KEYEXCH: + if (!p) goto unexp; kx_message(&p->kx, ch & MSG_TYPEMASK, &b); break; case MSG_MISC: switch (ch & MSG_TYPEMASK) { case MISC_NOP: + if (!p) goto unexp; T( trace(T_PEER, "peer: received NOP packet"); ) break; case MISC_PING: + if (!p) goto unexp; buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b)); p_txend(p); break; case MISC_PONG: + if (!p) goto unexp; p_ponged(p, MISC_PONG, &b); break; case MISC_EPING: buf_init(&bb, buf_t, sizeof(buf_t)); - if (p_decrypt(p, ch, &b, &bb)) + if (p_decrypt(&p, &a, n, ch, &b, &bb)) return; if (BOK(&bb)) { buf_flip(&bb); @@ -298,7 +381,7 @@ static void p_read(int fd, unsigned mode, void *v) break; case MISC_EPONG: buf_init(&bb, buf_t, sizeof(buf_t)); - if (p_decrypt(p, ch, &b, &bb)) + if (p_decrypt(&p, &a, n, ch, &b, &bb)) return; if (BOK(&bb)) { buf_flip(&bb); @@ -308,13 +391,16 @@ static void p_read(int fd, unsigned mode, void *v) } break; default: - p->st.n_reject++; + if (p) p->st.n_reject++; a_warn("PEER", "?PEER", p, "bad-packet", "unknown-category" "0x%02x", ch, A_END); break; + unexp: + a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END); + break; } } @@ -764,6 +850,7 @@ peer *p_create(peerspec *spec) a_notify("KXSTART", "?PEER", p, A_END); /* Couldn't tell anyone before */ } + if (p->spec.f & PSF_MOBILE) nmobile++; return (p); tidy_4: @@ -790,7 +877,8 @@ tidy_0: * Returns: A pointer to the peer's name. */ -const char *p_name(peer *p) { return (p->spec.name); } +const char *p_name(peer *p) + { if (p) return (p->spec.name); else return ("-"); } /* --- @p_tag@ --- * * @@ -824,8 +912,10 @@ peer *p_findbyaddr(const addr *a) { peer_byaddr *pa; - if ((pa = am_find(&byaddr, a, 0, 0)) != 0) + if ((pa = am_find(&byaddr, a, 0, 0)) != 0) { + assert(pa->p); return (pa->p); + } return (0); } @@ -864,6 +954,8 @@ void p_destroy(peer *p) a_notify("KILL", "%s", p->spec.name, A_END); ksl_free(&p->ks); kx_free(&p->kx); + if (p->spec.f & PSF_MOBILE) + nmobile--; if (p->ifname) xfree(p->ifname); if (p->spec.tag) diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index 21426747..5b0fe3be 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -354,6 +354,16 @@ Use the public key to authenticate the peer. The default is to use the key tagged .IR peer . .TP +.B "\-mobile" +The peer is a mobile device, and is likely to change address rapidly. +If a packet arrives from an unknown address, the server's usual response +is to log a warning and discard it. If the server knows of any mobile +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 +notification. +.TP .BI "\-tunnel " tunnel Use the named tunnel driver, rather than the default. .\"-opts @@ -1082,6 +1092,12 @@ Key exchange with has begun or restarted. If key exchange keeps failing, this message will be repeated periodically. .SP +.BI "NEWADDR " peer " " address +The given mobile +.IR peer 's +IP address has been changed to +.IR address . +.SP .BI "NEWIFNAME " peer " " old-name " " new-name The given .IR peer 's diff --git a/server/tripe.h b/server/tripe.h index b6c1cd5d..8a0be518 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -341,7 +341,8 @@ typedef struct peerspec { addr sa; /* Socket address to speak to */ size_t sasz; /* Socket address size */ unsigned f; /* Flags for the peer */ -#define PSF_KXMASK 255u /* Key exchange flags to set */ +#define PSF_KXMASK 255u /* Key-exchange flags to set */ +#define PSF_MOBILE 256u /* Address may change rapidly */ } peerspec; typedef struct peer_byname { diff --git a/svc/connect.8.in b/svc/connect.8.in index ddd2636b..d529918d 100644 --- a/svc/connect.8.in +++ b/svc/connect.8.in @@ -174,6 +174,7 @@ The service will submit the command .IR time ] .RB [ \-key .IR tag ] +.RB [ \-mobile ] .RB [ \-tunnel .IR driver ] .I address @@ -211,6 +212,18 @@ to the key. .hP \*o The option +.B \-mobile +is provided if the peer's database record assigns the +.B mobile +key one of the values +.BR t , +.BR true , +.BR y , +.BR yes, +or +.BR on . +.hP \*o +The option .B \-tunnel .I driver is provided if the database record assigns a value diff --git a/svc/connect.in b/svc/connect.in index 37241bbc..0ae539f5 100644 --- a/svc/connect.in +++ b/svc/connect.in @@ -87,11 +87,13 @@ def addpeer(peer, addr): if peer.name in S.list(): S.kill(peer.name) try: + booltrue = ['t', 'true', 'y', 'yes', 'on'] S.add(peer.name, tunnel = peer.get('tunnel', None), keepalive = peer.get('keepalive', None), key = peer.get('key', None), - cork = peer.get('cork', 'nil') in ['t', 'true', 'y', 'yes', 'on'], + mobile = peer.get('mobile', 'nil') in booltrue, + cork = peer.get('cork', 'nil') in booltrue, *addr) except T.TripeError, exc: raise T.TripeJobError(*exc.args) -- [mdw]