diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2006-12-16 11:45:08 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2006-12-16 11:45:08 +0000 |
commit | 40435664ebf23fa4a24a268ae284e5603dbb002b (patch) | |
tree | a4a161e3d30f4ee36baa5e0d9da5b7f3921b4222 /usr.sbin/relayd | |
parent | b63e93d285f5b4780d059c1c782d157ec880df72 (diff) |
Import hostated, the host status daemon. This daemon will monitor
remote hosts and dynamically alter pf(4) tables and redirection rules
for active server load balancing. The daemon has been written by
Pierre-Yves Ritschard (pyr at spootnik.org) and was formerly known as
"slbd".
The daemon is fully functional but it still needs some work and
cleanup so we don't link it to the build yet. Some TODOs are a
partial rewrite of the check_* routines (use libevent whenever we
can), improvement of the manpages, and general knf and cleanup.
ok deraadt@ claudio@
Diffstat (limited to 'usr.sbin/relayd')
-rw-r--r-- | usr.sbin/relayd/Makefile | 17 | ||||
-rw-r--r-- | usr.sbin/relayd/buffer.c | 222 | ||||
-rw-r--r-- | usr.sbin/relayd/check_icmp.c | 212 | ||||
-rw-r--r-- | usr.sbin/relayd/check_tcp.c | 109 | ||||
-rw-r--r-- | usr.sbin/relayd/control.c | 340 | ||||
-rw-r--r-- | usr.sbin/relayd/hce.c | 318 | ||||
-rw-r--r-- | usr.sbin/relayd/imsg.c | 181 | ||||
-rw-r--r-- | usr.sbin/relayd/log.c | 159 | ||||
-rw-r--r-- | usr.sbin/relayd/parse.y | 937 | ||||
-rw-r--r-- | usr.sbin/relayd/pfe.c | 497 | ||||
-rw-r--r-- | usr.sbin/relayd/pfe_filter.c | 336 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.8 | 94 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.c | 377 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.conf.5 | 214 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.h | 307 |
15 files changed, 4320 insertions, 0 deletions
diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile new file mode 100644 index 00000000000..c04e70f3036 --- /dev/null +++ b/usr.sbin/relayd/Makefile @@ -0,0 +1,17 @@ +# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $ + +PROG= hostated +SRCS= parse.y log.c control.c buffer.c imsg.c hostated.c \ + pfe.c pfe_filter.c hce.c check_icmp.c check_tcp.c check_http.c +MAN= hostated.8 hostated.conf.5 + +LDADD= -levent +DPADD= ${LIBEVENT} +CFLAGS+= -Wall -Werror -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded +CLEANFILES+= y.tab.h + +.include <bsd.prog.mk> diff --git a/usr.sbin/relayd/buffer.c b/usr.sbin/relayd/buffer.c new file mode 100644 index 00000000000..3abe27b1e62 --- /dev/null +++ b/usr.sbin/relayd/buffer.c @@ -0,0 +1,222 @@ +/* $OpenBSD: buffer.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <net/if.h> +#include <errno.h> +#include <event.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hostated.h" + +int buf_realloc(struct buf *, size_t); +void buf_enqueue(struct msgbuf *, struct buf *); +void buf_dequeue(struct msgbuf *, struct buf *); + +struct buf * +buf_open(size_t len) +{ + struct buf *buf; + + if ((buf = calloc(1, sizeof(struct buf))) == NULL) + return (NULL); + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return (NULL); + } + buf->size = buf->max = len; + + return (buf); +} + +struct buf * +buf_dynamic(size_t len, size_t max) +{ + struct buf *buf; + + if (max < len) + return (NULL); + + if ((buf = buf_open(len)) == NULL) + return (NULL); + + if (max > 0) + buf->max = max; + + return (buf); +} + +int +buf_realloc(struct buf *buf, size_t len) +{ + u_char *b; + + /* on static buffers max is eq size and so the following fails */ + if (buf->wpos + len > buf->max) { + errno = ENOMEM; + return (-1); + } + + b = realloc(buf->buf, buf->wpos + len); + if (b == NULL) + return (-1); + buf->buf = b; + buf->size = buf->wpos + len; + + return (0); +} + +int +buf_add(struct buf *buf, void *data, size_t len) +{ + if (buf->wpos + len > buf->size) + if (buf_realloc(buf, len) == -1) + return (-1); + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return (0); +} + +void * +buf_reserve(struct buf *buf, size_t len) +{ + void *b; + + if (buf->wpos + len > buf->size) + if (buf_realloc(buf, len) == -1) + return (NULL); + + b = buf->buf + buf->wpos; + buf->wpos += len; + return (b); +} + +void * +buf_seek(struct buf *buf, size_t pos, size_t len) +{ + /* only allowed to seek in already written parts */ + if (pos + len > buf->wpos) + return (NULL); + + return (buf->buf + pos); +} + +int +buf_close(struct msgbuf *msgbuf, struct buf *buf) +{ + buf_enqueue(msgbuf, buf); + return (1); +} + +void +buf_free(struct buf *buf) +{ + free(buf->buf); + free(buf); +} + +void +msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void +msgbuf_clear(struct msgbuf *msgbuf) +{ + struct buf *buf; + + while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) + buf_dequeue(msgbuf, buf); +} + +int +msgbuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct buf *buf, *next; + int i = 0; + ssize_t n; + struct msghdr msg; + + bzero(&iov, sizeof(iov)); + bzero(&msg, sizeof(msg)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->size - buf->rpos; + i++; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { + if (errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) /* try later */ + return (0); + else + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (-2); + } + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->size) { + n -= buf->size - buf->rpos; + buf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } + + return (0); +} + +void +buf_enqueue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); + msgbuf->queued++; +} + +void +buf_dequeue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_REMOVE(&msgbuf->bufs, buf, entry); + msgbuf->queued--; + buf_free(buf); +} diff --git a/usr.sbin/relayd/check_icmp.c b/usr.sbin/relayd/check_icmp.c new file mode 100644 index 00000000000..df92b444af9 --- /dev/null +++ b/usr.sbin/relayd/check_icmp.c @@ -0,0 +1,212 @@ +/* $OpenBSD: check_icmp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <limits.h> +#include <event.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#include "hostated.h" + +int check_icmp6(struct host *, int, int); +int check_icmp4(struct host *, int, int); +int in_cksum(u_short *, int); + +int check_icmp(struct host *host, int s, int s6, int timeout) +{ + if (host->ss.ss_family == AF_INET) + return (check_icmp4(host, s, timeout)); + else + return (check_icmp6(host, s6, timeout)); +} + +int check_icmp6(struct host *host, int s, int timeout) +{ + struct sockaddr *to; + struct icmp6_hdr *icp; + int ident; + ssize_t i; + int cc; + int datalen = (64 - 8); + u_char packet[datalen]; + fd_set fdset; + socklen_t len; + struct timeval tv; + + to = (struct sockaddr *)&host->ss; + ident = getpid() & 0xFFFF; + len = sizeof(struct sockaddr_in6); + + bzero(&packet, sizeof(packet)); + icp = (struct icmp6_hdr *)packet; + icp->icmp6_type = ICMP6_ECHO_REQUEST; + icp->icmp6_code = 0; + icp->icmp6_seq = 1; + icp->icmp6_id = ident; + + memset((packet + sizeof(*icp)), 'X', datalen); + cc = datalen + 8; + + i = sendto(s, packet, cc, 0, to, len); + + if (i < 0 || i != cc) { + log_warn("check_icmp6: cannot send ping"); + return (HOST_UNKNOWN); + } + + tv.tv_sec = timeout / 1000; + tv.tv_usec = timeout % 1000; + FD_ZERO(&fdset); + FD_SET(s, &fdset); + switch (select(s + 1, &fdset, NULL, NULL, &tv)) { + case -1: + if (errno == EINTR) { + log_warnx("check_icmp6: interrupted"); + return (HOST_UNKNOWN); + } else + fatal("check_icmp6: select"); + case 0: + log_debug("check_icmp6: timeout"); + return (HOST_DOWN); + default: + bzero(&packet, sizeof(packet)); + i = recvfrom(s, packet, cc, 0, to, &len); + if (i < 0 || i != cc) { + log_warn("check_icmp6: did not receive valid ping"); + return (HOST_DOWN); + } + icp = (struct icmp6_hdr *)(packet); + if (icp->icmp6_id != ident) { + log_warnx("check_icmp6: did not receive valid ident"); + return (HOST_DOWN); + } + break; + } + return (HOST_UP); +} + +int check_icmp4(struct host *host, int s, int timeout) +{ + struct sockaddr *to; + struct icmp *icp; + int ident; + ssize_t i; + int cc; + int datalen = (64 - 8); + u_char packet[datalen]; + fd_set fdset; + socklen_t len; + struct timeval tv; + + to = (struct sockaddr *)&host->ss; + ident = getpid() & 0xFFFF; + len = sizeof(struct sockaddr_in); + + bzero(&packet, sizeof(packet)); + icp = (struct icmp *)packet; + icp->icmp_type = htons(ICMP_ECHO); + icp->icmp_code = 0; + icp->icmp_seq = htons(1); + icp->icmp_id = htons(ident); + icp->icmp_cksum = 0; + + memset(icp->icmp_data, 'X', datalen); + cc = datalen + 8; + icp->icmp_cksum = in_cksum((u_short *)icp, cc); + + i = sendto(s, packet, cc, 0, to, len); + + if (i < 0 || i != cc) { + log_warn("check_icmp4: cannot send ping"); + return (HOST_UNKNOWN); + } + + tv.tv_sec = timeout / 1000; + tv.tv_usec = timeout % 1000; + FD_ZERO(&fdset); + FD_SET(s, &fdset); + switch (select(s + 1, &fdset, NULL, NULL, &tv)) { + case -1: + if (errno == EINTR) { + log_warnx("check_icmp4: ping interrupted"); + return (HOST_UNKNOWN); + } else + fatal("check_icmp4: select"); + case 0: + log_debug("check_icmp4: timeout"); + return (HOST_DOWN); + default: + bzero(&packet, sizeof(packet)); + i = recvfrom(s, packet, cc, 0, to, &len); + if (i < 0 || i != cc) { + log_warn("check_icmp4: did not receive valid ping"); + return (HOST_DOWN); + } + icp = (struct icmp *)(packet + sizeof(struct ip)); + if (ntohs(icp->icmp_id) != ident) { + log_warnx("check_icmp4: did not receive valid ident"); + return (HOST_DOWN); + } + break; + } + return (HOST_UP); +} + +/* from ping.c */ +int +in_cksum(u_short *addr, int len) +{ + int nleft = len; + u_short *w = addr; + int sum = 0; + u_short answer = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + *(u_char *)(&answer) = *(u_char *)w ; + sum += answer; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return(answer); +} diff --git a/usr.sbin/relayd/check_tcp.c b/usr.sbin/relayd/check_tcp.c new file mode 100644 index 00000000000..5ef386731f9 --- /dev/null +++ b/usr.sbin/relayd/check_tcp.c @@ -0,0 +1,109 @@ +/* $OpenBSD: check_tcp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <net/if.h> +#include <limits.h> +#include <event.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "hostated.h" + +int +check_tcp(struct host *host, struct table *table) +{ + int sock; + + if ((sock = tcp_connect(host, table)) <= 0) + return (sock); + close(sock); + return (HOST_UP); +} + +int +tcp_connect(struct host *host, struct table *table) +{ + int s; + socklen_t len; + struct timeval tv; + struct sockaddr sa; + fd_set fdset; + + switch (host->ss.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&host->ss)->sin_port = + htons(table->port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)&host->ss)->sin6_port = + htons(table->port); + break; + } + + len = ((struct sockaddr *)&host->ss)->sa_len; + + if ((s = socket(host->ss.ss_family, SOCK_STREAM, 0)) == -1) + fatal("check_tcp: cannot create socket"); + + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + fatal("check_tcp: cannot set non blocking socket"); + + if (connect(s, (struct sockaddr *)&host->ss, len) == -1) { + if (errno != EINPROGRESS && errno != EWOULDBLOCK) { + close(s); + return (HOST_DOWN); + } + } else + return (s); + + tv.tv_sec = table->timeout / 1000; + tv.tv_usec = table->timeout % 1000; + FD_ZERO(&fdset); + FD_SET(s, &fdset); + + switch(select(s + 1, NULL, &fdset, NULL, &tv)) { + case -1: + if (errno != EINTR) + fatal("check_tcp: select"); + else + return(HOST_UNKNOWN); + case 0: + close(s); + return (HOST_DOWN); + default: + if (getpeername(s, &sa, &len) == -1) { + if (errno == ENOTCONN) { + close(s); + return (HOST_DOWN); + } else { + log_debug("check_tcp: unknown peername"); + close(s); + return (HOST_UNKNOWN); + } + } else + return (s); + } + return (HOST_UNKNOWN); +} diff --git a/usr.sbin/relayd/control.c b/usr.sbin/relayd/control.c new file mode 100644 index 00000000000..2f994e458f4 --- /dev/null +++ b/usr.sbin/relayd/control.c @@ -0,0 +1,340 @@ +/* $OpenBSD: control.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <net/if.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> + +#include "hostated.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_connlist ctl_conns; + +int control_imsg_relay(struct imsg *imsg); + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +int +control_init(void) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("control_init: socket"); + return (-1); + } + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path)); + + if (unlink(HOSTATED_SOCKET) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", HOSTATED_SOCKET); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("control_init: bind: %s", HOSTATED_SOCKET); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(HOSTATED_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(HOSTATED_SOCKET); + return (-1); + } + + session_socket_blockmode(fd, BM_NONBLOCK); + control_state.fd = fd; + + return (0); +} + +int +control_listen(void) +{ + + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("control_listen: listen"); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST, + control_accept, NULL); + event_add(&control_state.ev, NULL); + + return (0); +} + +void +control_cleanup(void) +{ + + unlink(HOSTATED_SOCKET); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + len = sizeof(sun); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sun, &len)) == -1) { + if (errno != EWOULDBLOCK && errno != EINTR) + log_warn("control_accept"); + return; + } + + session_socket_blockmode(connfd, BM_NONBLOCK); + + if ((c = malloc(sizeof(struct ctl_conn))) == NULL) { + log_warn("control_accept"); + return; + } + + imsg_init(&c->ibuf, connfd, control_dispatch_imsg); + c->ibuf.events = EV_READ; + event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events, + c->ibuf.handler, &c->ibuf); + event_add(&c->ibuf.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.fd != fd; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.pid != pid; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) + log_warn("control_close: fd %d: not found", fd); + + msgbuf_clear(&c->ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->ibuf.ev); + close(c->ibuf.fd); + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *arg) +{ + struct ctl_conn *c; + struct imsg imsg; + objid_t id; + int n; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_dispatch_imsg: fd %d: not found", fd); + return; + } + + switch (event) { + case EV_READ: + if ((n = imsg_read(&c->ibuf)) <= 0) { + control_close(fd); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&c->ibuf.w) < 0) { + control_close(fd); + return; + } + imsg_event_add(&c->ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(&c->ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_SHOW_SUM: + show(c); + break; + case IMSG_CTL_SERVICE_DISABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (disable_service(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_SERVICE_ENABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (enable_service(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_TABLE_DISABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (disable_table(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_TABLE_ENABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (enable_table(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_HOST_DISABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (disable_host(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_HOST_ENABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (enable_host(c, id)) + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, + NULL, 0); + else + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + NULL, 0); + break; + case IMSG_CTL_SHUTDOWN: + case IMSG_CTL_RELOAD: + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, NULL, 0); + break; + default: + log_debug("control_dispatch_imsg: " + "error handling imsg %d", imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->ibuf); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose(&c->ibuf, imsg->hdr.type, 0, imsg->hdr.pid, + imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); +} + +void +session_socket_blockmode(int fd, enum blockmodes bm) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fnctl F_GETFL"); + + if (bm == BM_NONBLOCK) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fnctl F_SETFL"); +} diff --git a/usr.sbin/relayd/hce.c b/usr.sbin/relayd/hce.c new file mode 100644 index 00000000000..7144357538b --- /dev/null +++ b/usr.sbin/relayd/hce.c @@ -0,0 +1,318 @@ +/* $OpenBSD: hce.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <net/if.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <err.h> +#include <pwd.h> + +#include "hostated.h" + +void hce_sig_handler(int sig, short, void *); +void hce_shutdown(void); +void hce_dispatch_imsg(int, short, void *); +void hce_dispatch_parent(int, short, void *); +void hce_launch_checks(int, short, void *); + +static struct hostated *env = NULL; +struct imsgbuf *ibuf_pfe; +struct imsgbuf *ibuf_main; + +void +hce_sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + hce_shutdown(); + default: + fatalx("hce_sig_handler: unexpected signal"); + } +} + +pid_t +hce(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_pfe2hce[2]) +{ + pid_t pid; + struct passwd *pw; + struct timeval tv; + struct event ev_sigint; + struct event ev_sigterm; + + switch (pid = fork()) { + case -1: + fatal("hce: cannot fork"); + case 0: + break; + default: + return (pid); + } + + env = x_env; + + /* this is needed for icmp tests */ + if ((env->icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) + err(1, "socket"); + if ((env->icmp6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) + err(1, "socket"); + + if ((pw = getpwnam(HOSTATED_USER)) == NULL) + fatal("hce: getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("hce: chroot"); + if (chdir("/") == -1) + fatal("hce: chdir(\"/\")"); + + setproctitle("host check engine"); + hostated_process = PROC_HCE; + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("hce: can't drop privileges"); + + event_init(); + + signal_set(&ev_sigint, SIGINT, hce_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, hce_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + /* setup pipes */ + close(pipe_pfe2hce[1]); + close(pipe_parent2hce[0]); + close(pipe_parent2pfe[0]); + close(pipe_parent2pfe[1]); + + if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL || + (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL) + fatal("hce"); + imsg_init(ibuf_pfe, pipe_pfe2hce[0], hce_dispatch_imsg); + imsg_init(ibuf_main, pipe_parent2hce[1], hce_dispatch_parent); + + ibuf_pfe->events = EV_READ; + event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events, + ibuf_pfe->handler, ibuf_pfe); + event_add(&ibuf_pfe->ev, NULL); + + ibuf_main->events = EV_READ; + event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events, + ibuf_main->handler, ibuf_main); + event_add(&ibuf_main->ev, NULL); + + evtimer_set(&env->ev, hce_launch_checks, NULL); + tv.tv_sec = env->interval; + tv.tv_usec = 0; + evtimer_add(&env->ev, &tv); + + hce_launch_checks(0, 0, NULL); + event_dispatch(); + + hce_shutdown(); + + return (0); +} + +void +hce_launch_checks(int fd, short event, void *arg) +{ + int previous_up; + struct host *host; + struct table *table; + struct ctl_status st; + struct timeval tv; + + tv.tv_sec = env->interval; + tv.tv_usec = 0; + evtimer_add(&env->ev, &tv); + bzero(&st, sizeof(st)); + TAILQ_FOREACH(table, &env->tables, entry) { + if (table->flags & F_DISABLE) + continue; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->flags & F_DISABLE) + continue; + previous_up = host->up; + switch (table->check) { + case CHECK_ICMP: + host->up = check_icmp(host, env->icmp_sock, + env->icmp6_sock, + table->timeout); + break; + case CHECK_TCP: + host->up = check_tcp(host, table); + break; + case CHECK_HTTP_CODE: + host->up = check_http_code(host, table); + break; + case CHECK_HTTP_DIGEST: + host->up = check_http_digest(host, table); + break; + default: + fatalx("hce_launch_checks: unknown check type"); + break; + } + if (host->up != previous_up) { + st.id = host->id; + st.up = host->up; + imsg_compose(ibuf_pfe, IMSG_HOST_STATUS, 0, 0, + &st, sizeof(st)); + } + } + } + /* tell pfe we're finished */ + imsg_compose(ibuf_pfe, IMSG_SYNC, 0, 0, NULL, 0); +} + +void +hce_shutdown(void) +{ + log_info("host check engine exiting"); + _exit(0); +} + +void +hce_dispatch_imsg(int fd, short event, void *ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + objid_t id; + struct host *host; + struct table *table; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("hce_dispatch_imsg: imsg_read_error"); + if (n == 0) + fatalx("hce_dispatch_imsg: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("hce_dispatch_imsg: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("hce_dispatch_imsg: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("hce_dispatch_imsg: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_DISABLE: + memcpy(&id, imsg.data, sizeof(id)); + if ((host = host_find(env, id)) == NULL) + fatalx("hce_dispatch_imsg: desynchronized"); + host->flags |= F_DISABLE; + host->up = HOST_UNKNOWN; + break; + case IMSG_HOST_ENABLE: + memcpy(&id, imsg.data, sizeof(id)); + if ((host = host_find(env, id)) == NULL) + fatalx("hce_dispatch_imsg: desynchronized"); + host->flags &= ~(F_DISABLE); + host->up = HOST_UNKNOWN; + break; + case IMSG_TABLE_DISABLE: + memcpy(&id, imsg.data, sizeof(id)); + if ((table = table_find(env, id)) == NULL) + fatalx("hce_dispatch_imsg: desynchronized"); + table->flags |= F_DISABLE; + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + break; + case IMSG_TABLE_ENABLE: + memcpy(&id, imsg.data, sizeof(id)); + if ((table = table_find(env, id)) == NULL) + fatalx("hce_dispatch_imsg: desynchronized"); + table->flags &= ~(F_DISABLE); + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + break; + default: + log_debug("hce_dispatch_msg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +hce_dispatch_parent(int fd, short event, void * ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("hce_dispatch_parent: imsg_read error"); + if (n == 0) /* connection closed */ + fatalx("hce_dispatch_parent: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("hce_dispatch_parent: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("hce_dispatch_parent: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("hce_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("hce_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } +} diff --git a/usr.sbin/relayd/imsg.c b/usr.sbin/relayd/imsg.c new file mode 100644 index 00000000000..feafd8700c1 --- /dev/null +++ b/usr.sbin/relayd/imsg.c @@ -0,0 +1,181 @@ +/* $OpenBSD: imsg.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <net/if.h> +#include <errno.h> +#include <event.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hostated.h" + +void +imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *)) +{ + msgbuf_init(&ibuf->w); + bzero(&ibuf->r, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); + ibuf->handler = handler; + TAILQ_INIT(&ibuf->fds); +} + +ssize_t +imsg_read(struct imsgbuf *ibuf) +{ + ssize_t n; + + if ((n = recv(ibuf->fd, ibuf->r.buf + ibuf->r.wpos, + sizeof(ibuf->r.buf) - ibuf->r.wpos, 0)) == -1) { + if (errno != EINTR && errno != EAGAIN) { + log_warn("imsg_read: pipe read error"); + return (-1); + } + return (0); + } + + ibuf->r.wpos += n; + + return (n); +} + +ssize_t +imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +{ + size_t av, left, datalen; + + av = ibuf->r.wpos; + + if (IMSG_HEADER_SIZE > av) + return (0); + + memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); + if (imsg->hdr.len < IMSG_HEADER_SIZE || + imsg->hdr.len > MAX_IMSGSIZE) { + log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u", + imsg->hdr.len, imsg->hdr.type); + return (-1); + } + if (imsg->hdr.len > av) + return (0); + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; + if ((imsg->data = malloc(datalen)) == NULL) { + log_warn("imsg_get"); + return (-1); + } + memcpy(imsg->data, ibuf->r.rptr, datalen); + + if (imsg->hdr.len < av) { + left = av - imsg->hdr.len; + memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); + ibuf->r.wpos = left; + } else + ibuf->r.wpos = 0; + + return (datalen + IMSG_HEADER_SIZE); +} + +int +imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, + pid_t pid, void *data, u_int16_t datalen) +{ + struct buf *wbuf; + int n; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + if (imsg_add(wbuf, data, datalen) == -1) + return (-1); + + if ((n = imsg_close(ibuf, wbuf)) < 0) + return (-1); + + return (n); +} + +/* ARGSUSED */ +struct buf * +imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, + pid_t pid, u_int16_t datalen) +{ + struct buf *wbuf; + struct imsg_hdr hdr; + + if (datalen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) { + log_warnx("imsg_create: len %u > MAX_IMSGSIZE; " + "type %u peerid %lu", datalen + IMSG_HEADER_SIZE, + type, peerid); + return (NULL); + } + + hdr.len = (u_int16_t)(datalen + IMSG_HEADER_SIZE); + hdr.type = type; + hdr.peerid = peerid; + if ((hdr.pid = pid) == 0) + hdr.pid = ibuf->pid; + if ((wbuf = buf_open(hdr.len)) == NULL) { + log_warn("imsg_create: buf_open"); + return (NULL); + } + if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) + return (NULL); + + return (wbuf); +} + +int +imsg_add(struct buf *msg, void *data, u_int16_t datalen) +{ + if (datalen) + if (buf_add(msg, data, datalen) == -1) { + log_warnx("imsg_add: buf_add error"); + buf_free(msg); + return (-1); + } + return (datalen); +} + +int +imsg_close(struct imsgbuf *ibuf, struct buf *msg) +{ + int n; + + if ((n = buf_close(&ibuf->w, msg)) < 0) { + log_warnx("imsg_close: buf_close error"); + buf_free(msg); + return (-1); + } + imsg_event_add(ibuf); + + return (n); +} + +void +imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} diff --git a/usr.sbin/relayd/log.c b/usr.sbin/relayd/log.c new file mode 100644 index 00000000000..28a7c9a7d4c --- /dev/null +++ b/usr.sbin/relayd/log.c @@ -0,0 +1,159 @@ +/* $OpenBSD: log.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +/* prototypes */ +void log_init(int); +void vlog(int, const char *, va_list); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *); +void fatalx(const char *); + +int debug; + +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y new file mode 100644 index 00000000000..977b7a74cb1 --- /dev/null +++ b/usr.sbin/relayd/parse.y @@ -0,0 +1,937 @@ +/* $OpenBSD: parse.y,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <netdb.h> +#include <string.h> + +#include "hostated.h" + +struct hostated *conf = NULL; +static FILE *fin = NULL; +static int lineno = 1; +static int errors = 0; +const char *infile; +char *start_state; +objid_t last_service_id = 0; +objid_t last_table_id = 0; +objid_t last_host_id = 0; + +static struct service *service = NULL; +static struct table *table = NULL; + +int yyerror(const char *, ...); +int yyparse(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(FILE *); +int lungetc(int); +int findeol(void); +int yylex(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entries; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); +int cmdline_symset(char *); + +struct address *host_v4(const char *); +struct address *host_v6(const char *); +int host_dns(const char *, struct addresslist *, + int, in_port_t, const char *); +int host(const char *, struct addresslist *, + int, in_port_t, const char *); + +typedef struct { + union { + u_int32_t number; + char *string; + struct host *host; + } v; + int lineno; +} YYSTYPE; + +%} + +%token SERVICE TABLE BACKUP HOST REAL +%token CHECK HTTP HTTPS TCP ICMP EXTERNAL +%token TIMEOUT CODE DIGEST PORT TAG INTERFACE +%token VIRTUAL IP INTERVAL DISABLE +%token ERROR +%token <v.string> STRING +%type <v.string> interface +%type <v.number> number +%type <v.host> host + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar varset '\n' + | grammar main '\n' + | grammar service '\n' + | grammar table '\n' + | grammar error '\n' { errors++; } + ; + +number : STRING { + const char *estr; + + $$ = strtonum($1, 0, UINT_MAX, &estr); + if (estr) { + yyerror("cannot parse number %s : %s", + $1, estr); + free($1); + YYERROR; + } + free($1); + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +main : INTERVAL number { conf->interval = $2; } + ; + +service : SERVICE STRING { + struct service *srv; + + TAILQ_FOREACH(srv, &conf->services, entry) + if (!strcmp(srv->name, $2)) + break; + if (srv != NULL) { + yyerror("service %s defined twice", $2); + free($2); + YYERROR; + } + if ((srv = calloc(1, sizeof (*srv))) == NULL) + fatal("out of memory"); + + if (strlcpy(srv->name, $2, sizeof (srv->name)) >= + sizeof (srv->name)) { + yyerror("service name truncated"); + YYERROR; + } + free ($2); + srv->id = last_service_id++; + if (last_service_id == UINT_MAX) { + yyerror("too many services defined"); + YYERROR; + } + service = srv; + } '{' optnl serviceopts_l '}' { + if (service->table == NULL) { + yyerror("service %s has no table", + service->name); + YYERROR; + } + if (TAILQ_EMPTY(&service->virts)) { + yyerror("service %s has no virtual ip", + service->name); + YYERROR; + } + conf->servicecount++; + if (service->backup == NULL) + service->backup = &conf->empty_table; + else if (service->backup->port != + service->table->port) { + yyerror("service %s uses two different ports " + "for its table and backup table", + service->name); + YYERROR; + } + + if (!(service->flags & F_DISABLE)) + service->flags |= F_ADD; + TAILQ_INSERT_HEAD(&conf->services, service, entry); + } + ; + +serviceopts_l : serviceopts_l serviceoptsl nl + | serviceoptsl optnl + ; + +serviceoptsl : TABLE STRING { + struct table *tb; + + TAILQ_FOREACH(tb, &conf->tables, entry) + if (!strcmp(tb->name, $2)) + break; + if (tb == NULL) { + yyerror("no such table: %s", $2); + free($2); + YYERROR; + } else { + service->table = tb; + service->table->serviceid = service->id; + service->table->flags |= F_USED; + free($2); + } + } + | BACKUP TABLE STRING { + struct table *tb; + + if (service->backup) { + yyerror("backup already specified"); + free($3); + YYERROR; + } + + TAILQ_FOREACH(tb, &conf->tables, entry) + if (!strcmp(tb->name, $3)) + break; + + if (tb == NULL) { + yyerror("no such table: %s", $3); + free($3); + YYERROR; + } else { + service->backup = tb; + service->backup->serviceid = service->id; + service->backup->flags |= (F_USED|F_BACKUP); + free($3); + } + } + | VIRTUAL IP STRING PORT number interface { + if ($5 < 1 || $5 > USHRT_MAX) { + yyerror("invalid port number: %d", $5); + free($3); + free($6); + YYERROR; + } + if (host($3, &service->virts, + SRV_MAX_VIRTS, htons($5), $6) <= 0) { + yyerror("invalid virtual ip: %s", $3); + free($3); + free($6); + YYERROR; + } + free($3); + free($6); + } + | DISABLE { service->flags |= F_DISABLE; } + | TAG STRING { + if (strlcpy(service->tag, $2, sizeof(service->tag)) >= + sizeof(service->tag)) { + yyerror("service tag name truncated"); + free($2); + YYERROR; + } + free($2); + } + ; + +table : TABLE STRING { + struct table *tb; + + TAILQ_FOREACH(tb, &conf->tables, entry) + if (!strcmp(tb->name, $2)) + break; + if (tb != NULL) { + yyerror("table %s defined twice"); + free($2); + YYERROR; + } + + if ((tb = calloc(1, sizeof (*tb))) == NULL) + fatal("out of memory"); + + if (strlcpy(tb->name, $2, sizeof (tb->name)) >= + sizeof (tb->name)) { + yyerror("table name truncated"); + YYERROR; + } + tb->id = last_table_id++; + tb->timeout = CONNECT_TIMEOUT; + if (last_table_id == UINT_MAX) { + yyerror("too many tables defined"); + YYERROR; + } + free ($2); + table = tb; + } '{' optnl tableopts_l '}' { + if (table->port == 0) { + yyerror("table %s has no port", table->name); + YYERROR; + } + if (TAILQ_EMPTY(&table->hosts)) { + yyerror("table %s has no hosts", table->name); + YYERROR; + } + if (table->check == CHECK_NOCHECK) { + yyerror("table %s has no check", table->name); + YYERROR; + } + conf->tablecount++; + TAILQ_INSERT_HEAD(&conf->tables, table, entry); + } + ; + +tableopts_l : tableopts_l tableoptsl nl + | tableoptsl optnl + ; + +tableoptsl : host { + $1->tableid = table->id; + $1->tablename = table->name; + TAILQ_INSERT_HEAD(&table->hosts, $1, entry); + } + | TIMEOUT number { + table->timeout = $2; + } + | CHECK ICMP { + table->check = CHECK_ICMP; + } + | CHECK TCP { + table->check = CHECK_TCP; + } + | CHECK HTTP STRING CODE number { + table->check = CHECK_HTTP_CODE; + table->retcode = $5; + if (strlcpy(table->path, $3, sizeof (table->path)) >= + sizeof (table->path)) { + yyerror("http path truncated"); + free($3); + YYERROR; + } + } + | CHECK HTTP STRING DIGEST STRING { + table->check = CHECK_HTTP_DIGEST; + if (strlcpy(table->path, $3, sizeof (table->path)) >= + sizeof (table->path)) { + yyerror("http path truncated"); + free($3); + free($5); + YYERROR; + } + if (strlcpy(table->digest, $5, sizeof (table->digest)) + >= sizeof (table->digest)) { + yyerror("http digest truncated"); + free($3); + free($5); + YYERROR; + } + free($3); + free($5); + } + | REAL PORT number { + if ($3 < 1 || $3 >= USHRT_MAX) { + yyerror("invalid port number: %d", $3); + YYERROR; + } + table->port = $3; + } + | DISABLE { table->flags |= F_DISABLE; } + ; + +interface : /*empty*/ { $$ = NULL; } + | INTERFACE STRING { $$ = $2; } + ; + +host : HOST STRING { + struct host *r; + struct address *a; + struct addresslist al; + + if ((r = calloc(1, sizeof (*r))) == NULL) + fatal("out of memory"); + + TAILQ_INIT(&al); + if (host($2, &al, 1, 0, NULL) <= 0) { + yyerror("invalid host %s", $2); + free($2); + YYERROR; + } + a = TAILQ_FIRST(&al); + memcpy(&r->ss, &a->ss, sizeof(r->ss)); + free(a); + + if (strlcpy(r->name, $2, sizeof (r->name)) >= + sizeof (r->name)) { + yyerror("host name truncated"); + free($2); + YYERROR; + } else { + r->id = last_host_id++; + if (last_host_id == UINT_MAX) { + yyerror("too many hosts defined"); + YYERROR; + } + free($2); + $$ = r; + } + } + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + errors = 1; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", infile, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + {"backup", BACKUP}, + {"check", CHECK}, + {"code", CODE}, + {"digest", DIGEST}, + {"disable", DISABLE}, + {"external", EXTERNAL}, + {"host", HOST}, + {"http", HTTP}, + {"https", HTTPS}, + {"icmp", ICMP}, + {"interface", INTERFACE}, + {"interval", INTERVAL}, + {"ip", IP}, + {"port", PORT}, + {"real", REAL}, + {"service", SERVICE}, + {"table", TABLE}, + {"tag", TAG}, + {"tcp", TCP}, + {"timeout", TIMEOUT}, + {"virtual", VIRTUAL} + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(FILE *f) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + while ((c = getc(f)) == '\\') { + next = getc(f); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = lineno; + lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(f); + } while (c == '\t' || c == ' '); + ungetc(c, f); + c = ' '; + } + + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(fin); + if (c == '\n') { + lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int endc, c; + int token; + +top: + p = buf; + while ((c = lgetc(fin)) == ' ') + ; /* nothing */ + + yylval.lineno = lineno; + if (c == '#') + while ((c = lgetc(fin)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + endc = c; + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + if (c == endc) { + *p = '\0'; + break; + } + if (c == '\n') { + lineno++; + continue; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + errx(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = lineno; + lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +parse_config(struct hostated *x_conf, const char *filename, int opts) +{ + struct sym *sym, *next; + + conf = x_conf; + + TAILQ_INIT(&conf->services); + TAILQ_INIT(&conf->tables); + memset(&conf->empty_table, 0, sizeof(conf->empty_table)); + conf->empty_table.id = EMPTY_TABLE; + conf->empty_table.flags |= F_DISABLE; + (void)strlcpy(conf->empty_table.name, "empty", + sizeof(conf->empty_table.name)); + + conf->interval = CHECK_INTERVAL; + conf->opts = opts; + + if ((fin = fopen(filename, "r")) == NULL) { + warn("%s", filename); + return (NULL); + } + infile = filename; + yyparse(); + fclose(fin); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entries); + if ((conf->opts & HOSTATED_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + } + + if (TAILQ_EMPTY(&conf->services)) { + log_warnx("no services, nothing to do"); + errors++; + } + + /* Verify that every table is used */ + TAILQ_FOREACH(table, &conf->tables, entry) + if (!(table->flags & F_USED)) { + log_warnx("unused table: %s", table->name); + errors++; + } + + if (errors) { + bzero(&conf, sizeof (*conf)); + return (-1); + } + + return (0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entries)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entries); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entries) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +struct address * +host_v4(const char *s) +{ + struct in_addr ina; + struct sockaddr_in *sain; + struct address *h; + + bzero(&ina, sizeof(ina)); + if (inet_pton(AF_INET, s, &ina) != 1) + return (NULL); + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_family = AF_INET; + sain->sin_addr.s_addr = ina.s_addr; + + return (h); +} + +struct address * +host_v6(const char *s) +{ + struct in6_addr ina6; + struct sockaddr_in6 *sin6; + struct address *h; + + bzero(&ina6, sizeof(ina6)); + if (inet_pton(AF_INET6, s, &ina6) != 1) + return (NULL); + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6)); + + return (h); +} + +int +host_dns(const char *s, struct addresslist *al, int max, + in_port_t port, const char *ifname) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct address *h; + + bzero(&hints, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ + error = getaddrinfo(s, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("host_dns: could not parse \"%s\": %s", s, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res && cnt < max; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + + h->port = port; + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) + log_warnx("host_dns: interface name truncated"); + return (-1); + } + h->ss.ss_family = res->ai_family; + if (res->ai_family == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + } + + TAILQ_INSERT_HEAD(al, h, entry); + cnt++; + } + if (cnt == max && res) { + log_warnx("host_dns: %s resolves to more than %d hosts", + s, max); + } + freeaddrinfo(res0); + + return (cnt); +} + +int +host(const char *s, struct addresslist *al, int max, + in_port_t port, const char *ifname) +{ + struct address *h; + + h = host_v4(s); + + /* IPv6 address? */ + if (h == NULL) + h = host_v6(s); + + if (h != NULL) { + h->port = port; + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) { + log_warnx("host: interface name truncated"); + return (-1); + } + } + + TAILQ_INSERT_HEAD(al, h, entry); + return (1); + } + + return (host_dns(s, al, max, port, ifname)); +} diff --git a/usr.sbin/relayd/pfe.c b/usr.sbin/relayd/pfe.c new file mode 100644 index 00000000000..0a7ff43949f --- /dev/null +++ b/usr.sbin/relayd/pfe.c @@ -0,0 +1,497 @@ +/* $OpenBSD: pfe.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <net/if.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include "hostated.h" + +void pfe_sig_handler(int sig, short, void *); +void pfe_shutdown(void); +void pfe_dispatch_imsg(int, short, void *); +void pfe_dispatch_parent(int, short, void *); + +void pfe_sync(void); + +static struct hostated *env = NULL; + +struct imsgbuf *ibuf_main; +struct imsgbuf *ibuf_hce; + +void +pfe_sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + pfe_shutdown(); + default: + fatalx("pfe_sig_handler: unexpected signal"); + } +} + +pid_t +pfe(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_pfe2hce[2]) +{ + pid_t pid; + struct passwd *pw; + struct event ev_sigint; + struct event ev_sigterm; + + switch (pid = fork()) { + case -1: + fatal("pfe: cannot fork"); + case 0: + break; + default: + return (pid); + } + + env = x_env; + + if (control_init() == -1) + fatalx("pfe: control socket setup failed"); + + init_filter(env); + init_tables(env); + + if ((pw = getpwnam(HOSTATED_USER)) == NULL) + fatal("pfe: getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("pfe: chroot"); + if (chdir("/") == -1) + fatal("pfe: chdir(\"/\")"); + + setproctitle("pf update engine"); + hostated_process = PROC_PFE; + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("pfe: cannot drop privileges"); + + event_init(); + + signal_set(&ev_sigint, SIGINT, pfe_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, pfe_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + /* setup pipes */ + close(pipe_pfe2hce[0]); + close(pipe_parent2pfe[0]); + close(pipe_parent2hce[0]); + close(pipe_parent2hce[1]); + + if ((ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL || + (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL) + fatal("pfe"); + imsg_init(ibuf_hce, pipe_pfe2hce[1], pfe_dispatch_imsg); + imsg_init(ibuf_main, pipe_parent2pfe[1], pfe_dispatch_parent); + + ibuf_hce->events = EV_READ; + event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events, + ibuf_hce->handler, ibuf_hce); + event_add(&ibuf_hce->ev, NULL); + + ibuf_main->events = EV_READ; + event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events, + ibuf_main->handler, ibuf_main); + event_add(&ibuf_main->ev, NULL); + + TAILQ_INIT(&ctl_conns); + control_listen(); + + event_dispatch(); + pfe_shutdown(); + + return (0); +} + +void +pfe_shutdown(void) +{ + flush_rulesets(env); + log_info("pf update engine exiting"); + _exit(0); +} + +void +pfe_dispatch_imsg(int fd, short event, void *ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + struct host *host; + struct table *table; + struct ctl_status st; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("pfe_dispatch_imsg: imsg_read_error"); + if (n == 0) + fatalx("pfe_dispatch_imsg: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("pfe_dispatch_imsg: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("pfe_dispatch_imsg: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("pfe_dispatch_imsg: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_STATUS: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st)) + fatalx("pfe_dispatch_imsg: invalid request"); + memcpy(&st, imsg.data, sizeof(st)); + if ((host = host_find(env, st.id)) == NULL) + fatalx("pfe_dispatch_imsg: invalid host id"); + if (host->up == st.up) { + log_debug("pfe_dispatch_imsg: host %d => %d", + host->id, host->up); + fatalx("pfe_dispatch_imsg: desynchronized"); + } + + if ((table = table_find(env, host->tableid)) == NULL) + fatalx("pfe_dispatch_imsg: invalid table id"); + + log_debug("pfe_dispatch_imsg: state %d for host %u %s", + st.up, host->id, host->name); + + if ((st.up == HOST_UNKNOWN && host->up == HOST_DOWN) || + (st.up == HOST_DOWN && host->up == HOST_UNKNOWN)) { + host->up = st.up; + break; + } + + if (st.up == HOST_UP) { + table->flags |= F_CHANGED; + table->up++; + host->flags |= F_ADD; + host->flags &= ~(F_DEL); + } else { + table->up--; + table->flags |= F_CHANGED; + host->flags |= F_DEL; + host->flags &= ~(F_ADD); + } + host->up = st.up; + break; + case IMSG_SYNC: + pfe_sync(); + break; + default: + log_debug("pfe_dispatch_imsg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +pfe_dispatch_parent(int fd, short event, void * ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + fatalx("pfe_dispatch_parent: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("pfe_dispatch_parent: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("pfe_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("pfe_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } +} + +void +show(struct ctl_conn *c) +{ + struct service *service; + struct host *host; + + TAILQ_FOREACH(service, &env->services, entry) { + imsg_compose(&c->ibuf, IMSG_CTL_SERVICE, 0, 0, + service, sizeof(*service)); + if (service->flags & F_DISABLE) + continue; + + imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0, + service->table, sizeof(*service->table)); + if (!(service->table->flags & F_DISABLE)) + TAILQ_FOREACH(host, &service->table->hosts, entry) + imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0, + host, sizeof(*host)); + + if (service->backup->id == EMPTY_TABLE) + continue; + imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0, + service->backup, sizeof(*service->backup)); + if (!(service->backup->flags & F_DISABLE)) + TAILQ_FOREACH(host, &service->backup->hosts, entry) + imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0, + host, sizeof(*host)); + } + imsg_compose(&c->ibuf, IMSG_CTL_END, 0, 0, NULL, 0); +} + + +int +disable_service(struct ctl_conn *c, objid_t id) +{ + struct service *service; + + if ((service = service_find(env, id)) == NULL) + return (-1); + + if (service->flags & F_DISABLE) + return (0); + + service->flags |= F_DISABLE; + service->flags &= ~(F_ADD); + service->flags |= F_DEL; + service->table->flags |= F_DISABLE; + log_debug("disable_service: disabled service %d", service->id); + pfe_sync(); + return (0); +} + +int +enable_service(struct ctl_conn *c, objid_t id) +{ + struct service *service; + + if ((service = service_find(env, id)) == NULL) + return (-1); + + if (!(service->flags & F_DISABLE)) + return (0); + + service->flags &= ~(F_DISABLE); + service->flags &= ~(F_DEL); + service->flags |= F_ADD; + log_debug("enable_service: enabled service %d", service->id); + + /* XXX: we're syncing twice */ + if (enable_table(c, service->table->id)) + return (-1); + if (enable_table(c, service->backup->id)) + return (-1); + return (0); +} + +int +disable_table(struct ctl_conn *c, objid_t id) +{ + struct table *table; + struct service *service; + struct host *host; + + if (id == EMPTY_TABLE) + return (-1); + if ((table = table_find(env, id)) == NULL) + return (-1); + if ((service = service_find(env, table->serviceid)) == NULL) + fatalx("disable_table: desynchronised"); + + if (table->flags & F_DISABLE) + return (0); + table->flags |= (F_DISABLE|F_CHANGED); + table->up = 0; + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + imsg_compose(ibuf_hce, IMSG_TABLE_DISABLE, 0, 0, &id, sizeof(id)); + log_debug("disable_table: disabled table %d", table->id); + pfe_sync(); + return (0); +} + +int +enable_table(struct ctl_conn *c, objid_t id) +{ + struct service *service; + struct table *table; + struct host *host; + + if (id == EMPTY_TABLE) + return (-1); + if ((table = table_find(env, id)) == NULL) + return (-1); + if ((service = service_find(env, table->serviceid)) == NULL) + fatalx("enable_table: desynchronised"); + + if (!(table->flags & F_DISABLE)) + return (0); + table->flags &= ~(F_DISABLE); + table->flags |= F_CHANGED; + table->up = 0; + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + imsg_compose(ibuf_hce, IMSG_TABLE_ENABLE, 0, 0, &id, sizeof(id)); + log_debug("enable_table: enabled table %d", table->id); + pfe_sync(); + return (0); +} + +int +disable_host(struct ctl_conn *c, objid_t id) +{ + struct host *host; + struct table *table; + + if ((host = host_find(env, id)) == NULL) + return (-1); + + if (host->flags & F_DISABLE) + return (0); + + if (host->up == HOST_UP) { + if ((table = table_find(env, host->tableid)) == NULL) + fatalx("disable_host: invalid table id"); + table->up--; + table->flags |= F_CHANGED; + } + + host->up = HOST_UNKNOWN; + host->flags |= F_DISABLE; + host->flags |= F_DEL; + host->flags &= ~(F_ADD); + + imsg_compose(ibuf_hce, IMSG_HOST_DISABLE, 0, 0, &id, sizeof (id)); + log_debug("disable_host: disabled host %d", host->id); + pfe_sync(); + return (0); +} + +int +enable_host(struct ctl_conn *c, objid_t id) +{ + struct host *host; + + if ((host = host_find(env, id)) == NULL) + return (-1); + + if (!(host->flags & F_DISABLE)) + return (0); + + host->up = HOST_UNKNOWN; + host->flags &= ~(F_DISABLE); + host->flags &= ~(F_DEL); + host->flags &= ~(F_ADD); + + imsg_compose(ibuf_hce, IMSG_HOST_ENABLE, 0, 0, &id, sizeof (id)); + log_debug("enable_host: enabled host %d", host->id); + pfe_sync(); + return (0); +} + +void +pfe_sync(void) +{ + struct service *service; + struct table *active; + int backup; + + TAILQ_FOREACH(service, &env->services, entry) { + backup = (service->flags & F_BACKUP); + service->flags &= ~(F_BACKUP); + service->flags &= ~(F_DOWN); + + if (service->flags & F_DISABLE || + (service->table->up == 0 && service->backup->up == 0)) { + service->flags |= F_DOWN; + active = NULL; + } else if (service->table->up == 0 && service->backup->up > 0) { + service->flags |= F_BACKUP; + active = service->backup; + active->flags |= service->table->flags & F_CHANGED; + active->flags |= service->backup->flags & F_CHANGED; + } else + active = service->table; + + if (active != NULL && active->flags & F_CHANGED) + sync_table(env, service, active); + + service->table->flags &= ~(F_CHANGED); + service->backup->flags &= ~(F_CHANGED); + + if (service->flags & F_DOWN) { + if (service->flags & F_ACTIVE_RULESET) { + flush_table(env, service); + log_debug("pfe_sync: disabling ruleset"); + service->flags &= ~(F_ACTIVE_RULESET); + sync_ruleset(env, service, 0); + } + } else if (!(service->flags & F_ACTIVE_RULESET)) { + log_debug("pfe_sync: enabling ruleset"); + service->flags |= F_ACTIVE_RULESET; + sync_ruleset(env, service, 1); + } + } +} diff --git a/usr.sbin/relayd/pfe_filter.c b/usr.sbin/relayd/pfe_filter.c new file mode 100644 index 00000000000..b7bd7550070 --- /dev/null +++ b/usr.sbin/relayd/pfe_filter.c @@ -0,0 +1,336 @@ +/* $OpenBSD: pfe_filter.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/param.h> + +#include <net/if.h> +#include <net/pfvar.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <limits.h> +#include <fcntl.h> +#include <event.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include "hostated.h" + +struct pfdata { + int dev; + struct pf_anchor *anchor; + struct pfioc_trans pft; + struct pfioc_trans_e pfte; +}; + +int transaction_init(struct hostated *, const char *); +int transaction_commit(struct hostated *); +void kill_tables(struct hostated *); + +void +init_filter(struct hostated *env) +{ + struct pf_status status; + + if ((env->pf = calloc(1, sizeof(*(env->pf)))) == NULL) + fatal("calloc"); + if ((env->pf->dev = open(PF_SOCKET, O_RDWR)) == -1) + fatal("init_filter: cannot open pf socket"); + if (ioctl(env->pf->dev, DIOCGETSTATUS, &status) == -1) + fatal("init_filter: DIOCGETSTATUS"); + if (!status.running) + fatalx("init_filter: pf is disabled"); + log_debug("init_filter: filter init done"); +} + +void +init_tables(struct hostated *env) +{ + int i; + struct service *service; + struct pfr_table *tables; + struct pfioc_table io; + + if ((tables = calloc(env->servicecount, sizeof(*tables))) == NULL) + fatal("calloc"); + i = 0; + + TAILQ_FOREACH(service, &env->services, entry) { + (void)strlcpy(tables[i].pfrt_anchor, HOSTATED_ANCHOR "/", + sizeof(tables[i].pfrt_anchor)); + (void)strlcat(tables[i].pfrt_anchor, service->name, + sizeof(tables[i].pfrt_anchor)); + (void)strlcpy(tables[i].pfrt_name, service->name, + sizeof(tables[i].pfrt_name)); + tables[i].pfrt_flags |= PFR_TFLAG_PERSIST; + i++; + } + if (i != env->servicecount) + fatalx("init_tables: table count modified"); + + memset(&io, 0, sizeof(io)); + io.pfrio_size = env->servicecount; + io.pfrio_esize = sizeof(*tables); + io.pfrio_buffer = tables; + + if (ioctl(env->pf->dev, DIOCRADDTABLES, &io) == -1) + fatal("init_tables: cannot create tables"); + log_debug("created %d tables", io.pfrio_nadd); + + if (io.pfrio_nadd == env->servicecount) + return; + + /* + * clear all tables, since some already existed + */ + TAILQ_FOREACH(service, &env->services, entry) + flush_table(env, service); +} + +void +kill_tables(struct hostated *env) { + struct pfioc_table io; + struct service *service; + + memset(&io, 0, sizeof(io)); + TAILQ_FOREACH(service, &env->services, entry) { + (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)); + (void)strlcat(io.pfrio_table.pfrt_anchor, service->name, + sizeof(io.pfrio_table.pfrt_anchor)); + if (ioctl(env->pf->dev, DIOCRCLRTABLES, &io) == -1) + fatal("kill_tables: ioctl faile: ioctl failed"); + } + log_debug("kill_tables: deleted %d tables", io.pfrio_ndel); +} + +void +sync_table(struct hostated *env, struct service *service, struct table *table) +{ + int i; + struct pfioc_table io; + struct pfr_addr *addlist; + struct sockaddr_in *sain; + struct sockaddr_in6 *sain6; + struct host *host; + + if (table == NULL) + return; + + if (table->up == 0) { + flush_table(env, service); + return; + } + + if ((addlist = calloc(table->up, sizeof(*addlist))) == NULL) + fatal("calloc"); + + memset(&io, 0, sizeof(io)); + io.pfrio_esize = sizeof(struct pfr_addr); + io.pfrio_size = table->up; + io.pfrio_size2 = 0; + io.pfrio_buffer = addlist; + (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)); + (void)strlcat(io.pfrio_table.pfrt_anchor, service->name, + sizeof(io.pfrio_table.pfrt_anchor)); + (void)strlcpy(io.pfrio_table.pfrt_name, service->name, + sizeof(io.pfrio_table.pfrt_name)); + + i = 0; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->up != 1) + continue; + memset(&(addlist[i]), 0, sizeof(addlist[i])); + switch (host->ss.ss_family) { + case AF_INET: + sain = (struct sockaddr_in *)&host->ss; + addlist[i].pfra_af = AF_INET; + memcpy(&(addlist[i].pfra_ip4addr), &sain->sin_addr, + sizeof(sain->sin_addr)); + addlist[i].pfra_net = 32; + break; + case AF_INET6: + sain6 = (struct sockaddr_in6 *)&host->ss; + addlist[i].pfra_af = AF_INET6; + memcpy(&(addlist[i].pfra_ip6addr), &sain6->sin6_addr, + sizeof(sain6->sin6_addr)); + addlist[i].pfra_net = 128; + break; + default: + fatalx("sync_table: unknown address family"); + break; + } + i++; + } + if (i != table->up) + fatalx("sync_table: desynchronized"); + + if (ioctl(env->pf->dev, DIOCRSETADDRS, &io) == -1) + fatal("sync_table: cannot set address list"); + + log_debug("sync_table: table %s: %d added, %d deleted, %d changed", + io.pfrio_table.pfrt_name, + io.pfrio_nadd, io.pfrio_ndel, io.pfrio_nchange); +} + +void +flush_table(struct hostated *env, struct service *service) +{ + struct pfioc_table io; + + memset(&io, 0, sizeof(io)); + (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)); + (void)strlcat(io.pfrio_table.pfrt_anchor, service->name, + sizeof(io.pfrio_table.pfrt_anchor)); + (void)strlcpy(io.pfrio_table.pfrt_name, service->name, + sizeof(io.pfrio_table.pfrt_name)); + if (ioctl(env->pf->dev, DIOCRCLRADDRS, &io) == -1) + fatal("flush_table: cannot flush table"); + log_debug("flush_table: flushed table %s", service->name); + return; +} + +int +transaction_init(struct hostated *env, const char *anchor) +{ + env->pf->pft.size = 1; + env->pf->pft.esize = sizeof env->pf->pfte; + env->pf->pft.array = &env->pf->pfte; + + memset(&env->pf->pfte, 0, sizeof env->pf->pfte); + strlcpy(env->pf->pfte.anchor, anchor, PF_ANCHOR_NAME_SIZE); + env->pf->pfte.rs_num = PF_RULESET_RDR; + + if (ioctl(env->pf->dev, DIOCXBEGIN, &env->pf->pft) == -1) + return (-1); + return (0); +} + +int +transaction_commit(struct hostated *env) +{ + if (ioctl(env->pf->dev, DIOCXCOMMIT, &env->pf->pft) == -1) + return (-1); + return (0); +} + +void +sync_ruleset(struct hostated *env, struct service *service, int enable) +{ + struct pfioc_rule rio; + struct pfioc_pooladdr pio; + struct sockaddr_in *sain; + struct sockaddr_in6 *sain6; + struct address *address; + char anchor[PF_ANCHOR_NAME_SIZE]; + + bzero(anchor, sizeof(anchor)); + (void)strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor)); + (void)strlcat(anchor, service->name, sizeof(anchor)); + transaction_init(env, anchor); + + if (!enable) { + transaction_commit(env); + log_debug("sync_ruleset: rules removed"); + return; + } + + TAILQ_FOREACH(address, &service->virts, entry) { + memset(&rio, 0, sizeof(rio)); + memset(&pio, 0, sizeof(pio)); + (void)strlcpy(rio.anchor, anchor, sizeof(rio.anchor)); + + rio.ticket = env->pf->pfte.ticket; + if (ioctl(env->pf->dev, DIOCBEGINADDRS, &pio) == -1) + fatal("sync_ruleset: cannot initialise address pool"); + + rio.pool_ticket = pio.ticket; + rio.rule.af = address->ss.ss_family; + rio.rule.proto = IPPROTO_TCP; + rio.rule.src.addr.type = PF_ADDR_ADDRMASK; + rio.rule.dst.addr.type = PF_ADDR_ADDRMASK; + rio.rule.dst.port_op = PF_OP_EQ; + rio.rule.dst.port[0] = address->port; + rio.rule.rtableid = -1; /* stay in the main routing table */ + rio.rule.action = PF_RDR; + if (strlen(service->tag)) + (void)strlcpy(rio.rule.tagname, service->tag, + sizeof(rio.rule.tagname)); + if (strlen(address->ifname)) + (void)strlcpy(rio.rule.ifname, address->ifname, + sizeof(rio.rule.ifname)); + + if (address->ss.ss_family == AF_INET) { + sain = (struct sockaddr_in *)&address->ss; + + rio.rule.dst.addr.v.a.addr.addr32[0] = + sain->sin_addr.s_addr; + rio.rule.dst.addr.v.a.mask.addr32[0] = 0xffffffff; + + } else { + sain6 = (struct sockaddr_in6 *)&address->ss; + + memcpy(&rio.rule.dst.addr.v.a.addr.v6, + &sain6->sin6_addr.s6_addr, + sizeof(sain6->sin6_addr.s6_addr)); + memset(&rio.rule.dst.addr.v.a.mask.addr8, 0xff, 16); + } + + pio.addr.addr.type = PF_ADDR_TABLE; + (void)strlcpy(pio.addr.addr.v.tblname, service->name, + sizeof(pio.addr.addr.v.tblname)); + if (ioctl(env->pf->dev, DIOCADDADDR, &pio) == -1) + fatal("sync_ruleset: cannot add address to pool"); + + rio.rule.rpool.proxy_port[0] = service->table->port; + rio.rule.rpool.port_op = PF_OP_EQ; + rio.rule.rpool.opts = PF_POOL_ROUNDROBIN; + + if (ioctl(env->pf->dev, DIOCADDRULE, &rio) == -1) + fatal("cannot add rule"); + log_debug("sync_ruleset: rule added"); + } + transaction_commit(env); +} + +void +flush_rulesets(struct hostated *env) +{ + struct service *service; + char anchor[PF_ANCHOR_NAME_SIZE]; + + kill_tables(env); + TAILQ_FOREACH(service, &env->services, entry) { + strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor)); + strlcat(anchor, service->name, sizeof(anchor)); + transaction_init(env, anchor); + transaction_commit(env); + } + strlcpy(anchor, HOSTATED_ANCHOR, sizeof(anchor)); + transaction_init(env, anchor); + transaction_commit(env); + log_debug("flush_rulesets: flushed rules"); +} diff --git a/usr.sbin/relayd/relayd.8 b/usr.sbin/relayd/relayd.8 new file mode 100644 index 00000000000..ea9f7db07be --- /dev/null +++ b/usr.sbin/relayd/relayd.8 @@ -0,0 +1,94 @@ +.\" $OpenBSD: relayd.8,v 1.1 2006/12/16 11:45:07 reyk Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd November 1, 2006 +.Dt HOSTATED 8 +.Os +.Sh NAME +.Nm hostated +.Nd Host Status daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is the host status daemon for server load balancing. +Its main purpose is to maintain pf tables up to date +as well as related pf rdr rules. +To communicate with +.Xr pf 4 +.Nm +uses the anchor facility. To enable +.Nm +to install rulesets through the anchor you will +need the following line in the NAT section of your +.Xr pf.conf 5 +configuration file: +.Bd -literal -offset 2n +rdr-anchor "hostated/*" +.Ed +.Pp +.Nm +manipulates three data types: services, tables and hosts. +Each service represents a +.Xr pf 4 +rdr rule. A service contains at least one table and one virtual ip which +are used to create the proper rule. +Each table contains at least one host, and is mapped to a +.Xr pf 4 +table. Additionnaly, a table can be backed up i.e its content will be swapped +by the content of another table when it is empty. This can be used to serve +static content when a dynamic service goes down. +See +.Xr hostated.conf 5 +for a more detailed explanation of how to configure +.Nm +. +.Pp +.Xr hostatectl 8 +can be used to enable or disable hosts, tables and services as well +as showing the current status of each object. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Do not daemonize. +If this options is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Specify an alternative configurate file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/var/run/hostated.sockXX" -compact +.It /etc/hostated.conf +Default +.Nm +configuration file. +.It /var/run/hostated.sock +Unix-domain socket used for communication with +.Xr hostatectl 8 . +.El +.Sh SEE ALSO +.Xr hostated.conf 5 , +.Xr hostatectl 8 diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c new file mode 100644 index 00000000000..fb67140c8c8 --- /dev/null +++ b/usr.sbin/relayd/relayd.c @@ -0,0 +1,377 @@ +/* $OpenBSD: relayd.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <net/if.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <signal.h> +#include <unistd.h> +#include <pwd.h> + +#include "hostated.h" + +__dead void usage(void); + +void main_sig_handler(int, short, void *); +void main_shutdown(void); +void main_dispatch_pfe(int, short, void *); +void main_dispatch_hce(int, short, void *); +int check_child(pid_t, const char *); + +int pipe_parent2pfe[2]; +int pipe_parent2hce[2]; +int pipe_pfe2hce[2]; + +struct imsgbuf *ibuf_pfe; +struct imsgbuf *ibuf_hce; + +pid_t pfe_pid = 0; +pid_t hce_pid = 0; + +void +main_sig_handler(int sig, short event, void *arg) +{ + int die = 0; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + case SIGCHLD: + if (check_child(pfe_pid, "pf udpate engine")) { + pfe_pid = 0; + die = 1; + } + if (check_child(hce_pid, "host check engine")) { + hce_pid = 0; + die = 1; + } + if (die) + main_shutdown(); + break; + case SIGHUP: + /* reconfigure */ + break; + default: + fatalx("unexpected signal"); + } +} + +/* __dead is for lint */ +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "%s [-dnv] [-f file]\n", __progname); + exit (1); +} + +int main(int argc, char *argv[]) +{ + int c; + int debug; + u_int32_t opts; + struct hostated env; + const char *conffile; + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + + opts = 0; + debug = 0; + conffile = CONF_FILE; + bzero(&env, sizeof (env)); + + for (;(c = getopt(argc, argv, "dnf:v")) != -1;) { + switch (c) { + case 'd': + debug = 1; + break; + case 'n': + opts |= HOSTATED_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'v': + opts |= HOSTATED_OPT_VERBOSE; + break; + default: + usage(); + } + + } + + log_init(debug); + + if (parse_config(&env, conffile, opts)) + exit(1); + + if (env.opts & HOSTATED_OPT_NOACTION) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + if (geteuid()) + errx(1, "need root privileges"); + + if (getpwnam(HOSTATED_USER) == NULL) + errx(1, "unknown user %s", HOSTATED_USER); + + if (!debug) + daemon(1, 0); + + log_info("startup"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2pfe) == -1) + fatal("socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2hce) == -1) + fatal("socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_pfe2hce) == -1) + fatal("socketpair"); + + session_socket_blockmode(pipe_parent2pfe[0], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2pfe[1], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2hce[0], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2hce[1], BM_NONBLOCK); + session_socket_blockmode(pipe_pfe2hce[0], BM_NONBLOCK); + session_socket_blockmode(pipe_pfe2hce[1], BM_NONBLOCK); + + pfe_pid = pfe(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce); + hce_pid = hce(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce); + + setproctitle("parent"); + + event_init(); + + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + + close(pipe_parent2pfe[1]); + close(pipe_parent2hce[1]); + close(pipe_pfe2hce[0]); + close(pipe_pfe2hce[1]); + + if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL || + (ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + + imsg_init(ibuf_pfe, pipe_parent2pfe[0], main_dispatch_pfe); + imsg_init(ibuf_hce, pipe_parent2hce[0], main_dispatch_hce); + + ibuf_pfe->events = EV_READ; + event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events, + ibuf_pfe->handler, ibuf_pfe); + event_add(&ibuf_pfe->ev, NULL); + + ibuf_hce->events = EV_READ; + event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events, + ibuf_hce->handler, ibuf_hce); + event_add(&ibuf_hce->ev, NULL); + + event_dispatch(); + + return (0); +} + +void +main_shutdown(void) +{ + pid_t pid; + + if (pfe_pid) + kill(pfe_pid, SIGTERM); + if (hce_pid) + kill(hce_pid, SIGTERM); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + control_cleanup(); + log_info("terminating"); + exit(0); +} + +int +check_child(pid_t pid, const char *pname) +{ + int status; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("check_child: lost child: %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + log_warnx("check_child: lost child: %s terminated; signal %d", + pname, WTERMSIG(status)); + return (1); + } + } + + return (0); +} + +void +imsg_event_add(struct imsgbuf *ibuf) +{ + ibuf->events = EV_READ; + if (ibuf->w.queued) + ibuf->events |= EV_WRITE; + + event_del(&ibuf->ev); + event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf); + event_add(&ibuf->ev, NULL); +} + +void +main_dispatch_pfe(int fd, short event, void *ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read_error"); + if (n == 0) + fatalx("parent: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_pfe: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("main_dispatch_pfe: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +main_dispatch_hce(int fd, short event, void * ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + fatalx("parent: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_hce: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("main_dispatch_hce: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } +} + +struct host * +host_find(struct hostated *env, objid_t id) +{ + struct table *table; + struct host *host; + + TAILQ_FOREACH(table, &env->tables, entry) + TAILQ_FOREACH(host, &table->hosts, entry) + if (host->id == id) + return (host); + return (NULL); +} + +struct table * +table_find(struct hostated *env, objid_t id) +{ + struct table *table; + + TAILQ_FOREACH(table, &env->tables, entry) + if (table->id == id) + return (table); + return (NULL); +} + +struct service * +service_find(struct hostated *env, objid_t id) +{ + struct service *service; + + TAILQ_FOREACH(service, &env->services, entry) + if (service->id == id) + return (service); + return (NULL); +} diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 new file mode 100644 index 00000000000..349dce672b4 --- /dev/null +++ b/usr.sbin/relayd/relayd.conf.5 @@ -0,0 +1,214 @@ +.\" $OpenBSD: relayd.conf.5,v 1.1 2006/12/16 11:45:07 reyk Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd November 1, 2006 +.Dt HOSTATED.CONF 5 +.Os +.Sh NAME +.Nm hostated.conf +.Nd Host Status daemon configuration file. +.Sh DESCRIPTION +The +.Xr hostated 8 +daemon maintains +.Xr pf 4 +tables up to date. +.Sh SECTIONS +The +.Nm +configuration file is divided into four main sections. +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global settings for +.Xr hostated 8 . +.It Sy Tables +Table definitions describe the content of a +.Xr pf 4 +table and the method used for checking the health of the hosts +they contain. +.It Sy Services +Services will be translated to +.Xr pf 4 +rdr rules if their table or backup table have content. +.El +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, and may contain letters, digits, +and underscores. +Macro names may not be reserved words (for example, +.Ic table , +.Ic service , +or +.Ic timeout ) . +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent +www1="10.0.0.1" +www2="10.0.0.2" +table webhosts { + check tcp + timeout 300 + real port 80 + host $www1 + host $www2 +} +.Ed +.Sh GLOBAL CONFIGURATION +Only one global setting can be set. +.Pp +.Bl -tag -width Ds -compact +.It Xo +.Ic interval Ar number +.Xc +Set the interval in seconds at which the hosts will be checked. +The default interval is 10 seconds. +.El +.Sh TABLES +Tables are used to group a set of hosts that can be checked using the same +method. Only one health-checking method can be used per table. +Table specific configuration directives are described below. +.Bl -tag -width Ds +.It Ic check tcp +Use a simple tcp connect to check that hosts are up. +.It Ic check icmp +Ping hosts in this table to determine wether they are up or not. +This method will automatically use icmp or icmpv6 depending on the +address family of each host. +.It Ic check http Ar path Ic code Ar number +For each host in the table, verify that retrieving the URL +.Ar path +gives the HTTP return code +.Ar number +.It Ic check http Ar path Ic digest Ar string +For each host in the table, verify that retrieving the URL +.Ar path +produces a content whose SHA1 digest is +.Ar digest +. The digest does not take the HTTP headers into account. To compute the +digest you can use this simple command: +.Bd -literal -offset 2n +ftp -o - http://host[:port]/path | sha1 + +.Ed +This will give you a digest of the form +.Bd -literal -offset 2n +a9993e36476816aba3e25717850c26c9cd0d89d + +.Ed +that you can use as-is in your digest statement. +.It Ic timeout Ar number +Set the timeout in milliseconds for each host that is checked. +The default timeout is 200 milliseconds. +.It Ic real port Ar number +When using the tcp or http checking methods, use this port to connect +to hosts. This parameter is mandatory. Main and backup tables need +to have the same real port. +.It Ic host Ar address +Add the host whose address is +.Ar address +to the list of hosts to be checked in this table. +Each table needs at least one host. +.It Ic disable +Start the table disabled, no hosts will be checked in this table. +The table can be later enabled through +.Xr hostatectl 8 . +.El +.Sh SERVICES +Services represent a +.Xr pf 4 +rdr rule, they are used to specify which addresses will be redirected +to the hosts in the specified tables. +The configuration directives that are valid in this context are described +below. +.Bl -tag -width Ds +.It Ic virtual ip Ar address Ic port Ar number +Specify an address and a port that will be used to redirect requests +to the hosts in the main or backup table. +Optionally an interface name can be specified like this +.Bd -literal -offset indent +interface ``ifname'' + +.Ed +to specify which interface the rdr rule will be enabled on. +.It Ic table Ar name +Specify the main table to be used. This is mandatory. +.It Ic backup table Ar name +Specify the table to switch to when all hosts in the main table +are seen as down or disabled. +.It Ic disable +Set the service initially disabled. It can be later enabled through +.It Ic tag Ar name +Automatically tag packets passing through the +.Xr pf 4 +rdr rule with the name supplied. This allows for easier filter rules +in your main +.Xr pf 4 +configuration. +.Xr hostatectl 5 . +.El +.Sh EXAMPLE +This configuration file would create a service 'www' which load-balances +4 hosts and falls back to 1 host containing a ``sorry page'': +.Bd -literal -offset indent +## +## +www1=front-www1.private.example.com +www2=front-www2.private.example.com +www3=front-www3.private.example.com +www4=front-www4.private.example.com + +interval 5 + +table phphosts { + timeout 300 + real port 8080 + check http "/" digest 630aa3c2f... + host $www1 + host $www2 + host $www3 + host $www4 +} + +table sorryhost { + check icmp + disable + timeout 300 + real port 8080 + host sorryhost.private.example.com +} + +service www { + virtual ip www.example.com port 8080 interface trunk0 + virtual ip www6.example.com port 80 interface trunk0 + + tag HOSTATED + table phphosts + backup table sorryhost +} +.Ed +.Sh FILES +.Bl -tag -width "/etc/hostated.conf" -compact +.It Pa /etc/hostated.conf +.Xr hostated 8 +configuration file +.El +.Sh SEE ALSO +.Xr hostated 8 , +.Xr hostatectl 8 . diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h new file mode 100644 index 00000000000..d59a934be48 --- /dev/null +++ b/usr.sbin/relayd/relayd.h @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define CONF_FILE "/etc/hostated.conf" +#define HOSTATED_SOCKET "/var/run/hostated.sock" +#define PF_SOCKET "/dev/pf" +#define HOSTATED_USER "_hostated" +#define HOSTATED_ANCHOR "hostated" +#define CONNECT_TIMEOUT 200 +#define CHECK_INTERVAL 10 +#define EMPTY_TABLE UINT_MAX +#define TABLE_NAME_SIZE 16 +#define TAG_NAME_SIZE 16 +#define SRV_NAME_SIZE 16 +#define SRV_MAX_VIRTS 16 + +#define READ_BUF_SIZE 65535 + +/* buffer */ +struct buf { + TAILQ_ENTRY(buf) entry; + u_char *buf; + size_t size; + size_t max; + size_t wpos; + size_t rpos; +}; + +struct msgbuf { + TAILQ_HEAD(, buf) bufs; + u_int32_t queued; + int fd; +}; + +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 8192 + +struct buf_read { + u_char buf[READ_BUF_SIZE]; + u_char *rptr; + size_t wpos; +}; + +struct imsgbuf { + TAILQ_HEAD(, imsg_fd) fds; + struct buf_read r; + struct msgbuf w; + struct event ev; + void (*handler)(int, short, void *); + int fd; + pid_t pid; + short events; +}; + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_OK, /* answer to hostatectl requests */ + IMSG_CTL_FAIL, + IMSG_CTL_END, + IMSG_CTL_SERVICE, + IMSG_CTL_TABLE, + IMSG_CTL_HOST, + IMSG_CTL_SHOW_SUM, /* hostatectl requests */ + IMSG_CTL_SERVICE_ENABLE, + IMSG_CTL_SERVICE_DISABLE, + IMSG_CTL_TABLE_ENABLE, + IMSG_CTL_TABLE_DISABLE, + IMSG_CTL_HOST_ENABLE, + IMSG_CTL_HOST_DISABLE, + IMSG_CTL_SHUTDOWN, + IMSG_CTL_RELOAD, + IMSG_SERVICE_ENABLE, /* notifies from pfe to hce */ + IMSG_SERVICE_DISABLE, + IMSG_TABLE_ENABLE, + IMSG_TABLE_DISABLE, + IMSG_HOST_ENABLE, + IMSG_HOST_DISABLE, + IMSG_TABLE_STATUS, /* notifies from hce to pfe */ + IMSG_HOST_STATUS, + IMSG_SYNC +}; + +struct imsg_hdr { + enum imsg_type type; + u_int16_t len; + u_int32_t peerid; + pid_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + void *data; +}; + +typedef u_int32_t objid_t; + +struct ctl_status { + objid_t id; + int up; +}; + +struct address { + struct sockaddr_storage ss; + in_port_t port; + char ifname[IFNAMSIZ]; + TAILQ_ENTRY(address) entry; +}; +TAILQ_HEAD(addresslist, address); + +#define F_DISABLE 0x01 +#define F_BACKUP 0x02 +#define F_USED 0x04 +#define F_ACTIVE_RULESET 0x04 +#define F_DOWN 0x08 +#define F_ADD 0x10 +#define F_DEL 0x20 +#define F_CHANGED 0x40 + +struct host { + u_int8_t flags; + objid_t id; + objid_t tableid; + char *tablename; + char name[MAXHOSTNAMELEN]; + int up; +#define HOST_DOWN -1 +#define HOST_UNKNOWN 0 +#define HOST_UP 1 + struct sockaddr_storage ss; + TAILQ_ENTRY(host) entry; +}; +TAILQ_HEAD(hostlist, host); + +struct table { + objid_t id; + objid_t serviceid; + u_int8_t flags; + int check; +#define CHECK_NOCHECK 0 +#define CHECK_ICMP 1 +#define CHECK_TCP 2 +#define CHECK_HTTP_CODE 3 +#define CHECK_HTTP_DIGEST 4 + int up; + in_port_t port; + int retcode; + int timeout; + char name[TABLE_NAME_SIZE]; + char path[MAXPATHLEN]; + char digest[41]; /* length of sha1 digest * 2 */ + struct hostlist hosts; + TAILQ_ENTRY(table) entry; +}; +TAILQ_HEAD(tablelist, table); + +struct service { + objid_t id; + u_int8_t flags; + in_port_t port; + char name[SRV_NAME_SIZE]; + char tag[TAG_NAME_SIZE]; + struct addresslist virts; + struct table *table; + struct table *backup; /* use this if no host up */ + TAILQ_ENTRY(service) entry; +}; +TAILQ_HEAD(servicelist, service); + +enum { + PROC_MAIN, + PROC_PFE, + PROC_HCE +} hostated_process; + +struct hostated { + u_int8_t opts; +#define HOSTATED_OPT_VERBOSE 0x01 +#define HOSTATED_OPT_NOACTION 0x04 + struct pfdata *pf; + int interval; + int icmp_sock; + int icmp6_sock; + int tablecount; + int servicecount; + struct table empty_table; + struct event ev; + struct tablelist tables; + struct servicelist services; +}; + +/* initially control.h */ +struct { + struct event ev; + int fd; +} control_state; + +enum blockmodes { + BM_NORMAL, + BM_NONBLOCK +}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgbuf ibuf; + +}; +TAILQ_HEAD(ctl_connlist, ctl_conn); + +/* control.c */ +int control_init(void); +int control_listen(void); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); +void control_cleanup(void); + +void session_socket_blockmode(int, enum blockmodes); + +extern struct ctl_connlist ctl_conns; + +/* parse.y */ +int parse_config(struct hostated *, const char *, int); + +/* log.c */ +void log_init(int); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *); +void fatalx(const char *); + +/* buffer.c */ +struct buf *buf_open(size_t); +struct buf *buf_dynamic(size_t, size_t); +int buf_add(struct buf *, void *, size_t); +void *buf_reserve(struct buf *, size_t); +void *buf_seek(struct buf *, size_t, size_t); +int buf_close(struct msgbuf *, struct buf *); +void buf_free(struct buf *); +void msgbuf_init(struct msgbuf *); +void msgbuf_clear(struct msgbuf *); +int msgbuf_write(struct msgbuf *); + +/* imsg.c */ +void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *)); +ssize_t imsg_read(struct imsgbuf *); +ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t, + void *, u_int16_t); +struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t, + u_int16_t); +int imsg_add(struct buf *, void *, u_int16_t); +int imsg_close(struct imsgbuf *, struct buf *); +void imsg_free(struct imsg *); +void imsg_event_add(struct imsgbuf *); /* needs to be provided externally */ + +/* pfe.c */ +pid_t pfe(struct hostated *, int [2], int [2], int [2]); +void show(struct ctl_conn *); +int enable_service(struct ctl_conn *, objid_t); +int enable_table(struct ctl_conn *, objid_t); +int enable_host(struct ctl_conn *, objid_t); +int disable_service(struct ctl_conn *, objid_t); +int disable_table(struct ctl_conn *, objid_t); +int disable_host(struct ctl_conn *, objid_t); + +/* pfe_filter.c */ +void init_filter(struct hostated *); +void init_tables(struct hostated *); +void flush_table(struct hostated *, struct service *); +void sync_table(struct hostated *, struct service *, struct table *); +void sync_ruleset(struct hostated *, struct service *, int); +void flush_rulesets(struct hostated *); + +/* hce.c */ +pid_t hce(struct hostated *, int [2], int [2], int [2]); + +/* check_icmp.c */ +int check_icmp(struct host *, int, int, int); + +/* check_tcp.c */ +int check_tcp(struct host *, struct table *); +int tcp_connect(struct host *, struct table *); + +/* check_tcp.c */ +int check_http_code(struct host *, struct table *); +int check_http_digest(struct host *, struct table *); + +/* hostated.c */ +struct host *host_find(struct hostated *, objid_t); +struct table *table_find(struct hostated *, objid_t); +struct service *service_find(struct hostated *, objid_t); |