From 0ba8de86535cf6025076f5034b76ab753e4dde08 Mon Sep 17 00:00:00 2001 Message-Id: <0ba8de86535cf6025076f5034b76ab753e4dde08.1714979643.git.mdw@distorted.org.uk> From: Mark Wooding Date: Fri, 16 Sep 2005 13:08:42 +0000 Subject: [PATCH] Keepalives and pings. Organization: Straylight/Edgeware From: mdw --- admin.c | 207 +++++++++++++++++++---- doc/tripe-admin.5 | 121 ++++++++++++- ethereal/packet-tripe.c | 54 ++++++ peer.c | 366 +++++++++++++++++++++++++++++++++++----- tripe-protocol.h | 14 +- tripe.h | 82 +++++++-- 6 files changed, 751 insertions(+), 93 deletions(-) diff --git a/admin.c b/admin.c index bbfc0ed6..e081bdf6 100644 --- a/admin.c +++ b/admin.c @@ -71,6 +71,7 @@ static sig s_term, s_int, s_hup; #define F_INIT 2u #define T_RESOLVE SEC(30) +#define T_PING SEC(5) static void a_destroy(admin */*a*/); static void a_lock(admin */*a*/); @@ -467,6 +468,29 @@ static void a_sighup(int sig, void *v) a_warn("SERVER ignore signal SIGHUP"); } +/* --- @a_parsetime@ --- * + * + * Arguments; @const char *p@ = time string to parse + * + * Returns: Time in seconds, or @< 0@ on error. + */ + +static long a_parsetime(const char *p) +{ + char *q; + long t = strtol(p, &q, 0); + + switch (*q) { + case 'd': t *= 24; + case 'h': t *= 60; + case 'm': t *= 60; + case 's': if (q[1] != 0) + default: t = -1; + case 0: break; + } + return (t); +} + /*----- Adding peers ------------------------------------------------------*/ /* --- @a_resolve@ --- * @@ -489,18 +513,18 @@ static void a_resolve(struct hostent *h, void *v) sel_rmtimer(&a->t); if (!h) a_fail(a, "resolve-error %s", a->paddr); - else if (p_find(a->pname)) - a_fail(a, "peer-exists %s", a->pname); + else if (p_find(a->peer.name)) + a_fail(a, "peer-exists %s", a->peer.name); else { - memcpy(&a->peer.sin.sin_addr, h->h_addr, sizeof(struct in_addr)); - if (!p_create(a->pname, a->tops, &a->peer.sa, a->sasz)) - a_fail(a, "peer-create-fail %s", a->pname); + memcpy(&a->peer.sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr)); + if (!p_create(&a->peer)) + a_fail(a, "peer-create-fail %s", a->peer.name); else a_ok(a); } - xfree(a->pname); + xfree(a->peer.name); xfree(a->paddr); - a->pname = 0; + a->peer.name = 0; selbuf_enable(&a->b); a_unlock(a); } @@ -523,9 +547,9 @@ static void a_timer(struct timeval *tv, void *v) T( trace(T_ADMIN, "admin: %u resolver timeout", a->seq); ) bres_abort(&a->r); a_fail(a, "resolver-timeout %s\n", a->paddr); - xfree(a->pname); + xfree(a->peer.name); xfree(a->paddr); - a->pname = 0; + a->peer.name = 0; selbuf_enable(&a->b); a_unlock(a); } @@ -546,7 +570,6 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) unsigned long pt; struct timeval tv; unsigned i, j; - const tunnel_ops *tops = tun_default; char *p; /* --- Make sure someone's not got there already --- */ @@ -556,6 +579,12 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) return; } + /* --- Set stuff up --- */ + + a->peer.name = av[0]; + a->peer.t_ka = 0; + a->peer.tops = tun_default; + /* --- Parse options --- */ i = 1; @@ -563,37 +592,42 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) if (!av[i]) goto bad_syntax; if (mystrieq(av[i], "-tunnel")) { - i++; - if (!av[i]) - goto bad_syntax; + if (!av[++i]) goto bad_syntax; for (j = 0;; j++) { if (!tunnels[j]) { a_fail(a, "unknown-tunnel %s", av[i]); return; } if (mystrieq(av[i], tunnels[j]->name)) { - tops = tunnels[j]; + a->peer.tops = tunnels[j]; break; } } - i++; + } else if (mystrieq(av[i], "-keepalive")) { + long t; + if (!av[++i]) goto bad_syntax; + if ((t = a_parsetime(av[i])) < 0) { + a_fail(a, "bad-time-spec %s", av[i]); + return; + } + a->peer.t_ka = t; } else if (mystrieq(av[i], "--")) { i++; break; } else break; + i++; } /* --- Fill in the easy bits of address --- */ - BURN(a->peer); if (mystrieq(av[i], "inet")) i++; if (ac - i != 2) { - a_fail(a, "bad-syntax -- add PEER [-tunnel TUN] [inet] ADDRESS PORT"); + a_fail(a, "bad-syntax -- add PEER [OPTIONS] [inet] ADDRESS PORT"); return; } - a->peer.sin.sin_family = AF_INET; - a->sasz = sizeof(a->peer.sin); + a->peer.sa.sin.sin_family = AF_INET; + a->peer.sasz = sizeof(a->peer.sa.sin); pt = strtoul(av[i + 1], &p, 0); if (*p) { struct servent *s = getservbyname(av[i + 1], "udp"); @@ -607,15 +641,16 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) a_fail(a, "invalid-port %lu", pt); return; } - a->peer.sin.sin_port = htons(pt); + a->peer.sa.sin.sin_port = htons(pt); /* --- If the name is numeric, do it the easy way --- */ - if (inet_aton(av[i], &a->peer.sin.sin_addr)) { - if (!p_create(av[0], tops, &a->peer.sa, a->sasz)) + if (inet_aton(av[i], &a->peer.sa.sin.sin_addr)) { + if (!p_create(&a->peer)) a_fail(a, "peer-create-fail %s", av[0]); else a_ok(a); + a->peer.name = 0; return; } @@ -626,9 +661,8 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) * the system continues regardless), but makes life simpler for the client. */ - a->pname = xstrdup(av[0]); + a->peer.name = xstrdup(av[0]); a->paddr = xstrdup(av[i]); - a->tops = tops; selbuf_disable(&a->b); gettimeofday(&tv, 0); tv.tv_sec += T_RESOLVE; @@ -639,10 +673,108 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) return; bad_syntax: - a_fail(a, "bad-syntax -- add PEER [-tunnel TUN] ADDR ..."); + a_fail(a, "bad-syntax -- add PEER [OPTIONS] ADDR ..."); + return; +} + +/*----- Ping --------------------------------------------------------------*/ + +/* --- @a_pong@ --- * + * + * Arguments: @int rc@ = return code + * @void *av@ = admin connection which requested the ping + * + * Returns: --- + * + * Use: Collects what happened to a ping message. + */ + +static void a_pong(int rc, void *av) +{ + admin *a = av; + struct timeval tv; + double millis; + + a_lock(a); + switch (rc) { + case PING_OK: + gettimeofday(&tv, 0); + tv_sub(&tv, &tv, &a->pingtime); + millis = (double)tv.tv_sec * 1000 + (double)tv.tv_usec/1000; + a_info(a, "ping-ok %.1f", millis); + a_ok(a); + break; + case PING_TIMEOUT: + a_info(a, "ping-timeout"); + a_ok(a); + break; + case PING_PEERDIED: + a_info(a, "ping-peer-died"); + a_ok(a); + break; + default: + abort(); + } + a_unlock(a); +} + +/* --- @acmd_ping@, @acmd_eping@ --- * + * + * Arguments: @admin *a@ = connection which requested the ping + * @unsigned ac@ = argument count + * @char *av[]@ = pointer to the argument list + * + * Returns: --- + * + * Use: Pings a peer. + */ + +static void a_ping(admin *a, unsigned ac, char *av[], + const char *cmd, unsigned msg) +{ + long t = T_PING; + int i; + peer *p; + + i = 0; + for (;;) { + if (!av[i]) + goto bad_syntax; + if (mystrieq(av[i], "-timeout")) { + if (!av[++i]) goto bad_syntax; + if ((t = a_parsetime(av[i])) < 0) { + a_fail(a, "bad-time-spec %s", av[i]); + return; + } + } else if (mystrieq(av[i], "--")) { + i++; + break; + } else + break; + i++; + } + + if (!av[i]) goto bad_syntax; + if ((p = p_find(av[i])) == 0) { + a_fail(a, "unknown-peer %s", av[i]); + return; + } + gettimeofday(&a->pingtime, 0); + if (p_pingsend(p, &a->ping, msg, t, a_pong, a)) + a_fail(a, "ping-send-failed"); + return; + +bad_syntax: + a_fail(a, "bad-syntax -- %s [OPTIONS] PEER", cmd); return; } +static void acmd_ping(admin *a, unsigned ac, char *av[]) + { a_ping(a, ac, av, "ping", MISC_PING); } +static void acmd_eping(admin *a, unsigned ac, char *av[]) + { a_ping(a, ac, av, "eping", MISC_EPING); } + + /*----- Administration commands -------------------------------------------*/ /* --- Miscellaneous commands --- */ @@ -856,6 +988,17 @@ static void acmd_kill(admin *a, unsigned ac, char *av[]) } } +static void acmd_forcekx(admin *a, unsigned ac, char *av[]) +{ + peer *p; + if ((p = p_find(av[0])) == 0) + a_fail(a, "unknown-peer %s", av[0]); + else { + kx_start(&p->kx); + a_ok(a); + } +} + static void acmd_quit(admin *a, unsigned ac, char *av[]) { a_warn("SERVER quit admin-request"); @@ -903,8 +1046,11 @@ static const acmd acmdtab[] = { { "ifname", "ifname PEER", 1, 1, acmd_ifname }, { "addr", "addr PEER", 1, 1, acmd_addr }, { "stats", "stats PEER", 1, 1, acmd_stats }, + { "ping", "ping [OPTIONS] PEER", 1, 0xffff, acmd_ping }, + { "eping", "eping [OPTIONS] PEER", 1, 0xffff, acmd_eping }, { "kill", "kill PEER", 1, 1, acmd_kill }, - { "add", "add PEER [-tunnel TUN] ADDR ...", + { "forcekx", "forcekx PEER", 1, 1, acmd_forcekx }, + { "add", "add PEER [OPTIONS] ADDR ...", 2, 0xffff, acmd_add }, { "tunnels", "tunnels", 0, 0, acmd_tunnels }, { "quit", "quit", 0, 0, acmd_quit }, @@ -955,12 +1101,14 @@ static void a_unlock(admin *a) a->seq); ) selbuf_destroy(&a->b); - if (a->pname) { - xfree(a->pname); + if (a->peer.name) { + xfree(a->peer.name); xfree(a->paddr); bres_abort(&a->r); sel_rmtimer(&a->t); } + if (a->ping.p) + p_pingdone(&a->ping, PING_NONOTIFY); if (a->b.reader.fd != a->w.fd) close(a->b.reader.fd); close(a->w.fd); @@ -1081,7 +1229,8 @@ void a_create(int fd_in, int fd_out, unsigned f) T( static unsigned seq = 0; a->seq = seq++; ) T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); ) - a->pname = 0; + a->peer.name = 0; + a->ping.p = 0; a->f = f; if (fd_in == STDIN_FILENO) a_stdin = a; diff --git a/doc/tripe-admin.5 b/doc/tripe-admin.5 index 79874e91..327d1b56 100644 --- a/doc/tripe-admin.5 +++ b/doc/tripe-admin.5 @@ -109,7 +109,22 @@ is the network address (see above for the format) at which the peer can be contacted. The following options are recognised. .RS .TP -.BI "-tunnel " tunnel +.BI "\-keepalive " time +Send a no-op packet if we've not sent a packet to the peer in the last +.I time +interval. This is useful for persuading port-translating firewalls to +believe that the `connection' is still active. The +.I time +is expressed as a nonnegative integer followed optionally by +.BR d , +.BR h , +.BR m , +or +.BR s +for days, hours, minutes, or seconds respectively; if no suffix is +given, seconds are assumed. +.TP +.BI "\-tunnel " tunnel Use the named tunnel driver, rather than the default. .RE .TP @@ -123,6 +138,14 @@ line reporting the IP address and port number stored for Causes the server to disassociate itself from its terminal and become a background task. This only works once. A warning is issued. .TP +.BI "EPING \fR[" options "\fR] " peer +Sends an encrypted ping to the peer, and expects an encrypted response. +This checks that the peer is running (and not being impersonated), and +that it can encrypt and decrypt packets correctly. Options and +responses are the same as for the +.B PING +command. +.TP .B "HELP" Causes the server to emit an .B INFO @@ -157,6 +180,44 @@ Issues a .B USER notification to all interested administration clients. .TP +.BI "PING \fR[" options "\fR] " peer +Send a transport-level ping to the peer. The ping and its response are +not encrypted or authenticated. This command, possibly in conjunction +with tracing, is useful for ensuring that UDP packets are actually +flowing in both directions. See also the +.B EPING +command. +.IP +An +.B INFO +line is printed describing the outcome: +.RS +.TP +.BI "ping-ok " millis +A response was received +.I millis +after the ping was sent. +.TP +.BI "ping-timeout" +No response was received within the time allowed. +.TP +.BI "ping-peer-died" +The peer was killed (probably by another admin connection) before a +response was received. +.RE +.IP +Options recognized for this command are: +.RS +.TP +.BI "\-timeout " time +Wait for +.I time +seconds before giving up on a response. The default is 5 seconds. (The +time format is the same as for the +.B "ADD \-keepalive" +option.) +.RE +.TP .B "PORT" Emits an .B INFO @@ -316,6 +377,18 @@ server is already running as a daemon. (For any command.) The command couldn't be understood: e.g., the number of arguments was wrong. .TP +.BI "bad-time-spec " word +The +.I word +is not a valid time interval specification. Acceptable time +specifications are nonnegative integers followed optionally by +.BR d , +.BR h , +.BR m , +or +.BR s , +for days, hours, minutes, or seconds, respectively. +.TP .BI "bad-trace-option " char (For .BR TRACE .) @@ -351,6 +424,10 @@ why. There is already a peer named .IR peer . .TP +.B "ping-send-failed" +The attempt to send a ping packet failed, probably due to lack of +encryption keys. +.TP .BI "resolve-error " hostname (For .BR ADD .) @@ -571,13 +648,6 @@ a peer, or .RB ` \- ' if none is relevant. .TP -.BI "PEER \- unexpected-source " address\fR... -A packet arrived from -.I address -(a network address \(en see above), but no peer is known at that -address. This may indicate a misconfiguration, or simply be a result of -one end of a connection being set up before the other. -.TP .BI "PEER " peer " bad-packet no-type" An empty packet arrived. This is very strange. .TP @@ -593,6 +663,15 @@ The message type (in hex) isn't understood. Probably a strange random packet from somewhere; could be an unlikely bug. .TP +.BI "PEER " peer " corrupt-encrypted-ping" +The peer sent a ping response which matches an outstanding ping, but its +payload is wrong. There's definitely a bug somewhere. +.TP +.BI "PEER " peer " corrupt-transport-ping" +The peer (apparently) sent a ping response which matches an outstanding +ping, but its payload is wrong. Either there's a bug, or the bad guys +are playing tricks on you. +.TP .BI "PEER " peer " decrypt-failed" An encrypted IP packet failed to decrypt. It may have been mangled in transit, or may be a very old packet from an expired previous session @@ -600,6 +679,14 @@ key. There is usually a considerable overlap in the validity periods of successive session keys, so this shouldn't occur unless the key exchange takes ages or fails. .TP +.BI "PEER " peer " malformed-encrypted-ping" +The peer sent a ping response which is hopelessly invalid. There's +definitely a bug somewhere. +.TP +.BI "PEER " peer " malformed-transport-ping" +The peer (apparently) sent a ping response which is hopelessly invalid. +Either there's a bug, or the bad guys are playing tricks on you. +.TP .BI "PEER " peer " packet-build-failed" There wasn't enough space in our buffer to put the packet we wanted to send. Shouldn't happen. @@ -610,6 +697,24 @@ An error occurred trying to read an incoming packet. .BI "PEER " peer " socket-write-error \-\- " message An error occurred attempting to send a network packet. We lost that one. +.TP +.BI "PEER " peer " unexpected-encrypted-ping 0x" id +The peer sent an encrypted 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. +.TP +.BI "PEER \- unexpected-source " address\fR... +A packet arrived from +.I address +(a network address \(en see above), but no peer is known at that +address. This may indicate a misconfiguration, or simply be a result of +one end of a connection being set up before the other. +.TP +.BI "PEER " peer " unexpected-transport-ping 0x" id +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. .SS "SERVER warnings" These indicate problems concerning the server process as a whole. .TP diff --git a/ethereal/packet-tripe.c b/ethereal/packet-tripe.c index aaad0ec9..44c74aa6 100644 --- a/ethereal/packet-tripe.c +++ b/ethereal/packet-tripe.c @@ -64,6 +64,8 @@ static int hf_tripe_ct_seq = -1; static int hf_tripe_ct_iv = -1; static int hf_tripe_ct_ct = -1; static int hf_tripe_ct_tag = -1; +static int hf_tripe_misc_type = -1; +static int hf_tripe_misc_payload = -1; static int hf_tripe_kx_type = -1; static hfge hf_tripe_kx_mychal = { -1, -1, -1, -1, -1, -1, -1, -1 }; static int hf_tripe_kx_mycookie = -1; @@ -178,6 +180,32 @@ static void dissect_tripe(tvbuff_t *b, packet_info *p, proto_tree *t) break; } break; + case MSG_MISC: + switch (ty & MSG_TYPEMASK) { + case MISC_NOP: + col_set_str(p->cinfo, COL_INFO, "Miscellaneous, no-operation"); + break; + case MISC_PING: + col_set_str(p->cinfo, COL_INFO, "Miscellaneous, transport ping"); + break; + case MISC_PONG: + col_set_str(p->cinfo, COL_INFO, + "Miscellaneous, transport ping reply"); + break; + case MISC_EPING: + col_set_str(p->cinfo, COL_INFO, "Miscellaneous, encrypted ping"); + break; + case MISC_EPONG: + col_set_str(p->cinfo, COL_INFO, + "Miscellaneous, encrypted ping reply"); + break; + default: + col_add_fstr(p->cinfo, COL_INFO, + "Miscellaneous, unknown type code %u", + ty & MSG_TYPEMASK); + break; + } + break; default: col_add_fstr(p->cinfo, COL_INFO, "Unknown category code %u, unknown type code %u", @@ -235,6 +263,22 @@ static void dissect_tripe(tvbuff_t *b, packet_info *p, proto_tree *t) goto huh; } break; + case MSG_MISC: + proto_tree_add_item(tt, hf_tripe_misc_type, b, 0, 1, FALSE); + switch (ty & MSG_TYPEMASK) { + case MISC_NOP: + case MISC_PING: + case MISC_PONG: + proto_tree_add_item(tt, hf_tripe_misc_payload, + b, off, -1, FALSE); + goto done; + case MISC_EPING: + case MISC_EPONG: + goto ct; + default: + goto huh; + } + break; default: goto huh; } @@ -316,6 +360,16 @@ void proto_register_tripe(void) FT_BYTES, BASE_NONE, 0, 0, "This is the message authentication code tag for the ciphertext." } }, + { &hf_tripe_misc_type, { + "Miscellaneous message type", "tripe.misc.type", + FT_UINT8, BASE_HEX, 0, MSG_TYPEMASK, + "This is the TrIPE miscellaneous message type subcode." + } }, + { &hf_tripe_misc_payload, { + "Miscellaneous message type", "tripe.misc.payload", + FT_BYTES, BASE_NONE, 0, 0, + "This is the miscellaneous message payload." + } }, { &hf_tripe_kx_type, { "Key-exchange message type", "tripe.kx.type", FT_UINT8, BASE_HEX, vs_kxtype, MSG_TYPEMASK, diff --git a/peer.c b/peer.c index 402d08b5..15a12b7e 100644 --- a/peer.c +++ b/peer.c @@ -53,6 +53,73 @@ const tunnel_ops *tunnels[] = { /*----- Main code ---------------------------------------------------------*/ +/* --- @p_pingtype@ --- * + * + * Arguments: @unsigned msg@ = message type + * + * Returns: String to describe the message. + */ + +static const char *p_pingtype(unsigned msg) +{ + switch (msg & MSG_TYPEMASK) { + case MISC_PING: + case MISC_PONG: + return "transport-ping"; + case MISC_EPING: + case MISC_EPONG: + return "encrypted-ping"; + default: + abort(); + } +} + +/* --- @p_ponged@ --- * + * + * Arguments: @peer *p@ = peer packet arrived from + * @unsigned msg@ = message type + * @buf *b@ = buffer containing payload + * + * Returns: --- + * + * Use: Processes a ping response. + */ + +static void p_ponged(peer *p, unsigned msg, buf *b) +{ + uint32 id; + const octet *magic; + ping *pg; + + IF_TRACING(T_PEER, { + trace(T_PEER, "peer: received %s reply from %s", + p_pingtype(msg), p->spec.name); + trace_block(T_PACKET, "peer: ping contents", BBASE(b), BSZ(b)); + }) + + if (buf_getu32(b, &id) || + (magic = buf_get(b, sizeof(pg->magic))) == 0 || + BLEFT(b)) { + a_warn("PEER %s malformed-%s", p->spec.name, p_pingtype(msg)); + return; + } + + for (pg = p->pings; pg; pg = pg->next) { + if (pg->id == id) + goto found; + } + a_warn("PEER %s unexpected-%s 0x%08lx", + p->spec.name, p_pingtype(msg), (unsigned long)id); + return; + +found: + if (memcmp(magic, pg->magic, sizeof(pg->magic)) != 0) { + a_warn("PEER %s corrupt-%s", p->spec.name, p_pingtype(msg)); + return; + } + p_pingdone(pg, PING_OK); +} + /* --- @p_read@ --- * * * Arguments: @int fd@ = file descriptor to read from @@ -87,8 +154,8 @@ static void p_read(int fd, unsigned mode, void *v) assert(a.sa.sa_family == AF_INET); for (p = peers; p; p = p->next) { - if (p->peer.sin.sin_addr.s_addr == a.sin.sin_addr.s_addr && - p->peer.sin.sin_port == a.sin.sin_port) + if (p->spec.sa.sin.sin_addr.s_addr == a.sin.sin_addr.s_addr && + p->spec.sa.sin.sin_port == a.sin.sin_port) goto found; } a_warn("PEER - unexpected-source INET %s %u", @@ -97,7 +164,7 @@ static void p_read(int fd, unsigned mode, void *v) found: IF_TRACING(T_PEER, { - trace(T_PEER, "peer: packet received from `%s'", p->name); + trace(T_PEER, "peer: packet received from `%s'", p->spec.name); trace_block(T_PACKET, "peer: packet contents", buf_i, n); }) @@ -108,20 +175,20 @@ found: p->st.sz_in += n; buf_init(&b, buf_i, n); if ((ch = buf_getbyte(&b)) < 0) { - a_warn("PEER %s bad-packet no-type", p->name); + a_warn("PEER %s bad-packet no-type", p->spec.name); return; } switch (ch & MSG_CATMASK) { case MSG_PACKET: if (ch & MSG_TYPEMASK) { - a_warn("PEER %s bad-packet unknown-type 0x%02x", p->name, ch); + a_warn("PEER %s bad-packet unknown-type 0x%02x", p->spec.name, ch); p->st.n_reject++; return; } buf_init(&bb, buf_o, sizeof(buf_o)); if (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) { p->st.n_reject++; - a_warn("PEER %s decrypt-failed", p->name); + a_warn("PEER %s decrypt-failed", p->spec.name); return; } if (BOK(&bb)) { @@ -130,15 +197,56 @@ found: p->t->ops->inject(p->t, &bb); } else { p->st.n_reject++; - a_warn("PEER %s packet-build-failed", p->name); + a_warn("PEER %s packet-build-failed", p->spec.name); } break; case MSG_KEYEXCH: kx_message(&p->kx, ch & MSG_TYPEMASK, &b); break; + case MSG_MISC: + switch (ch & MSG_TYPEMASK) { + case MISC_NOP: + T( trace(T_PEER, "peer: received NOP packet"); ) + break; + case MISC_PING: + buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b)); + p_txend(p); + break; + case MISC_PONG: + p_ponged(p, MISC_PONG, &b); + break; + case MISC_EPING: + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ksl_decrypt(&p->ks, ch, &b, &bb)) { + p->st.n_reject++; + a_warn("PEER %s decrypt-failed", p->spec.name); + return; + } + if (BOK(&bb)) { + buf_flip(&bb); + if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPONG, &bb, + p_txstart(p, MSG_MISC | MISC_EPONG))) + kx_start(&p->kx); + p_txend(p); + } + break; + case MISC_EPONG: + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ksl_decrypt(&p->ks, ch, &b, &bb)) { + p->st.n_reject++; + a_warn("PEER %s decrypt-failed", p->spec.name); + return; + } + if (BOK(&bb)) { + buf_flip(&bb); + p_ponged(p, MISC_EPONG, &bb); + } + break; + } + break; default: p->st.n_reject++; - a_warn("PEER %s bad-packet unknown-category 0x%02x", p->name, ch); + a_warn("PEER %s bad-packet unknown-category 0x%02x", p->spec.name, ch); break; } } @@ -170,23 +278,157 @@ buf *p_txstart(peer *p, unsigned msg) * Use: Sends a packet to the peer. */ -void p_txend(peer *p) +static void p_setkatimer(peer *); + +static int p_dotxend(peer *p) { if (!BOK(&p->b)) { - a_warn("PEER %s packet-build-failed", p->name); - return; + a_warn("PEER %s packet-build-failed", p->spec.name); + return (0); } IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", BBASE(&p->b), BLEN(&p->b)); ) if (sendto(sock.fd, BBASE(&p->b), BLEN(&p->b), - 0, &p->peer.sa, p->sasz) < 0) - a_warn("PEER %s socket-write-error -- %s", p->name, strerror(errno)); - else { + 0, &p->spec.sa.sa, p->spec.sasz) < 0) { + a_warn("PEER %s socket-write-error -- %s", + p->spec.name, strerror(errno)); + return (0); + } else { p->st.n_out++; p->st.sz_out += BLEN(&p->b); + return (1); } } +void p_txend(peer *p) +{ + if (p_dotxend(p) && p->spec.t_ka) { + sel_rmtimer(&p->tka); + p_setkatimer(p); + } +} + +/* --- @p_pingwrite@ --- * + * + * Arguments: @ping *p@ = ping structure + * @buf *b@ = buffer to write in + * + * Returns: --- + * + * Use: Fills in a ping structure and writes the packet payload. + */ + +static void p_pingwrite(ping *p, buf *b) +{ + static uint32 seq = 0; + + p->id = U32(seq++); + GR_FILL(&rand_global, p->magic, sizeof(p->magic)); + buf_putu32(b, p->id); + buf_put(b, p->magic, sizeof(p->magic)); +} + +/* --- @p_pingdone@ --- * + * + * Arguments: @ping *p@ = ping structure + * @int rc@ = return code to pass on + * + * Returns: --- + * + * Use: Disposes of a ping structure, maybe sending a notification. + */ + +void p_pingdone(ping *p, int rc) +{ + if (!p->p) return; + if (p->prev) p->prev->next = p->next; + else p->p->pings = p->next; + if (p->next) p->next->prev = p->prev; + if (rc != PING_TIMEOUT) sel_rmtimer(&p->t); + p->p = 0; + if (rc >= 0) p->func(rc, p->arg); +} + +/* --- @p_pingtimeout@ --- * + * + * Arguments: @struct timeval *now@ = the time now + * @void *pv@ = pointer to ping block + * + * Returns: --- + * + * Use: Called when a ping times out. + */ + +static void p_pingtimeout(struct timeval *now, void *pv) +{ + ping *p = pv; + + T( trace(T_PEER, "peer: ping 0x%08lx timed out", (unsigned long)p->id); ) + p_pingdone(p, PING_TIMEOUT); +} + +/* --- @p_pingsend@ --- * + * + * Arguments: @peer *p@ = destination peer + * @ping *pg@ = structure to fill in + * @unsigned type@ = message type + * @unsigned long timeout@ = how long to wait before giving up + * @void (*func)(int, void *)@ = callback function + * @void *arg@ = argument for callback + * + * Returns: Zero if successful, nonzero if it failed. + * + * Use: Sends a ping to a peer. Call @func@ with a nonzero argument + * if we get an answer within the timeout, or zero if no answer. + */ + +int p_pingsend(peer *p, ping *pg, unsigned type, + unsigned long timeout, + void (*func)(int, void *), void *arg) +{ + buf *b, bb; + struct timeval tv; + + assert(!pg->p); + + switch (type) { + case MISC_PING: + pg->msg = MISC_PONG; + b = p_txstart(p, MSG_MISC | MISC_PING); + p_pingwrite(pg, b); + p_txend(p); + break; + case MISC_EPING: + pg->msg = MISC_EPONG; + b = p_txstart(p, MSG_MISC | MISC_EPING); + buf_init(&bb, buf_t, sizeof(buf_t)); + p_pingwrite(pg, &bb); + buf_flip(&bb); + if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPING, &bb, b)) + kx_start(&p->kx); + if (!BOK(b)) + return (-1); + p_txend(p); + break; + default: + abort(); + break; + } + + pg->next = p->pings; + pg->prev = 0; + pg->p = p; + pg->func = func; + pg->arg = arg; + p->pings = pg; + gettimeofday(&tv, 0); + tv.tv_sec += timeout; + sel_addtimer(&sel, &pg->t, &tv, p_pingtimeout, pg); + T( trace(T_PEER, "peer: send %s 0x%08lx to %s", + p_pingtype(type), (unsigned long)pg->id, p->spec.name); ) + return (0); +} + /* --- @p_tun@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -259,7 +501,7 @@ const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); } * Returns: A pointer to the peer's address. */ -const addr *p_addr(peer *p) { return (&p->peer); } +const addr *p_addr(peer *p) { return (&p->spec.sa); } /* --- @p_init@ --- * * @@ -322,12 +564,47 @@ unsigned p_port(void) return (ntohs(a.sin.sin_port)); } +/* --- @p_keepalive@ --- * + * + * Arguments: @struct timeval *now@ = the current time + * @void *pv@ = peer to wake up + * + * Returns: --- + * + * Use: Sends a keepalive ping message to its peer. + */ + +static void p_keepalive(struct timeval *now, void *pv) +{ + peer *p = pv; + p_txstart(p, MSG_MISC | MISC_NOP); p_dotxend(p); + T( trace(T_PEER, "peer: sent keepalive to %s", p->spec.name); ) + p_setkatimer(p); +} + +/* --- @p_setkatimer@ --- * + * + * Arguments: @peer *p@ = peer to set + * + * Returns: --- + * + * Use: Resets the keepalive timer thing. + */ + +static void p_setkatimer(peer *p) +{ + struct timeval tv; + + if (!p->spec.t_ka) + return; + gettimeofday(&tv, 0); + tv.tv_sec += p->spec.t_ka; + sel_addtimer(&sel, &p->tka, &tv, p_keepalive, p); +} + /* --- @p_create@ --- * * - * Arguments: @const char *name@ = name for this peer - * @const tunnel_ops *tops@ = tunnel to use - * @struct sockaddr *sa@ = socket address of peer - * @size_t sz@ = size of socket address + * Arguments: @peerspec *spec@ = information about this peer * * Returns: Pointer to the peer block, or null if it failed. * @@ -335,46 +612,47 @@ unsigned p_port(void) * by this point. */ -peer *p_create(const char *name, const tunnel_ops *tops, - struct sockaddr *sa, size_t sz) +peer *p_create(peerspec *spec) { peer *p = CREATE(peer); - T( trace(T_PEER, "peer: creating new peer `%s'", name); ) - p->name = xstrdup(name); + T( trace(T_PEER, "peer: creating new peer `%s'", spec->name); ) + p->spec = *spec; + p->spec.name = xstrdup(spec->name); p->ks = 0; p->prev = 0; - memcpy(&p->peer.sa, sa, sz); - p->sasz = sz; memset(&p->st, 0, sizeof(stats)); p->st.t_start = time(0); - if (kx_init(&p->kx, p, &p->ks)) + if ((p->t = spec->tops->create(p)) == 0) goto tidy_0; - if ((p->t = tops->create(p)) == 0) + p_setkatimer(p); + if (kx_init(&p->kx, p, &p->ks)) goto tidy_1; p->next = peers; if (peers) peers->prev = p; peers = p; - switch (p->peer.sa.sa_family) { + switch (p->spec.sa.sa.sa_family) { case AF_INET: a_notify("ADD %s %s INET %s %u", - name, + spec->name, p->t->ops->ifname(p->t), - inet_ntoa(p->peer.sin.sin_addr), - (unsigned)ntohs(p->peer.sin.sin_port)); + inet_ntoa(p->spec.sa.sin.sin_addr), + (unsigned)ntohs(p->spec.sa.sin.sin_port)); break; default: - a_notify("ADD %s %s UNKNOWN", name, p->t->ops->ifname(p->t)); + a_notify("ADD %s %s UNKNOWN", spec->name, p->t->ops->ifname(p->t)); break; } - a_notify("KXSTART %s", name); /* Couldn't tell anyone before */ + a_notify("KXSTART %s", spec->name); /* Couldn't tell anyone before */ return (p); tidy_1: - kx_free(&p->kx); + if (spec->t_ka) + sel_rmtimer(&p->tka); + p->t->ops->destroy(p->t); tidy_0: - xfree(p->name); + xfree(p->spec.name); DESTROY(p); return (0); } @@ -386,7 +664,7 @@ tidy_0: * Returns: A pointer to the peer's name. */ -const char *p_name(peer *p) { return (p->name); } +const char *p_name(peer *p) { return (p->spec.name); } /* --- @p_find@ --- * * @@ -401,7 +679,7 @@ peer *p_find(const char *name) { peer *p; for (p = peers; p; p = p->next) { - if (strcmp(name, p->name) == 0) + if (strcmp(name, p->spec.name) == 0) return (p); } return (0); @@ -418,12 +696,20 @@ peer *p_find(const char *name) void p_destroy(peer *p) { - T( trace(T_PEER, "peer: destroying peer `%s'", p->name); ) - a_notify("KILL %s", p->name); + ping *pg, *ppg; + + T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); ) + a_notify("KILL %s", p->spec.name); ksl_free(&p->ks); kx_free(&p->kx); p->t->ops->destroy(p->t); - xfree(p->name); + if (p->spec.t_ka) + sel_rmtimer(&p->tka); + xfree(p->spec.name); + for (pg = p->pings; pg; pg = ppg) { + ppg = pg->next; + p_pingdone(pg, PING_PEERDIED); + } if (p->next) p->next->prev = p->prev; if (p->prev) diff --git a/tripe-protocol.h b/tripe-protocol.h index 1961785a..ebf7a71d 100644 --- a/tripe-protocol.h +++ b/tripe-protocol.h @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: tripe-protocol.h,v 1.2 2004/04/08 01:36:17 mdw Exp $ + * $Id$ * * Protocol definition for TrIPE * @@ -68,13 +68,23 @@ #define KX_SWITCHOK 5u #define KX_NMSG 6u +/* --- Miscellaneous packets --- */ + +#define MSG_MISC 0x20 + +#define MISC_NOP 0u /* Do nothing; ignore me */ +#define MISC_PING 1u /* Transport-level ping */ +#define MISC_PONG 2u /* Transport-level ping response */ +#define MISC_EPING 3u /* Encrypted ping */ +#define MISC_EPONG 4u /* Encrypted ping response */ + /* --- Symmetric encryption and keysets --- * * * Packets consist of an 80-bit MAC, a 32-bit sequence number, and the * encrypted payload. * * The plaintext is encrypted using Blowfish in CBC mode with ciphertext - * stealing (as described in [Schneier]. The initialization vector is + * stealing (as described in [Schneier]). The initialization vector is * selected randomly, and prepended to the actual ciphertext. * * The MAC is computed using the HMAC construction with RIPEMD160 over the diff --git a/tripe.h b/tripe.h index 9cf7abf3..002b3735 100644 --- a/tripe.h +++ b/tripe.h @@ -80,6 +80,7 @@ #include #include #include +#include #include @@ -292,18 +293,45 @@ typedef struct stats { * The main structure which glues everything else together. */ +typedef struct peerspec { + char *name; /* Peer's name */ + const tunnel_ops *tops; /* Tunnel operations */ + unsigned long t_ka; /* Keep alive interval */ + addr sa; /* Socket address to speak to */ + size_t sasz; /* Socket address size */ +} peerspec; + typedef struct peer { struct peer *next, *prev; /* Links to next and previous */ - char *name; /* Name of this peer */ + struct ping *pings; /* Pings we're waiting for */ + peerspec spec; /* Specifications for this peer */ tunnel *t; /* Tunnel for local packets */ keyset *ks; /* List head for keysets */ buf b; /* Buffer for sending packets */ - addr peer; /* Peer socket address */ - size_t sasz; /* Socket address size */ stats st; /* Statistics */ keyexch kx; /* Key exchange protocol block */ + sel_timer tka; /* Timer for keepalives */ } peer; +typedef struct ping { + struct ping *next, *prev; /* Links to next and previous */ + peer *p; /* Peer so we can free it */ + unsigned msg; /* Kind of response expected */ + uint32 id; /* Id so we can recognize response */ + octet magic[32]; /* Some random data */ + sel_timer t; /* Timeout for ping */ + void (*func)(int /*rc*/, void */*arg*/); /* Function to call when done */ + void *arg; /* Argument for callback */ +} ping; + +enum { + PING_NONOTIFY = -1, + PING_OK = 0, + PING_TIMEOUT, + PING_PEERDIED, + PING_MAX +}; + /* --- Admin structure --- */ #define OBUFSZ 16384u @@ -320,16 +348,15 @@ typedef struct admin { #ifndef NTRACE unsigned seq; /* Sequence number for tracing */ #endif - char *pname; /* Peer name to create */ - char *paddr; /* Address string to resolve */ obuf *o_head, *o_tail; /* Output buffer list */ selbuf b; /* Line buffer for commands */ sel_file w; /* Selector for write buffering */ + peerspec peer; /* Peer pending creation */ + char *paddr; /* Hostname to be resolved */ bres_client r; /* Background resolver task */ sel_timer t; /* Timer for resolver */ - const tunnel_ops *tops; /* Tunnel to use */ - addr peer; /* Address to set */ - size_t sasz; /* Size of the address */ + ping ping; /* Ping pending response */ + struct timeval pingtime; /* Time last ping was sent */ } admin; #define AF_DEAD 1u /* Destroy this admin block */ @@ -736,6 +763,37 @@ extern buf *p_txstart(peer */*p*/, unsigned /*msg*/); extern void p_txend(peer */*p*/); +/* --- @p_pingsend@ --- * + * + * Arguments: @peer *p@ = destination peer + * @ping *pg@ = structure to fill in + * @unsigned type@ = message type + * @unsigned long timeout@ = how long to wait before giving up + * @void (*func)(int, void *)@ = callback function + * @void *arg@ = argument for callback + * + * Returns: Zero if successful, nonzero if it failed. + * + * Use: Sends a ping to a peer. Call @func@ with a nonzero argument + * if we get an answer within the timeout, or zero if no answer. + */ + +extern int p_pingsend(peer */*p*/, ping */*pg*/, unsigned /*type*/, + unsigned long /*timeout*/, + void (*/*func*/)(int, void *), void */*arg*/); + +/* --- @p_pingdone@ --- * + * + * Arguments: @ping *p@ = ping structure + * @int rc@ = return code to pass on + * + * Returns: --- + * + * Use: Disposes of a ping structure, maybe sending a notification. + */ + +extern void p_pingdone(ping */*p*/, int /*rc*/); + /* --- @p_tun@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -809,10 +867,7 @@ unsigned p_port(void); /* --- @p_create@ --- * * - * Arguments: @const char *name@ = name for this peer - * @const tunnel_ops *tops@ = tunnel to use - * @struct sockaddr *sa@ = socket address of peer - * @size_t sz@ = size of socket address + * Arguments: @peerspec *spec@ = information about this peer * * Returns: Pointer to the peer block, or null if it failed. * @@ -820,8 +875,7 @@ unsigned p_port(void); * by this point. */ -extern peer *p_create(const char */*name*/, const tunnel_ops */*tops*/, - struct sockaddr */*sa*/, size_t /*sz*/); +extern peer *p_create(peerspec */*spec*/); /* --- @p_name@ --- * * -- [mdw]