From: mdw Date: Fri, 30 Sep 2005 14:41:12 +0000 (+0000) Subject: Backgrounded commands and RELOAD. X-Git-Tag: 1.0.0pre8~118 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/commitdiff_plain/de014da64011b21929158b746803d69cbfb05ee6 Backgrounded commands and RELOAD. --- diff --git a/admin.c b/admin.c index e081bdf6..92ea0a64 100644 --- a/admin.c +++ b/admin.c @@ -116,41 +116,26 @@ again: return (done); } -/* --- @dosend@ --- * +/* --- @doqueue@ -- * * - * Arguemnts: @admin *a@ = pointer to an admin block + * Arguments: @oqueue *q@ = pointer to output queue * @const char *p@ = pointer to buffer to write - * @size_t sz@ = size of data to write + * @size_t sz@ = size of buffer * - * Returns: --- + * Returns: Nonzero if the queue was previously empty. * - * Use: Sends data to an admin client. + * Use: Queues data to be written later. */ -static void dosend(admin *a, const char *p, size_t sz) +static int doqueue(oqueue *q, const char *p, size_t sz) { - ssize_t n; obuf *o; + int rc = 0; + size_t n; - if (a->f & AF_DEAD) - return; - - /* --- Try to send the data immediately --- */ - - if (!a->o_head) { - if ((n = trywrite(a, p, sz)) < 0) - return; - p += n; - sz -= n; - if (!sz) - return; - } - - /* --- Fill buffers with the data until it's all gone --- */ - - o = a->o_tail; + o = q->tl; if (!o) - sel_addfile(&a->w); + rc = 1; else if (o->p_in < o->buf + OBUFSZ) goto noalloc; @@ -158,11 +143,11 @@ static void dosend(admin *a, const char *p, size_t sz) o = xmalloc(sizeof(obuf)); o->next = 0; o->p_in = o->p_out = o->buf; - if (a->o_tail) - a->o_tail->next = o; + if (q->tl) + q->tl->next = o; else - a->o_head = o; - a->o_tail = o; + q->hd = o; + q->tl = o; noalloc: n = o->buf + OBUFSZ - o->p_in; @@ -173,6 +158,37 @@ static void dosend(admin *a, const char *p, size_t sz) p += n; sz -= n; } while (sz); + + return (rc); +} + +/* --- @dosend@ --- * + * + * Arguemnts: @admin *a@ = pointer to an admin block + * @const char *p@ = pointer to buffer to write + * @size_t sz@ = size of data to write + * + * Returns: --- + * + * Use: Sends data to an admin client. + */ + +static void dosend(admin *a, const char *p, size_t sz) +{ + ssize_t n; + + if (a->f & AF_DEAD) + return; + if (!a->out.hd) { + if ((n = trywrite(a, p, sz)) < 0) + return; + p += n; + sz -= n; + if (!sz) + return; + } + if (doqueue(&a->out, p, sz)) + sel_addfile(&a->w); } /* --- @a_flush@ --- * @@ -192,7 +208,7 @@ static void a_flush(int fd, unsigned mode, void *v) obuf *o, *oo; ssize_t n; - o = a->o_head; + o = a->out.hd; while (o) { if ((n = trywrite(a, o->p_out, o->p_in - o->p_out)) < 0) return; @@ -203,9 +219,9 @@ static void a_flush(int fd, unsigned mode, void *v) o = o->next; xfree(oo); } - a->o_head = o; + a->out.hd = o; if (!o) { - a->o_tail = 0; + a->out.tl = 0; sel_rmfile(&a->w); } } @@ -215,7 +231,8 @@ static void a_flush(int fd, unsigned mode, void *v) /* --- @a_write@, @a_vwrite@ --- * * * Arguments: @admin *a@ = admin connection to write to - * @const char *tag@ = tag prefix string, or null + * @const char *status@ = status code to report + * @const char *tag@ = tag string, or null * @const char *fmt@ = pointer to format string * @va_list ap@ = arguments in list * @...@ = other arguments @@ -225,26 +242,32 @@ static void a_flush(int fd, unsigned mode, void *v) * Use: Sends a message to an admin connection. */ -static void a_vwrite(admin *a, const char *tag, const char *fmt, va_list ap) +static void a_vwrite(admin *a, const char *status, const char *tag, + const char *fmt, va_list ap) { dstr d = DSTR_INIT; + + if (tag) dstr_puts(&d, "BG"); + dstr_puts(&d, status); if (tag) { + dstr_putc(&d, ' '); dstr_puts(&d, tag); - if (fmt) - dstr_putc(&d, ' '); } - if (fmt) + if (fmt) { + dstr_putc(&d, ' '); dstr_vputf(&d, fmt, &ap); + } dstr_putc(&d, '\n'); dosend(a, d.buf, d.len); dstr_destroy(&d); } -static void a_write(admin *a, const char *tag, const char *fmt, ...) +static void a_write(admin *a, const char *status, const char *tag, + const char *fmt, ...) { va_list ap; va_start(ap, fmt); - a_vwrite(a, tag, fmt, ap); + a_vwrite(a, status, tag, fmt, ap); va_end(ap); } @@ -259,13 +282,13 @@ static void a_write(admin *a, const char *tag, const char *fmt, ...) * Use: Convenience functions for @a_write@. */ -static void a_ok(admin *a) { a_write(a, "OK", 0); } +static void a_ok(admin *a) { a_write(a, "OK", 0, 0); } static void a_info(admin *a, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - a_vwrite(a, "INFO", fmt, ap); + a_vwrite(a, "INFO", 0, fmt, ap); va_end(ap); } @@ -273,16 +296,16 @@ static void a_fail(admin *a, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - a_vwrite(a, "FAIL", fmt, ap); + a_vwrite(a, "FAIL", 0, fmt, ap); va_end(ap); } /* --- @a_alert@, @a_valert@, @a_rawalert@ --- * * * Arguments: @unsigned f_and, f_eq@ = filter for connections - * @const char *tag@ = tag prefix string + * @const char *status@ = status string * @const char *fmt@ = pointer to format string - @ @const char *p@ = pointer to raw string + * @const char *p@ = pointer to raw string * @size_t sz@ = size of raw string * @va_list ap@ = arguments in list * @...@ = other arguments @@ -293,7 +316,7 @@ static void a_fail(admin *a, const char *fmt, ...) * filter. */ -static void a_rawalert(unsigned f_and, unsigned f_eq, const char *tag, +static void a_rawalert(unsigned f_and, unsigned f_eq, const char *status, const char *p, size_t sz) { admin *a, *aa; @@ -301,13 +324,11 @@ static void a_rawalert(unsigned f_and, unsigned f_eq, const char *tag, if (!(flags & F_INIT)) return; - if (tag) { - dstr_puts(&d, tag); - if (p) - dstr_putc(&d, ' '); - } - if (p) + dstr_puts(&d, status); + if (p) { + dstr_putc(&d, ' '); dstr_putm(&d, p, sz); + } dstr_putc(&d, '\n'); p = d.buf; sz = d.len; @@ -382,9 +403,7 @@ void a_warn(const char *fmt, ...) #ifndef NTRACE static void a_trace(const char *p, size_t sz, void *v) -{ - a_rawalert(AF_TRACE, AF_TRACE, "TRACE", p, sz); -} + { a_rawalert(AF_TRACE, AF_TRACE, "TRACE", p, sz); } #endif /* --- @a_notify@ --- * @@ -419,10 +438,10 @@ void a_quit(void) { peer *p; - while ((p = p_first()) != 0) - p_destroy(p); close(sock.fd); unlink(sockname); + while ((p = p_first()) != 0) + p_destroy(p); exit(0); } @@ -491,67 +510,200 @@ static long a_parsetime(const char *p) return (t); } +/*----- Backgrounded operations -------------------------------------------*/ + +/* --- @a_bgrelease@ --- * + * + * Arguments: @admin_bgop *bg@ = backgrounded operation + * + * Returns: --- + * + * Use: Removes a backgrounded operation from the queue, since + * (presumably) it's done. + */ + +static void a_bgrelease(admin_bgop *bg) +{ + admin *a = bg->a; + + if (bg->tag) + xfree(bg->tag); + else + selbuf_enable(&a->b); + if (bg->next) + bg->next->prev = bg->prev; + if (bg->prev) + bg->prev->next = bg->next; + else + a->bg = bg->next; + xfree(bg); + if (a->f & AF_CLOSE) + a_destroy(a); +} + +/* --- @a_bgok@, @a_bginfo@, @a_bgfail@ --- * + * + * Arguments: @admin_bgop *bg@ = backgrounded operation + * @const char *fmt@ = format string + * @...@ = other arguments + * + * Returns: --- + * + * Use: Convenience functions for @a_write@. + */ + +static void a_bgok(admin_bgop *bg) + { a_write(bg->a, "OK", bg->tag, 0); } + +static void a_bginfo(admin_bgop *bg, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + a_vwrite(bg->a, "INFO", bg->tag, fmt, ap); + va_end(ap); +} + +static void a_bgfail(admin_bgop *bg, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + a_vwrite(bg->a, "FAIL", bg->tag, fmt, ap); + va_end(ap); +} + +/* --- @a_bgadd@ --- * + * + * Arguments: @admin *a@ = administration connection + * @admin_bgop *bg@ = pointer to background operation + * @const char *tag@ = background tag, or null for foreground + * @void (*cancel)(admin_bgop *)@ = cancel function + * + * Returns: --- + * + * Use: Links a background job into the list. + */ + +static void a_bgadd(admin *a, admin_bgop *bg, const char *tag, + void (*cancel)(admin_bgop *)) +{ + if (tag) + bg->tag = xstrdup(tag); + else { + bg->tag = 0; + selbuf_disable(&a->b); + } + bg->a = a; + bg->cancel = cancel; + bg->next = a->bg; + bg->prev = 0; + a->bg = bg; + if (tag) a_write(a, "DETACH", tag, 0); +} + /*----- Adding peers ------------------------------------------------------*/ + +/* --- @a_addfree@ --- * + * + * Arguments: @admin_addop *add@ = operation block + * + * Returns: --- + * + * Use: Frees an add operation. + */ + +static void a_addfree(admin_addop *add) +{ + if (add->peer.name) xfree(add->peer.name); + if (add->paddr) xfree(add->paddr); +} + +/* --- @a_addcancel@ --- * + * + * Arguments: @admin_bgop *bg@ = background operation + * + * Returns: --- + * + * Use: Cancels an add operation. + */ + +static void a_addcancel(admin_bgop *bg) +{ + admin_addop *add = (admin_addop *)bg; + + sel_rmtimer(&add->t); + bres_abort(&add->r); + a_addfree(add); +} + +/* --- @a_doadd@ --- * + * + * Arguments: @admin_addop *add@ = operation block + * + * Returns: --- + * + * Use: Does the peer add thing. + */ + +static void a_doadd(admin_addop *add) +{ + if (p_find(add->peer.name)) + a_bgfail(&add->bg, "peer-exists %s", add->peer.name); + else if (!p_create(&add->peer)) + a_bgfail(&add->bg, "peer-create-fail %s", add->peer.name); + else + a_bgok(&add->bg); +} -/* --- @a_resolve@ --- * +/* --- @a_addresolve@ --- * * * Arguments: @struct hostent *h@ = pointer to resolved hostname - * @void *v@ = pointer to admin block + * @void *v@ = pointer to add operation * * Returns: --- * * Use: Handles a completed name resolution. */ -static void a_resolve(struct hostent *h, void *v) +static void a_addresolve(struct hostent *h, void *v) { - admin *a = v; + admin_addop *add = v; - a_lock(a); - T( trace(T_ADMIN, "admin: %u resolved", a->seq); ) + a_lock(add->bg.a); + T( trace(T_ADMIN, "admin: %u resolved", add->bg.a->seq); ) TIMER; - sel_rmtimer(&a->t); if (!h) - a_fail(a, "resolve-error %s", a->paddr); - else if (p_find(a->peer.name)) - a_fail(a, "peer-exists %s", a->peer.name); + a_bgfail(&add->bg, "resolve-error %s", add->paddr); else { - 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); + memcpy(&add->peer.sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr)); + a_doadd(add); } - xfree(a->peer.name); - xfree(a->paddr); - a->peer.name = 0; - selbuf_enable(&a->b); - a_unlock(a); + sel_rmtimer(&add->t); + a_addfree(add); + a_bgrelease(&add->bg); + a_unlock(add->bg.a); } -/* --- @a_timer@ --- * +/* --- @a_addtimer@ --- * * * Arguments: @struct timeval *tv@ = timer - * @void *v@ = pointer to admin block + * @void *v@ = pointer to add operation * * Returns: --- * * Use: Times out a resolver. */ -static void a_timer(struct timeval *tv, void *v) +static void a_addtimer(struct timeval *tv, void *v) { - admin *a = v; - - a_lock(a); - 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->peer.name); - xfree(a->paddr); - a->peer.name = 0; - selbuf_enable(&a->b); - a_unlock(a); + admin_addop *add = v; + + a_lock(add->bg.a); + T( trace(T_ADMIN, "admin: %u resolver timeout", add->bg.a->seq); ) + a_bgfail(&add->bg, "resolver-timeout %s\n", add->paddr); + bres_abort(&add->r); + a_addfree(add); + a_bgrelease(&add->bg); + a_unlock(add->bg.a); } /* --- @acmd_add@ --- * @@ -571,19 +723,23 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) struct timeval tv; unsigned i, j; char *p; + const char *tag = 0; + admin_addop *add = 0; /* --- Make sure someone's not got there already --- */ if (p_find(av[0])) { a_fail(a, "peer-exists %s", av[0]); - return; + goto fail; } /* --- Set stuff up --- */ - a->peer.name = av[0]; - a->peer.t_ka = 0; - a->peer.tops = tun_default; + add = xmalloc(sizeof(*add)); + add->peer.name = xstrdup(av[0]); + add->peer.t_ka = 0; + add->peer.tops = tun_default; + add->paddr = 0; /* --- Parse options --- */ @@ -591,7 +747,10 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) for (;;) { if (!av[i]) goto bad_syntax; - if (mystrieq(av[i], "-tunnel")) { + if (mystrieq(av[i], "-background")) { + if (!av[++i]) goto bad_syntax; + tag = av[i]; + } else if (mystrieq(av[i], "-tunnel")) { if (!av[++i]) goto bad_syntax; for (j = 0;; j++) { if (!tunnels[j]) { @@ -599,7 +758,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) return; } if (mystrieq(av[i], tunnels[j]->name)) { - a->peer.tops = tunnels[j]; + add->peer.tops = tunnels[j]; break; } } @@ -610,7 +769,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) a_fail(a, "bad-time-spec %s", av[i]); return; } - a->peer.t_ka = t; + add->peer.t_ka = t; } else if (mystrieq(av[i], "--")) { i++; break; @@ -624,98 +783,118 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) if (mystrieq(av[i], "inet")) i++; if (ac - i != 2) { a_fail(a, "bad-syntax -- add PEER [OPTIONS] [inet] ADDRESS PORT"); - return; + goto fail; } - a->peer.sa.sin.sin_family = AF_INET; - a->peer.sasz = sizeof(a->peer.sa.sin); + add->peer.sa.sin.sin_family = AF_INET; + add->peer.sasz = sizeof(add->peer.sa.sin); + add->paddr = xstrdup(av[i]); pt = strtoul(av[i + 1], &p, 0); if (*p) { struct servent *s = getservbyname(av[i + 1], "udp"); if (!s) { a_fail(a, "unknown-service %s", av[i + 1]); - return; + goto fail; } pt = ntohs(s->s_port); } if (pt == 0 || pt >= 65536) { a_fail(a, "invalid-port %lu", pt); - return; + goto fail; } - a->peer.sa.sin.sin_port = htons(pt); + add->peer.sa.sin.sin_port = htons(pt); + + /* --- Report backgrounding --- * + * + * Do this for consistency of interface, even if we're going to get the + * answer straight away. + */ + + a_bgadd(a, &add->bg, tag, a_addcancel); /* --- If the name is numeric, do it the easy way --- */ - 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; + if (inet_aton(av[i], &add->peer.sa.sin.sin_addr)) { + a_doadd(add); + a_addfree(add); + a_bgrelease(&add->bg); return; } - /* --- Store everything for later and crank up the resolver --- * - * - * We disable the line buffer until the resolver completes (or times out). - * This prevents other commands on the same connection (though the rest of - * the system continues regardless), but makes life simpler for the client. - */ + /* --- Store everything for later and crank up the resolver --- */ - a->peer.name = xstrdup(av[0]); - a->paddr = xstrdup(av[i]); - selbuf_disable(&a->b); gettimeofday(&tv, 0); tv.tv_sec += T_RESOLVE; - sel_addtimer(&sel, &a->t, &tv, a_timer, a); - bres_byname(&a->r, a->paddr, a_resolve, a); + sel_addtimer(&sel, &add->t, &tv, a_addtimer, add); + bres_byname(&add->r, add->paddr, a_addresolve, add); T( trace(T_ADMIN, "admin: %u resolving hostname `%s'", - a->seq, a->paddr); ) + a->seq, add->paddr); ) return; bad_syntax: a_fail(a, "bad-syntax -- add PEER [OPTIONS] ADDR ..."); +fail: + if (add) { + a_addfree(add); + xfree(add); + } return; } /*----- Ping --------------------------------------------------------------*/ +/* --- @a_pingcancel@ --- * + * + * Arguments: @admin_bgop *bg@ = background operation block + * + * Returns: --- + * + * Use: Cancels a running ping. + */ + +static void a_pingcancel(admin_bgop *bg) +{ + admin_pingop *pg = (admin_pingop *)bg; + p_pingdone(&pg->ping, PING_NONOTIFY); +} + /* --- @a_pong@ --- * * * Arguments: @int rc@ = return code - * @void *av@ = admin connection which requested the ping + * @void *v@ = ping operation block * * Returns: --- * * Use: Collects what happened to a ping message. */ -static void a_pong(int rc, void *av) +static void a_pong(int rc, void *v) { - admin *a = av; + admin_pingop *pg = v; struct timeval tv; double millis; - a_lock(a); + a_lock(pg->bg.a); switch (rc) { case PING_OK: gettimeofday(&tv, 0); - tv_sub(&tv, &tv, &a->pingtime); + tv_sub(&tv, &tv, &pg->pingtime); millis = (double)tv.tv_sec * 1000 + (double)tv.tv_usec/1000; - a_info(a, "ping-ok %.1f", millis); - a_ok(a); + a_bginfo(&pg->bg, "ping-ok %.1f", millis); + a_bgok(&pg->bg); break; case PING_TIMEOUT: - a_info(a, "ping-timeout"); - a_ok(a); + a_bginfo(&pg->bg, "ping-timeout"); + a_bgok(&pg->bg); break; case PING_PEERDIED: - a_info(a, "ping-peer-died"); - a_ok(a); + a_bginfo(&pg->bg, "ping-peer-died"); + a_bgok(&pg->bg); break; default: abort(); } - a_unlock(a); + a_bgrelease(&pg->bg); + a_unlock(pg->bg.a); } /* --- @acmd_ping@, @acmd_eping@ --- * @@ -735,12 +914,17 @@ static void a_ping(admin *a, unsigned ac, char *av[], long t = T_PING; int i; peer *p; + admin_pingop *pg = 0; + const char *tag = 0; i = 0; for (;;) { if (!av[i]) goto bad_syntax; - if (mystrieq(av[i], "-timeout")) { + if (mystrieq(av[i], "-background")) { + if (!av[++i]) goto bad_syntax; + tag = av[i]; + } else 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]); @@ -759,9 +943,13 @@ static void a_ping(admin *a, unsigned ac, char *av[], 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"); + pg = xmalloc(sizeof(*pg)); + gettimeofday(&pg->pingtime, 0); + a_bgadd(a, &pg->bg, tag, a_pingcancel); + if (p_pingsend(p, &pg->ping, msg, t, a_pong, pg)) { + a_bgfail(&pg->bg, "ping-send-failed"); + a_bgrelease(&pg->bg); + } return; bad_syntax: @@ -773,7 +961,6 @@ 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 -------------------------------------------*/ @@ -994,15 +1181,19 @@ static void acmd_forcekx(admin *a, unsigned ac, char *av[]) if ((p = p_find(av[0])) == 0) a_fail(a, "unknown-peer %s", av[0]); else { - kx_start(&p->kx); + kx_start(&p->kx, 1); a_ok(a); } } +static void acmd_reload(admin *a, unsigned ac, char *av[]) + { p_keyreload(); a_ok(a); } + static void acmd_quit(admin *a, unsigned ac, char *av[]) { a_warn("SERVER quit admin-request"); a_ok(a); + a_unlock(a); a_quit(); } @@ -1032,28 +1223,29 @@ typedef struct acmd { static void acmd_help(admin */*a*/, unsigned /*ac*/, char */*av*/[]); static const acmd acmdtab[] = { + { "add", "add PEER [OPTIONS] ADDR ...", + 2, 0xffff, acmd_add }, + { "addr", "addr PEER", 1, 1, acmd_addr }, + { "daemon", "daemon", 0, 0, acmd_daemon }, + { "eping", "eping [OPTIONS] PEER", 1, 0xffff, acmd_eping }, + { "forcekx", "forcekx PEER", 1, 1, acmd_forcekx }, { "help", "help", 0, 0, acmd_help }, - { "version", "version", 0, 0, acmd_version }, -#ifndef NTRACE - { "trace", "trace [OPTIONS]", 0, 1, acmd_trace }, -#endif - { "watch", "watch [OPTIONS]", 0, 1, acmd_watch }, + { "ifname", "ifname PEER", 1, 1, acmd_ifname }, + { "kill", "kill PEER", 1, 1, acmd_kill }, + { "list", "list", 0, 0, acmd_list }, { "notify", "notify MESSAGE ...", 1, 0xffff, acmd_notify }, - { "warn", "warn MESSAGE ...", 1, 0xffff, acmd_warn }, + { "ping", "ping [OPTIONS] PEER", 1, 0xffff, acmd_ping }, { "port", "port", 0, 0, acmd_port }, - { "daemon", "daemon", 0, 0, acmd_daemon }, - { "list", "list", 0, 0, acmd_list }, - { "ifname", "ifname PEER", 1, 1, acmd_ifname }, - { "addr", "addr PEER", 1, 1, acmd_addr }, + { "quit", "quit", 0, 0, acmd_quit }, + { "reload", "reload", 0, 0, acmd_reload }, { "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 }, - { "forcekx", "forcekx PEER", 1, 1, acmd_forcekx }, - { "add", "add PEER [OPTIONS] ADDR ...", - 2, 0xffff, acmd_add }, +#ifndef NTRACE + { "trace", "trace [OPTIONS]", 0, 1, acmd_trace }, +#endif { "tunnels", "tunnels", 0, 0, acmd_tunnels }, - { "quit", "quit", 0, 0, acmd_quit }, + { "version", "version", 0, 0, acmd_version }, + { "warn", "warn MESSAGE ...", 1, 0xffff, acmd_warn }, + { "watch", "watch [OPTIONS]", 0, 1, acmd_watch }, { 0, 0, 0, 0, 0 } }; @@ -1091,26 +1283,30 @@ static void a_lock(admin *a) { assert(!(a->f & AF_LOCK)); a->f |= AF_LOCK; } static void a_unlock(admin *a) { + admin_bgop *bg, *bbg; + assert(a->f & AF_LOCK); + + /* --- If we're not dead, that's fine --- */ + if (!(a->f & AF_DEAD)) { a->f &= ~AF_LOCK; return; } + /* --- If we are, then destroy the rest of the block --- */ + T( trace(T_ADMIN, "admin: completing destruction of connection %u", a->seq); ) selbuf_destroy(&a->b); - if (a->peer.name) { - xfree(a->peer.name); - xfree(a->paddr); - bres_abort(&a->r); - sel_rmtimer(&a->t); + for (bg = a->bg; bg; bg = bbg) { + bbg = bg->next; + bg->cancel(bg); + if (bg->tag) xfree(bg->tag); + xfree(bg); } - if (a->ping.p) - p_pingdone(&a->ping, PING_NONOTIFY); - if (a->b.reader.fd != a->w.fd) - close(a->b.reader.fd); + if (a->b.reader.fd != a->w.fd) close(a->b.reader.fd); close(a->w.fd); if (a_stdin == a) @@ -1134,6 +1330,17 @@ static void a_unlock(admin *a) * care. */ +static void freequeue(oqueue *q) +{ + obuf *o, *oo; + + for (o = q->hd; o; o = oo) { + oo = o->next; + xfree(o); + } + q->hd = q->tl = 0; +} + static void a_destroy(admin *a) { /* --- Don't multiply destroy admin blocks --- */ @@ -1148,15 +1355,9 @@ static void a_destroy(admin *a) /* --- Free the output buffers --- */ - if (a->o_head) { - obuf *o, *oo; + if (a->out.hd) sel_rmfile(&a->w); - for (o = a->o_head; o; o = oo) { - oo = o->next; - xfree(o); - } - a->o_head = 0; - } + freequeue(&a->out); /* --- If the block is locked, that's all we can manage --- */ @@ -1190,7 +1391,12 @@ static void a_line(char *p, size_t len, void *vp) if (a->f & AF_DEAD) return; if (!p) { - a_destroy(a); + if (!a->bg) + a_destroy(a); + else { + a->f |= AF_CLOSE; + selbuf_disable(&a->b); + } return; } ac = str_qsplit(p, av, 16, 0, STRF_QUOTE); @@ -1229,22 +1435,18 @@ 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->peer.name = 0; - a->ping.p = 0; + a->bg = 0; a->f = f; - if (fd_in == STDIN_FILENO) - a_stdin = a; + if (fd_in == STDIN_FILENO) a_stdin = a; fdflags(fd_in, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); if (fd_out != fd_in) fdflags(fd_out, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); selbuf_init(&a->b, &sel, fd_in, a_line, a); sel_initfile(&sel, &a->w, fd_out, SEL_WRITE, a_flush, a); - a->o_head = 0; - a->o_tail = 0; + a->out.hd = a->out.tl = 0; a->next = admins; a->prev = 0; - if (admins) - admins->prev = a; + if (admins) admins->prev = a; admins = a; } diff --git a/client.c b/client.c index 314bb87c..4aece8d1 100644 --- a/client.c +++ b/client.c @@ -79,6 +79,7 @@ static const char *logname = 0; static FILE *logfp = 0; static unsigned f = 0; static int fd; +static const char *bgtag = 0; #define f_bogus 1u #define f_spawn 2u @@ -109,6 +110,21 @@ static void writelog(const char *cat, const char *msg) fprintf(logfp, "%s %s: %s\n", buf, cat, msg); } +static void checkbg(char **p) +{ + char *q = str_getword(p); + if (!q) + die(EXIT_FAILURE, "missing background tag"); + if (!bgtag || strcmp(bgtag, q) != 0) + die(EXIT_FAILURE, "unexpected background tag `%s'", q); +} + +static void checkfg(void) +{ + if (bgtag) + die(EXIT_FAILURE, "unexpected foreground response"); +} + static void cline(char *p, size_t len, void *b) { char *q; @@ -141,13 +157,29 @@ static void cline(char *p, size_t len, void *b) writelog("error", d.buf); dstr_destroy(&d); } - } else if (strcmp(q, "FAIL") == 0) + } else if (strcmp(q, "FAIL") == 0) { + checkfg(); die(EXIT_FAILURE, "%s", p); - else if (strcmp(q, "INFO") == 0) + } else if (strcmp(q, "INFO") == 0) { + checkfg(); puts(p); - else if (strcmp(q, "OK") == 0) + } else if (strcmp(q, "OK") == 0) { + checkfg(); + exit(0); + } else if (strcmp(q, "BGDETACH") == 0) { + if (bgtag) + die(EXIT_FAILURE, "repeat detach"); + bgtag = xstrdup(p); + } else if (strcmp(q, "BGOK") == 0) { + checkbg(&p); exit(0); - else + } else if (strcmp(q, "BGINFO") == 0) { + checkbg(&p); + puts(p); + } else if (strcmp(q, "BGFAIL") == 0) { + checkbg(&p); + die(EXIT_FAILURE, "%s", p); + } else die(EXIT_FAILURE, "unexpected output `%s %s'", q, p); } diff --git a/doc/tripe-admin.5 b/doc/tripe-admin.5 index 327d1b56..b4d51b5c 100644 --- a/doc/tripe-admin.5 +++ b/doc/tripe-admin.5 @@ -32,8 +32,8 @@ line is a identifying the type of command or response contained. Keywords in client commands are not case-sensitive; the server always uses uppercase for its keywords. -.SS "Server responses" -For client command, the server responds with zero or more +.SS "Simple commands" +For simple client command, the server responds with zero or more .B INFO lines, followed by either an .B OK @@ -48,13 +48,27 @@ response contains no further data. A code is followed by a machine-readable explanation of why the command failed. .PP -In addition, there are three types of asynchronous messages which -aren't associated with any particular command. The +Simple command processing is strictly synchronous: the server reads a +command, processes it, and responds, before reading the next command. +All commands can be run as simple commands. Long-running commands +(e.g., +.B ADD +and +.BR PING ) +block the client until they finish, but the rest of the server continues +running. +.SS "Asynchronous messages" +There are three types of asynchronous messages which +aren't associated with any particular command. +.PP +The .B WARN message contains a machine-readable message warning of an error encountered while processing a command, unexpected or unusual behaviour by a peer, or a possible attack by an adversary. Under normal -conditions, the server shouldn't emit any warnings. The +conditions, the server shouldn't emit any warnings. +.PP +The .B TRACE message contains a human-readable tracing message containing diagnostic information. Trace messages are controlled using the @@ -63,7 +77,9 @@ command-line option to the server, or the .B TRACE administration command (see below). Support for tracing can be disabled when the package is being configured, and may not be available in your -version. Finally, the +version. +.PP +Finally, the .B NOTE message is a machine-readable notification about some routine but interesting event such as creation or destruction of peers. @@ -71,6 +87,56 @@ interesting event such as creation or destruction of peers. The presence of asynchronous messages can be controlled using the .B WATCH command. +.SS "Background commands" +Some commands (e.g., +.B ADD +and +.BR PING ) +take a long time to complete. To prevent these long-running commands +from tying up a server connection, they can be run in the background. +Not all commands can be run like this: the ones that can provide a +.B \-background +option, which must be supplied with a +.IR tag . +.PP +A command may fail before it starts running in the background. In this +case, the server emits a +.B FAIL +response, as usual. To indicate that a command has started running in +the background, the server emits a response of the form +.BI "BGDETACH " tag \fR, +where +.I tag +is the value passed to the +.B \-background +option. From this point on, the server is ready to process more +commands and reply to them. +.PP +Responses to background commands are indicated by a line beginning with +one of the tokens +.BR BGOK , +.BR BGFAIL , +or +.BR BGINFO , +followed by the command tag. These correspond to the +.BR OK , +.BR FAIL , +and +.B INFO +responses for simple commands: +.B BGINFO +indicates information from a background command which has not completed +yet; and +.B BGOK +and +.B BGFAIL +indicates that a background command succeeded or failed, respectively. +.PP +A background command will never issue an +.B OK +response: it will always detach and then issue a +.B BGOK +response. .SS "Network addresses" A network address is a sequence of words. The first is a token identifying the network address family. The length of an address and @@ -109,6 +175,10 @@ is the network address (see above for the format) at which the peer can be contacted. The following options are recognised. .RS .TP +.BI "\-background " tag +Run the command in the background, using the given +.IR tag . +.TP .BI "\-keepalive " time Send a no-op packet if we've not sent a packet to the peer in the last .I time @@ -146,6 +216,11 @@ responses are the same as for the .B PING command. .TP +.BI "FORCEKX " peer +Requests the server to begin a new key exchange with +.I peer +immediately. +.TP .B "HELP" Causes the server to emit an .B INFO @@ -209,6 +284,10 @@ response was received. Options recognized for this command are: .RS .TP +.BI "\-background " tag +Run the command in the background, using the given +.IR tag . +.TP .BI "\-timeout " time Wait for .I time @@ -226,6 +305,11 @@ line containing just the number of the UDP port used by the server. If you've allowed your server to allocate a port dynamically, this is how to find out which one it chose. .TP +.B "RELOAD" +Instructs the server to recheck its keyring files. The server checks +these periodically anyway but it may be necessary to force a recheck, +for example after adding a new peer key. +.TP .B "QUIT" Instructs the server to exit immediately. A warning is sent. .TP @@ -363,6 +447,8 @@ warning to all interested administration clients. .SH "ERROR MESSAGES" The following .B FAIL +(or +.BR BGFAIL ) messages are sent to clients as a result of errors during command processing. .TP diff --git a/keyexch.c b/keyexch.c index f010a38b..e62809ef 100644 --- a/keyexch.c +++ b/keyexch.c @@ -168,7 +168,7 @@ static void timer(struct timeval *tv, void *v) keyexch *kx = v; kx->f &= ~KXF_TIMER; T( trace(T_KEYEXCH, "keyexch: timer has popped"); ) - kx_start(kx); + kx_start(kx, 0); } /* --- @settimer@ --- * @@ -1070,6 +1070,7 @@ static int checkpub(keyexch *kx) /* --- @kx_start@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context + * @int forcep@ = nonzero to ignore the quiet timer * * Returns: --- * @@ -1078,13 +1079,13 @@ static int checkpub(keyexch *kx) * this); if no exchange is in progress, one is commenced. */ -void kx_start(keyexch *kx) +void kx_start(keyexch *kx, int forcep) { time_t now = time(0); if (checkpub(kx)) return; - if (!VALIDP(kx, now)) { + if (forcep || !VALIDP(kx, now)) { stop(kx); start(kx, now); a_notify("KXSTART %s", p_name(kx->p)); diff --git a/keymgmt.c b/keymgmt.c index 73f62654..c7199e56 100644 --- a/keymgmt.c +++ b/keymgmt.c @@ -450,16 +450,16 @@ static int loadpub(dstr *d) return (0); } -/* --- @km_interval@ --- * +/* --- @km_reload@ --- * * * Arguments: --- * * Returns: Zero if OK, nonzero to force reloading of keys. * - * Use: Called on the interval timer to perform various useful jobs. + * Use: Checks the keyrings to see if they need reloading. */ -int km_interval(void) +int km_reload(void) { dstr d = DSTR_INIT; key_file *kf; diff --git a/peer.c b/peer.c index 1542b35e..0e2664bf 100644 --- a/peer.c +++ b/peer.c @@ -226,7 +226,7 @@ found: buf_flip(&bb); if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPONG, &bb, p_txstart(p, MSG_MISC | MISC_EPONG))) - kx_start(&p->kx); + kx_start(&p->kx, 0); p_txend(p); } break; @@ -389,8 +389,6 @@ int p_pingsend(peer *p, ping *pg, unsigned type, buf *b, bb; struct timeval tv; - assert(!pg->p); - switch (type) { case MISC_PING: pg->msg = MISC_PONG; @@ -405,7 +403,7 @@ int p_pingsend(peer *p, ping *pg, unsigned type, p_pingwrite(pg, &bb); buf_flip(&bb); if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPING, &bb, b)) - kx_start(&p->kx); + kx_start(&p->kx, 0); if (!BOK(b)) return (-1); p_txend(p); @@ -445,7 +443,7 @@ void p_tun(peer *p, buf *b) TIMER; if (ksl_encrypt(&p->ks, MSG_PACKET, b, bb)) - kx_start(&p->kx); + kx_start(&p->kx, 0); if (BOK(bb) && BLEN(bb)) { p->st.n_ipout++; p->st.sz_ipout += BLEN(bb); @@ -453,6 +451,25 @@ void p_tun(peer *p, buf *b) } } +/* --- @p_keyreload@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Forces a check of the daemon's keyring files. + */ + +void p_keyreload(void) +{ + peer *p; + + if (km_reload()) { + for (p = peers; p; p = p->next) + kx_newkeys(&p->kx); + } +} + /* --- @p_interval@ --- * * * Arguments: --- @@ -464,16 +481,11 @@ void p_tun(peer *p, buf *b) void p_interval(void) { - peer *p, *pp; - int reload; + peer *p; - reload = km_interval(); - for (p = peers; p; p = pp) { - pp = p->next; - if (reload) - kx_newkeys(&p->kx); + p_keyreload(); + for (p = peers; p; p = p->next) ksl_prune(&p->ks); - } } /* --- @p_stats@ --- * diff --git a/tripe-init.in b/tripe-init.in index b4c5e6d7..e3d835ac 100755 --- a/tripe-init.in +++ b/tripe-init.in @@ -170,12 +170,16 @@ case "$1" in $tripectl stats $i | sed 's/^/ /' done ;; + reload) + $tripectl reload + echo "Keyrings reloaded OK." + ;; restart | force-reload) sh $0 stop sh $0 start ;; *) - echo >&2 "usage: $0 start|stop|restart|status|force-reload" + echo >&2 "usage: $0 start|stop|restart|status|reload|force-reload" exit 1 ;; esac diff --git a/tripe.h b/tripe.h index 002b3735..a358fb91 100644 --- a/tripe.h +++ b/tripe.h @@ -342,30 +342,54 @@ typedef struct obuf { char buf[OBUFSZ]; /* The actual buffer */ } obuf; +typedef struct oqueue { + obuf *hd, *tl; /* Head and tail pointers */ +} oqueue; + +struct admin; + +typedef struct admin_bgop { + struct admin_bgop *next, *prev; /* Links to next and previous */ + struct admin *a; /* Owner job */ + char *tag; /* Tag string for messages */ + void (*cancel)(struct admin_bgop *); /* Destructor function */ +} admin_bgop; + +typedef struct admin_addop { + admin_bgop bg; /* Background operation header */ + peerspec peer; /* Peer pending creation */ + char *paddr; /* Hostname to be resolved */ + bres_client r; /* Background resolver task */ + sel_timer t; /* Timer for resolver */ +} admin_addop; + +typedef struct admin_pingop { + admin_bgop bg; /* Background operation header */ + ping ping; /* Ping pending response */ + struct timeval pingtime; /* Time last ping was sent */ +} admin_pingop; + typedef struct admin { struct admin *next, *prev; /* Links to next and previous */ unsigned f; /* Various useful flags */ #ifndef NTRACE unsigned seq; /* Sequence number for tracing */ #endif - obuf *o_head, *o_tail; /* Output buffer list */ + oqueue out; /* Output buffer list */ + oqueue delay; /* Delayed output buffer list */ + admin_bgop *bg; /* Backgrounded operations */ 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 */ - ping ping; /* Ping pending response */ - struct timeval pingtime; /* Time last ping was sent */ } admin; #define AF_DEAD 1u /* Destroy this admin block */ #define AF_LOCK 2u /* Don't destroy it yet */ #define AF_NOTE 4u /* Catch notifications */ +#define AF_WARN 8u /* Catch warning messages */ #ifndef NTRACE - #define AF_TRACE 8u /* Catch tracing */ + #define AF_TRACE 16u /* Catch tracing */ #endif -#define AF_WARN 16u /* Catch warning messages */ +#define AF_CLOSE 32u /* Client closed connection */ #ifndef NTRACE # define AF_ALLMSGS (AF_NOTE | AF_TRACE | AF_WARN) @@ -393,16 +417,16 @@ extern unsigned tr_flags; /* Trace options flags */ /*----- Key management ----------------------------------------------------*/ -/* --- @km_interval@ --- * +/* --- @km_reload@ --- * * * Arguments: --- * * Returns: Zero if OK, nonzero to force reloading of keys. * - * Use: Called on the interval timer to perform various useful jobs. + * Use: Checks the keyrings to see if they need reloading. */ -extern int km_interval(void); +extern int km_reload(void); /* --- @km_init@ --- * * @@ -437,6 +461,7 @@ extern int km_getpubkey(const char */*tag*/, ge */*kpub*/, /* --- @kx_start@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context + * @int forcep@ = nonzero to ignore the quiet timer * * Returns: --- * @@ -445,7 +470,7 @@ extern int km_getpubkey(const char */*tag*/, ge */*kpub*/, * this); if no exchange is in progress, one is commenced. */ -extern void kx_start(keyexch */*kx*/); +extern void kx_start(keyexch */*kx*/, int /*forcep*/); /* --- @kx_message@ --- * * @@ -806,6 +831,17 @@ extern void p_pingdone(ping */*p*/, int /*rc*/); extern void p_tun(peer */*p*/, buf */*b*/); +/* --- @p_keyreload@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Forces a check of the daemon's keyring files. + */ + +extern void p_keyreload(void); + /* --- @p_interval@ --- * * * Arguments: ---