/* -*-c-*-
*
- * $Id: peer.c,v 1.4 2001/02/16 21:40:24 mdw Exp $
+ * $Id$
*
* Communication with the peer
*
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: peer.c,v $
- * Revision 1.4 2001/02/16 21:40:24 mdw
- * Change key exchange message interface. Maintain statistics.
- *
- * Revision 1.3 2001/02/04 17:10:58 mdw
- * Make file descriptors be nonblocking and close-on-exec.
- *
- * Revision 1.2 2001/02/03 22:40:29 mdw
- * Put timer information into the entropy pool when packets are received
- * and on similar events. Reseed the generator on the interval timer.
- *
- * Revision 1.1 2001/02/03 20:26:37 mdw
- * Initial checkin.
- *
- */
-
/*----- Header files ------------------------------------------------------*/
#include "tripe.h"
static peer *peers = 0;
static sel_file sock;
+/*----- Tunnel table ------------------------------------------------------*/
+
+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;
+
/*----- Main code ---------------------------------------------------------*/
+static void checktimers(void)
+{
+ sel_timer *t, **tt;
+
+ tt = &sel.timers;
+ while (*tt) {
+ assert((*tt)->prev == tt);
+ tt = &(*tt)->next;
+ }
+}
+
+/* --- @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
sz = sizeof(addr);
n = recvfrom(fd, buf_i, sizeof(buf_i), 0, &a.sa, &sz);
if (n < 0) {
- a_warn("error reading socket: %s", strerror(errno));
+ a_warn("PEER - socket-read-error -- %s", strerror(errno));
return;
}
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("packet from unexpected peer: %s:%u",
- inet_ntoa(a.sin.sin_addr), (unsigned)ntohs(a.sin.sin_port));
+ a_warn("PEER - unexpected-source INET %s %u",
+ inet_ntoa(a.sin.sin_addr), (unsigned)ntohs(a.sin.sin_port));
return;
found:
- T( trace(T_PEER, "peer: packet received from `%s'", p->name);
- trace_block(T_PACKET, "peer: packet contents", buf_i, n); )
+ IF_TRACING(T_PEER, {
+ trace(T_PEER, "peer: packet received from `%s'", p->spec.name);
+ trace_block(T_PACKET, "peer: packet contents", buf_i, n);
+ })
/* --- Pick the packet apart --- */
p->st.sz_in += n;
buf_init(&b, buf_i, n);
if ((ch = buf_getbyte(&b)) < 0) {
- a_warn("bad packet from `%s': no type byte", 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("unknown packet type from `%s'", p->name);
+ 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, &b, &bb)) {
+ if (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) {
p->st.n_reject++;
- a_warn("couldn't decrypt inbound packet from `%s'", p->name);
+ a_warn("PEER %s decrypt-failed", p->spec.name);
return;
}
if (BOK(&bb)) {
p->st.n_ipin++;
p->st.sz_ipin += BSZ(&b);
- tun_inject(&p->t, &bb);
+ p->t->ops->inject(p->t, &bb);
} else {
p->st.n_reject++;
- a_warn("packet build failed");
+ 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, 0);
+ 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("bad packet from `%s': unknown packet type", p->name);
+ a_warn("PEER %s bad-packet unknown-category 0x%02x", p->spec.name, ch);
break;
}
}
* 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("packet build failed");
- 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("packet send to `%s' failed: %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->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);
+ T( trace(T_PEER, "peer: ping 0x%08lx done (rc = %d)",
+ (unsigned long)p->id, rc); )
+checktimers();
+ 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;
+
+ 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, 0);
+ 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;
+ if (p->pings) p->pings->prev = pg;
+ p->pings = pg;
+ gettimeofday(&tv, 0);
+ tv.tv_sec += timeout;
+ sel_addtimer(&sel, &pg->t, &tv, p_pingtimeout, pg);
+checktimers();
+ 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
void p_tun(peer *p, buf *b)
{
buf *bb = p_txstart(p, MSG_PACKET);
+
TIMER;
- if (ksl_encrypt(&p->ks, b, bb))
- kx_start(&p->kx);
+ if (ksl_encrypt(&p->ks, MSG_PACKET, b, bb))
+ kx_start(&p->kx, 0);
if (BOK(bb) && BLEN(bb)) {
p->st.n_ipout++;
p->st.sz_ipout += BLEN(bb);
}
}
+/* --- @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: ---
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@ --- *
* Returns: A pointer to the peer's interface name.
*/
-const char *p_ifname(peer *p) { return (tun_ifname(&p->t)); }
+const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); }
/* --- @p_addr@ --- *
*
* 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@ --- *
*
- * Arguments: @unsigned port@ = port number to listen to
+ * Arguments: @struct in_addr addr@ = address to bind to
+ * @unsigned port@ = port number to listen to
*
* Returns: ---
*
* Use: Initializes the peer system; creates the socket.
*/
-void p_init(unsigned port)
+void p_init(struct in_addr addr, unsigned port)
{
int fd;
struct sockaddr_in sin;
+ int len = PKBUFSZ;
+
+ /* --- 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(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.s_addr = INADDR_ANY;
+ 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));
+ }
fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0);
sel_addfile(&sock);
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
- * @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.
*
* by this point.
*/
-peer *p_create(const char *name, 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;
+ p->pings = 0;
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 (tun_create(&p->t, 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;
+ switch (p->spec.sa.sa.sa_family) {
+ case AF_INET:
+ a_notify("ADD %s %s INET %s %u",
+ spec->name,
+ p->t->ops->ifname(p->t),
+ inet_ntoa(p->spec.sa.sin.sin_addr),
+ (unsigned)ntohs(p->spec.sa.sin.sin_port));
+ break;
+ default:
+ a_notify("ADD %s %s UNKNOWN", spec->name, p->t->ops->ifname(p->t));
+ break;
+ }
+ 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);
}
* 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_spec@ --- *
+ *
+ * Arguments: @peer *p@ = pointer to a peer block
+ *
+ * Returns: Pointer to the peer's specification
+ */
+
+const peerspec *p_spec(peer *p) { return (&p->spec); }
/* --- @p_find@ --- *
*
{
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);
void p_destroy(peer *p)
{
- T( trace(T_PEER, "peer: destroying peer `%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);
- tun_destroy(&p->t);
- xfree(p->name);
+ p->t->ops->destroy(p->t);
+ 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)