X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/blobdiff_plain/786989941b7b4504f0234c4a318f929802e981ad..136f3f44e25317d5423b4bc0b075b297e6d00c93:/server/peer.c diff --git a/server/peer.c b/server/peer.c index 50dec936..dab3bfcf 100644 --- a/server/peer.c +++ b/server/peer.c @@ -1,55 +1,46 @@ /* -*-c-*- - * - * $Id$ * * Communication with the peer * * (c) 2001 Straylight/Edgeware */ -/*----- Licensing notice --------------------------------------------------* +/*----- Licensing notice --------------------------------------------------* * * This file is part of Trivial IP Encryption (TrIPE). * - * TrIPE is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * TrIPE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * + * TrIPE is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * * You should have received a copy of the GNU General Public License - * along with TrIPE; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with TrIPE. If not, see . */ /*----- Header files ------------------------------------------------------*/ #include "tripe.h" -/*----- Static variables --------------------------------------------------*/ +/*----- Global state ------------------------------------------------------*/ -static peer *peers = 0; -static sel_file sock; +udpsocket udpsock[NADDRFAM]; -/*----- Tunnel table ------------------------------------------------------*/ +/*----- Static variables --------------------------------------------------*/ -const tunnel_ops *tunnels[] = { -#ifdef TUN_LINUX - &tun_linux, -#endif -#ifdef TUN_BSD - &tun_bsd, -#endif -#ifdef TUN_UNET - &tun_unet, -#endif - &tun_slip, - 0 -}, *tun_default; +static sym_table byname; +static addrmap byaddr; +static unsigned nmobile; +static struct tunnel_node { + struct tunnel_node *next; + const tunnel_ops *tops; +} *tunnels, **tunnels_tail = &tunnels; +const tunnel_ops *dflttun; /*----- Main code ---------------------------------------------------------*/ @@ -90,7 +81,7 @@ 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); @@ -123,6 +114,198 @@ 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 + * @int ty@ message type to send + * @buf *bin, *bout@ = input and output buffers + * + * Returns: --- + * + * Use: Convenience function for packet encryption. Forces + * renegotiation when necessary. Check for the output buffer + * being broken to find out whether the encryption was + * successful. + */ + +static int p_encrypt(peer *p, int ty, buf *bin, buf *bout) +{ + int err = ksl_encrypt(&p->ks, ty, bin, bout); + + if (err == KSERR_REGEN) { + kx_start(&p->kx, 1); + err = 0; + } + if (!BOK(bout)) + err = -1; + return (err); +} + +/* --- @p_updateaddr@ --- * + * + * Arguments: @peer *p@ = pointer to peer block + * @const addr *a@ = address to associate with this peer + * + * Returns: Zero if the address was changed; @+1@ if it was already + * right. + * + * Use: Updates our idea of @p@'s address. + */ + +int p_updateaddr(peer *p, const addr *a) +{ + peer *q; + peer_byaddr *pa, *qa; + int ix; + unsigned f; + + /* --- Figure out how to proceed --- * + * + * If this address already belongs to a different peer, then swap the + * addresses over. This doesn't leave the displaced peer in an especially + * good state, but it ought to get sorted out soon enough. + */ + + pa = am_find(&byaddr, a, sizeof(peer_byaddr), &f); + if (f && pa->p == p) + return (+1); + else if (!f) { + T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); ) + am_remove(&byaddr, p->byaddr); + p->byaddr = pa; p->spec.sa = *a; pa->p = p; + p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0); + a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END); + return (0); + } else { + q = pa->p; qa = p->byaddr; + T( trace(T_PEER, "peer: swapping addresses for `%s' and `%s'", + p_name(p), p_name(q)); ) + q->byaddr = qa; qa->p = q; q->spec.sa = p->spec.sa; + p->byaddr = pa; pa->p = p; p->spec.sa = *a; + ix = p->afix; p->afix = q->afix; q->afix = ix; + a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END); + a_notify("NEWADDR", "?PEER", q, "?ADDR", &q->spec.sa, A_END); + return (0); + } +} + +/* --- @p_decrypt@ --- * + * + * 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 + * + * Returns: Zero on success; nonzero on error. + * + * 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 **pp, addr *a, size_t n, + int ty, buf *bin, buf *bout) +{ + peer *p, *q; + int err = KSERR_DECRYPT; + + /* --- If we have a match on the source address then try that first --- */ + + q = *pp; + if (q) { + T( trace(T_PEER, "peer: decrypting packet from known peer `%s'", + p_name(q)); ) + if ((err = ksl_decrypt(&q->ks, ty, bin, bout)) != KSERR_DECRYPT || + !(q->spec.f & PSF_MOBILE) || nmobile == 1) { + p = q; + goto match; + } + T( trace(T_PEER, "peer: failed to decrypt: try other mobile peers..."); ) + } else if (nmobile) + T( trace(T_PEER, "peer: unknown source: trying mobile peers...") ); + else { + p = 0; + goto searched; + } + + /* --- See whether any mobile peer is interested --- */ + + p = 0; + FOREACH_PEER(qq, { + if (qq == q || !(qq->spec.f & PSF_MOBILE)) continue; + if ((err = ksl_decrypt(&qq->ks, ty, bin, bout)) == KSERR_DECRYPT) { + T( trace(T_PEER, "peer: peer `%s' failed to decrypt", + p_name(qq)); ) + continue; + } else { + p = qq; + IF_TRACING(T_PEER, { + if (!err) + trace(T_PEER, "peer: peer `%s' reports success", p_name(qq)); + else { + trace(T_PEER, "peer: peer `%s' reports decryption error %d", + p_name(qq), err); + } + }) + break; + } + }); + + /* --- We've searched the mobile peers --- */ + +searched: + if (!p) { + if (!q) + a_warn("PEER", "-", "unexpected-source", "?ADDR", a, A_END); + else { + a_warn("PEER", "?PEER", p, "decrypt-failed", + "error-code", "%d", err, A_END); + p_rxupdstats(q, n); + } + return (-1); + } + + /* --- We found one that accepted, so update the peer's address --- */ + + if (!err) { + *pp = p; + p_updateaddr(p, a); + } + +match: + p_rxupdstats(p, n); + 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)) + return (-1); + return (0); +} + /* --- @p_read@ --- * * * Arguments: @int fd@ = file descriptor to read from @@ -138,33 +321,41 @@ static void p_read(int fd, unsigned mode, void *v) { peer *p = 0; addr a; - size_t sz; + socklen_t sz; ssize_t n; int ch; buf b, bb; +#ifndef NTRACE + int ix = -1; + char name[NI_MAXHOST], svc[NI_MAXSERV]; +#endif /* --- Read the data --- */ - TIMER; + QUICKRAND; sz = sizeof(addr); n = recvfrom(fd, buf_i, sizeof(buf_i), 0, &a.sa, &sz); if (n < 0) { a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END); return; } + IF_TRACING(T_PEER, { + ix = afix(a.sa.sa_family); + getnameinfo(&a.sa, sz, name, sizeof(name), svc, sizeof(svc), + NI_NUMERICHOST | NI_NUMERICSERV); + }) /* --- If the packet is a greeting, don't check peers --- */ if (n && buf_i[0] == (MSG_MISC | MISC_GREET)) { IF_TRACING(T_PEER, { - trace(T_PEER, "peer: greeting received from INET %s %u", - inet_ntoa(a.sin.sin_addr), - (unsigned)ntohs(a.sin.sin_port)); + trace(T_PEER, "peer: greeting received from %s %s %s", + aftab[ix].name, name, svc); trace_block(T_PACKET, "peer: greeting contents", buf_i, n); }) buf_init(&b, buf_i, n); buf_getbyte(&b); - if (c_check(&b) || BLEFT(&b)) { + if (c_check(0, 0, &b) || BLEFT(&b)) { a_warn("PEER", "-", "invalid-greeting", A_END); return; } @@ -175,28 +366,27 @@ 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. + */ - assert(a.sa.sa_family == AF_INET); - for (p = peers; p; p = p->next) { - 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", "?ADDR", &a, A_END); - return; + p = p_findbyaddr(&a); -found: 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 %s %s %s", + p_name(p), aftab[ix].name, name, svc); + } else { + trace(T_PEER, "peer: packet received from unknown address %s %s %s", + aftab[ix].name, name, svc); + } 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; buf_init(&b, buf_i, n); if ((ch = buf_getbyte(&b)) < 0) { a_warn("PEER", "?PEER", p, "bad-packet", "no-type", A_END); @@ -210,15 +400,12 @@ found: "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 (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) { - p->st.n_reject++; - a_warn("PEER", "?PEER", p, "decrypt-failed", A_END); + if (p_decrypt(&p, &a, n, MSG_PACKET, &b, &bb)) return; - } if (BOK(&bb)) { p->st.n_ipin++; p->st.sz_ipin += BSZ(&b); @@ -229,57 +416,70 @@ found: } break; case MSG_KEYEXCH: - kx_message(&p->kx, ch & MSG_TYPEMASK, &b); + if (p) p_rxupdstats(p, n); + if (kx_message(p ? &p->kx : 0, &a, ch & MSG_TYPEMASK, &b)) goto unexp; break; case MSG_MISC: switch (ch & MSG_TYPEMASK) { case MISC_NOP: + if (!p) goto unexp; + p_rxupdstats(p, n); T( trace(T_PEER, "peer: received NOP packet"); ) break; case MISC_PING: + if (!p) goto unexp; + p_rxupdstats(p, n); buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b)); p_txend(p); - break; + break; case MISC_PONG: + if (!p) goto unexp; + p_rxupdstats(p, n); 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", "?PEER", "decrypt-failed", A_END); + if (p_decrypt(&p, &a, n, ch, &b, &bb)) 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, 0); + p_encrypt(p, MSG_MISC | MISC_EPONG, &bb, + p_txstart(p, MSG_MISC | MISC_EPONG)); 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", "?PEER", p, "decrypt-failed", A_END); + if (p_decrypt(&p, &a, n, ch, &b, &bb)) return; - } if (BOK(&bb)) { buf_flip(&bb); p_ponged(p, MISC_EPONG, &bb); } break; + case MISC_BYE: + buf_init(&bb, buf_t, sizeof(buf_t)); + if (p_decrypt(&p, &a, n, ch, &b, &bb)) return; + if (!(p->spec.f&PSF_EPHEM)) return; + if (BOK(&bb)) { + buf_flip(&bb); + if (BSZ(&bb)) return; + p_destroy(p, 0); + } + break; } break; default: - p->st.n_reject++; + if (p) p->st.n_reject++; a_warn("PEER", "?PEER", p, "bad-packet", - "unknown-category" "0x%02x", ch, + "unknown-category", "0x%02x", ch, A_END); break; + unexp: + a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END); + break; } } @@ -301,6 +501,35 @@ buf *p_txstart(peer *p, unsigned msg) return (&p->b); } +/* --- @p_txaddr@ --- * + * + * Arguments: @const addr *a@ = recipient address + * @const void *p@ = pointer to packet to send + * @size_t sz@ = length of packet + * + * Returns: Zero if successful, nonzero on error. + * + * Use: Sends a packet to an address which (possibly) isn't a current + * peer. + */ + +int p_txaddr(const addr *a, const void *p, size_t sz) +{ + socklen_t sasz = addrsz(a); + int i; + + if ((i = afix(a->sa.sa_family)) < 0) { + a_warn("PEER", "?ADDR", a, "disabled-address-family", A_END); + return (-1); + } + IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); ) + if (sendto(udpsock[i].sf.fd, p, sz, 0, &a->sa, sasz) < 0) { + a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END); + return (-1); + } + return (0); +} + /* --- @p_txend@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -314,14 +543,16 @@ static void p_setkatimer(peer *); static int p_dotxend(peer *p) { + socklen_t sasz = addrsz(&p->spec.sa); + if (!BOK(&p->b)) { a_warn("PEER", "?PEER", p, "packet-build-failed", A_END); 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->spec.sa.sa, p->spec.sasz) < 0) { + if (sendto(udpsock[p->afix].sf.fd, BBASE(&p->b), BLEN(&p->b), + 0, &p->spec.sa.sa, sasz) < 0) { a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END); return (0); } else { @@ -433,8 +664,7 @@ int p_pingsend(peer *p, ping *pg, unsigned type, 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, 0); + p_encrypt(p, MSG_MISC | MISC_EPING, &bb, b); if (!BOK(b)) return (-1); p_txend(p); @@ -491,9 +721,8 @@ void p_tun(peer *p, buf *b) { buf *bb = p_txstart(p, MSG_PACKET); - TIMER; - if (ksl_encrypt(&p->ks, MSG_PACKET, b, bb)) - kx_start(&p->kx, 0); + QUICKRAND; + p_encrypt(p, MSG_PACKET, b, bb); if (BOK(bb) && BLEN(bb)) { p->st.n_ipout++; p->st.sz_ipout += BLEN(bb); @@ -512,12 +741,8 @@ void p_tun(peer *p, buf *b) void p_keyreload(void) { - peer *p; - - if (km_reload()) { - for (p = peers; p; p = p->next) - kx_newkeys(&p->kx); - } + if (km_reload()) + FOREACH_PEER(p, { kx_newkeys(&p->kx); }); } /* --- @p_interval@ --- * @@ -531,11 +756,8 @@ void p_keyreload(void) void p_interval(void) { - peer *p; - p_keyreload(); - for (p = peers; p; p = p->next) - ksl_prune(&p->ks); + FOREACH_PEER(p, { ksl_prune(&p->ks); }); } /* --- @p_stats@ --- * @@ -554,7 +776,25 @@ stats *p_stats(peer *p) { return (&p->st); } * Returns: A pointer to the peer's interface name. */ -const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); } +const char *p_ifname(peer *p) { return (p->ifname); } + +/* --- @p_setifname@ --- * + * + * Arguments: @peer *p@ = pointer to a peer block + * @const char *name@ = pointer to the new name + * + * Returns: --- + * + * Use: Changes the name held for a peer's interface. + */ + +void p_setifname(peer *p, const char *name) +{ + xfree(p->ifname); + p->ifname = xstrdup(name); + if (p->spec.tops->setifname) + p->spec.tops->setifname(p->t, name); +} /* --- @p_addr@ --- * * @@ -565,65 +805,226 @@ const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); } const addr *p_addr(peer *p) { return (&p->spec.sa); } -/* --- @p_init@ --- * +/* --- @p_bind@ --- * * - * Arguments: @struct in_addr addr@ = address to bind to - * @unsigned port@ = port number to listen to + * Arguments: @struct addrinfo *ailist@ = addresses to bind to * - * Returns: --- + * Returns: Zero on success, @-1@ on failure. * - * Use: Initializes the peer system; creates the socket. + * Use: Binds to the main UDP sockets. */ -void p_init(struct in_addr addr, unsigned port) +int p_bind(struct addrinfo *ailist) { - int fd; - struct sockaddr_in sin; + int fd = -1; int len = PKBUFSZ; + int yes = 1; + int i; + struct addrinfo *ai; + unsigned port, lastport = 0; + addr a; + socklen_t sz; + + for (i = 0; i < NADDRFAM; i++) udpsock[i].sf.fd = -1; + + for (ai = ailist; ai; ai = ai->ai_next) { + if ((i = afix(ai->ai_family)) < 0) continue; + if (udpsock[i].sf.fd != -1) continue; + + /* --- Note on socket buffer sizes --- * + * + * For some bizarre reason, Linux 2.2 (at least) doubles the socket + * buffer sizes I pass to @setsockopt@. I'm not putting special-case + * code here for Linux: BSD (at least TCPv2) does what I tell it rather + * than second-guessing me. + */ + + if ((fd = socket(ai->ai_family, SOCK_DGRAM, 0)) < 0) { + a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name, + "create-failed", "?ERRNO", A_END); + goto fail; + } + if (i == AFIX_INET6 && + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) { + a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name, + "set-v6only-failed", "?ERRNO", A_END); + goto fail; + } + assert(ai->ai_addrlen <= sizeof(a)); + memcpy(&a, ai->ai_addr, ai->ai_addrlen); + if ((port = getport(&a)) == 0 && lastport) setport(&a, lastport); + if (bind(fd, &a.sa, addrsz(&a))) { + a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name, + "bind-failed", "?ERRNO", A_END); + goto fail; + } + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) || + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) { + a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name, + "set-buffers-failed", "?ERRNO", A_END); + goto fail; + } + fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + if (port) + udpsock[i].port = port; + else { + sz = sizeof(a); + if (getsockname(fd, &a.sa, &sz)) { + a_warn("PEER", "-", "udp-socket", "%s", aftab[i].name, + "read-local-address-failed", "?ERRNO", A_END); + goto fail; + } + udpsock[i].port = lastport = getport(&a); + } + T( trace(T_PEER, "peer: created %s socket", aftab[i].name); ) + sel_initfile(&sel, &udpsock[i].sf, fd, SEL_READ, p_read, 0); + sel_addfile(&udpsock[i].sf); + fd = -1; + } - /* --- Note on socket buffer sizes --- * - * - * For some bizarre reason, Linux 2.2 (at least) doubles the socket buffer - * sizes I pass to @setsockopt@. I'm not putting special-case code here - * for Linux: BSD (at least TCPv2) does what I tell it rather than second- - * guessing me. - */ + return (0); + +fail: + if (fd != -1) close(fd); + p_unbind(); + return (-1); +} + +/* --- @p_unbind@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Unbinds the UDP sockets. There must not be any active peers, + * and none can be created until the sockets are rebound. + */ + +void p_unbind(void) +{ + int i; - if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) - die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno)); - BURN(sin); - sin.sin_family = AF_INET; - sin.sin_addr = addr; - sin.sin_port = htons(port); - if (bind(fd, (struct sockaddr *)&sin, sizeof(sin))) - die(EXIT_FAILURE, "bind failed: %s", strerror(errno)); - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) || - setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) { - die(EXIT_FAILURE, "failed to set socket buffer sizes: %s", - strerror(errno)); +#ifndef NDEBUG + { peer_iter it; p_mkiter(&it); assert(!p_next(&it)); } +#endif + + for (i = 0; i < NADDRFAM; i++) { + if (udpsock[i].sf.fd == -1) continue; + sel_rmfile(&udpsock[i].sf); + close(udpsock[i].sf.fd); + udpsock[i].sf.fd = -1; } - fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); - sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0); - sel_addfile(&sock); - T( trace(T_PEER, "peer: created socket"); ) } -/* --- @p_port@ --- * +/* --- @p_init@ --- * * * Arguments: --- * - * Returns: Port number used for socket. + * Returns: --- + * + * Use: Initializes the peer system. */ -unsigned p_port(void) +void p_init(void) { - addr a; - size_t sz = sizeof(addr); + sym_create(&byname); + am_create(&byaddr); +} + +/* --- @p_addtun@ --- * + * + * Arguments: @const tunnel_ops *tops@ = tunnel ops to add + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Adds a tunnel class to the list of known classes, if it + * initializes properly. If there is no current default tunnel, + * then this one is made the default. + * + * Does nothing if the tunnel class is already known. So adding + * a bunch of tunnels takes quadratic time, but there will be + * too few to care about. + */ + +int p_addtun(const tunnel_ops *tops) +{ + struct tunnel_node *tn; + + for (tn = tunnels; tn; tn = tn->next) + if (tn->tops == tops) return (0); + if (tops->init()) return (-1); + tn = CREATE(struct tunnel_node); + tn->next = 0; tn->tops = tops; + *tunnels_tail = tn; tunnels_tail = &tn->next; + if (!dflttun) dflttun = tops; + return (0); +} + +/* --- @p_setdflttun@ --- * + * + * Arguments: @const tunnel_ops *tops@ = tunnel ops to set + * + * Returns: --- + * + * Use: Sets the default tunnel. It must already be registered. The + * old default is forgotten. + */ + +void p_setdflttun(const tunnel_ops *tops) + { dflttun = tops; } + +/* --- @p_dflttun@ --- * + * + * Arguments: --- + * + * Returns: A pointer to the current default tunnel operations, or null + * if no tunnels are defined. + */ + +const tunnel_ops *p_dflttun(void) { return (dflttun); } + +/* --- @p_findtun@ --- * + * + * Arguments: @const char *name@ = tunnel name + * + * Returns: Pointer to the tunnel operations, or null. + * + * Use: Finds the operations for a named tunnel class. + */ + +const tunnel_ops *p_findtun(const char *name) +{ + const struct tunnel_node *tn; + + for (tn = tunnels; tn; tn = tn->next) + if (mystrieq(tn->tops->name, name) == 0) return (tn->tops); + return (0); +} + +/* --- @p_mktuniter@ --- * + * + * Arguments: @tuniter *i@ = pointer to iterator to initialize + * + * Returns: --- + * + * Use: Initializes a tunnel iterator. + */ + +void p_mktuniter(tun_iter *i) { i->next = tunnels; } + +/* --- @p_nexttun@ --- * + * + * Arguments: @tuniter *i@ = pointer to iterator + * + * Returns: Pointer to the next tunnel's operations, or null. + */ + +const tunnel_ops *p_nexttun(tun_iter *i) +{ + const struct tunnel_node *tn = i->next; - if (getsockname(sock.fd, &a.sa, &sz)) - die(EXIT_FAILURE, "couldn't read port number: %s", strerror(errno)); - assert(a.sa.sa_family == AF_INET); - return (ntohs(a.sin.sin_port)); + if (!tn) return (0); + else { i->next = tn->next; return (tn->tops); } } /* --- @p_keepalive@ --- * @@ -639,6 +1040,7 @@ unsigned p_port(void) 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); @@ -677,39 +1079,66 @@ static void p_setkatimer(peer *p) peer *p_create(peerspec *spec) { peer *p = CREATE(peer); + const tunnel_ops *tops = spec->tops; + int fd; + unsigned f; + + p->byname = sym_find(&byname, spec->name, -1, sizeof(peer_byname), &f); + if (f) goto tidy_0; + p->byaddr = am_find(&byaddr, &spec->sa, sizeof(peer_byaddr), &f); + if (f) goto tidy_1; + p->byname->p = p->byaddr->p = p; T( trace(T_PEER, "peer: creating new peer `%s'", spec->name); ) p->spec = *spec; - p->spec.name = xstrdup(spec->name); + p->spec.name = (/*unconst*/ char *)SYM_NAME(p->byname); + if (spec->tag) p->spec.tag = xstrdup(spec->tag); + if (spec->privtag) p->spec.privtag = xstrdup(spec->privtag); + if (spec->knock) p->spec.knock = xstrdup(spec->knock); p->ks = 0; - p->prev = 0; p->pings = 0; + p->ifname = 0; + p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0); memset(&p->st, 0, sizeof(stats)); p->st.t_start = time(0); - if ((p->t = spec->tops->create(p)) == 0) - goto tidy_0; + if (!(tops->flags & TUNF_PRIVOPEN)) + fd = -1; + else if ((fd = ps_tunfd(tops, &p->ifname)) < 0) + goto tidy_2; + if ((p->t = tops->create(p, fd, &p->ifname)) == 0) + goto tidy_3; + T( trace(T_TUNNEL, "peer: attached interface %s to peer `%s'", + p->ifname, p_name(p)); ) p_setkatimer(p); - if (kx_init(&p->kx, p, &p->ks)) - goto tidy_1; - p->next = peers; - if (peers) - peers->prev = p; - peers = p; + iv_addreason(); + if (kx_setup(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK)) + goto tidy_4; a_notify("ADD", - "?PEER", p, - "%s", p->t->ops->ifname(p->t), - "?ADDR", &p->spec.sa, - A_END); - a_notify("KXSTART", "?PEER", p, A_END); + "?PEER", p, + "%s", p->ifname, + "?ADDR", &p->spec.sa, + A_END); + if (!(p->spec.f & KXF_CORK)) { + a_notify("KXSTART", "?PEER", p, A_END); /* Couldn't tell anyone before */ + } + if (p->spec.f & PSF_MOBILE) nmobile++; return (p); -tidy_1: - if (spec->t_ka) - sel_rmtimer(&p->tka); +tidy_4: + if (spec->t_ka) sel_rmtimer(&p->tka); + xfree(p->ifname); p->t->ops->destroy(p->t); + iv_rmreason(); +tidy_3: + if (fd >= 0) close(fd); +tidy_2: + am_remove(&byaddr, p->byaddr); + if (p->spec.tag) xfree(p->spec.tag); + if (p->spec.privtag) xfree(p->spec.privtag); +tidy_1: + sym_remove(&byname, p->byname); tidy_0: - xfree(p->spec.name); DESTROY(p); return (0); } @@ -721,7 +1150,28 @@ 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@ --- * + * + * Arguments: @peer *p@ = pointer to a peer block + * + * Returns: A pointer to the peer's public key tag. + */ + +const char *p_tag(peer *p) + { return (p->spec.tag ? p->spec.tag : p->spec.name); } + +/* --- @p_privtag@ --- * + * + * Arguments: @peer *p@ = pointer to a peer block + * + * Returns: A pointer to the peer's private key tag. + */ + +const char *p_privtag(peer *p) + { return (p->spec.privtag ? p->spec.privtag : tag_priv); } /* --- @p_spec@ --- * * @@ -732,6 +1182,26 @@ const char *p_name(peer *p) { return (p->spec.name); } const peerspec *p_spec(peer *p) { return (&p->spec); } +/* --- @p_findbyaddr@ --- * + * + * Arguments: @const addr *a@ = address to look up + * + * Returns: Pointer to the peer block, or null if not found. + * + * Use: Finds a peer by address. + */ + +peer *p_findbyaddr(const addr *a) +{ + peer_byaddr *pa; + + if ((pa = am_find(&byaddr, a, 0, 0)) != 0) { + assert(pa->p); + return (pa->p); + } + return (0); +} + /* --- @p_find@ --- * * * Arguments: @const char *name@ = name to look up @@ -743,58 +1213,96 @@ const peerspec *p_spec(peer *p) { return (&p->spec); } peer *p_find(const char *name) { - peer *p; - for (p = peers; p; p = p->next) { - if (strcmp(name, p->spec.name) == 0) - return (p); - } - return (0); + peer_byname *pn; + + if ((pn = sym_find(&byname, name, -1, 0, 0)) != 0) + return (pn->p); + return (0); } /* --- @p_destroy@ --- * * * Arguments: @peer *p@ = pointer to a peer + * @int bye@ = say goodbye to the peer? * * Returns: --- * * Use: Destroys a peer. */ -void p_destroy(peer *p) +void p_destroy(peer *p, int bye) { ping *pg, *ppg; + buf *b, bb; T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); ) + + if (bye) { + b = p_txstart(p, MSG_MISC | MISC_BYE); + buf_init(&bb, buf_t, sizeof(buf_t)); + assert(BOK(&bb)); buf_flip(&bb); + p_encrypt(p, MSG_MISC | MISC_BYE, &bb, b); + p_txend(p); + } + a_notify("KILL", "%s", p->spec.name, A_END); ksl_free(&p->ks); kx_free(&p->kx); + if (p->spec.f & PSF_MOBILE) nmobile--; + if (p->ifname) xfree(p->ifname); + if (p->spec.tag) xfree(p->spec.tag); + if (p->spec.privtag) xfree(p->spec.privtag); + if (p->spec.knock) xfree(p->spec.knock); p->t->ops->destroy(p->t); - if (p->spec.t_ka) - sel_rmtimer(&p->tka); - xfree(p->spec.name); + if (p->spec.t_ka) sel_rmtimer(&p->tka); 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) - p->prev->next = p->next; - else - peers = p->next; + sym_remove(&byname, p->byname); + am_remove(&byaddr, p->byaddr); + iv_rmreason(); DESTROY(p); } -/* --- @p_first@, @p_next@ --- * +/* --- @p_destroyall@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Destroys all of the peers, saying goodbye. + */ + +void p_destroyall(void) { FOREACH_PEER(p, { p_destroy(p, 1); }); } + +/* --- @p_mkiter@ --- * + * + * Arguments: @peer_iter *i@ = pointer to an iterator + * + * Returns: --- + * + * Use: Initializes the iterator. + */ + +void p_mkiter(peer_iter *i) { sym_mkiter(&i->i, &byname); } + +/* --- @p_next@ --- * + * + * Arguments: @peer_iter *i@ = pointer to an iterator * - * Arguments: @peer *p@ = a peer block + * Returns: Next peer, or null if at the end. * - * Returns: @peer_first@ returns the first peer in some ordering; - * @peer_next@ returns the peer following a given one in the - * same ordering. Null is returned for the end of the list. + * Use: Returns the next peer. */ -peer *p_first(void) { return (peers); } -peer *p_next(peer *p) { return (p->next); } +peer *p_next(peer_iter *i) +{ + peer_byname *pn; + + if ((pn = sym_next(&i->i)) == 0) + return (0); + return (pn->p); +} /*----- That's all, folks -------------------------------------------------*/