diff options
62 files changed, 15144 insertions, 0 deletions
diff --git a/usr.sbin/hostatectl/Makefile b/usr.sbin/hostatectl/Makefile new file mode 100644 index 00000000000..cf9a63acd8b --- /dev/null +++ b/usr.sbin/hostatectl/Makefile @@ -0,0 +1,16 @@ +# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $ + +.PATH: ${.CURDIR}/../hostated + +PROG= hostatectl +SRCS= buffer.c imsg.c log.c hostatectl.c parser.c + +MAN= hostatectl.8 + +CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded + +.include <bsd.prog.mk> diff --git a/usr.sbin/hostatectl/hostatectl.8 b/usr.sbin/hostatectl/hostatectl.8 new file mode 100644 index 00000000000..fec16e42c89 --- /dev/null +++ b/usr.sbin/hostatectl/hostatectl.8 @@ -0,0 +1,69 @@ +.\" $OpenBSD: hostatectl.8,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 HOSTATECTL 8 +.Os +.Sh NAME +.Nm hostatectl +.Nd control the host status daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr hostated 8 +daemon. +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm show +.It Cm show summary +Show status of services, tables and hosts. +.It Cm service disable id +Disable a service. If it has +.Xr pf 4 +redirection rules installed, remove them. Mark the service's main table and - +if applicable - backup table disabled as well. +.It Cm service enable id +Enable a service. Mark the service's main table and - if applicable - backup +table enabled as well. +.It Cm table disable id +Disable a table. Consider all hosts disabled. If it is a +main table of a service which has a non-empty backup table, +swap the contents of the +.Xr pf 4 +table with those of the backup table. +.It Cm table enable id +Enable a table. Start doing checks for all hosts that aren't +individually disabled again. +.It Cm host disable id +Disable a host. Treat it as though it were always down. +.It Cm host enable id +Enable the host. Start checking its health again. +.El +.Sh FILES +.Bl -tag -width "/var/run/hostated.sockXX" -compact +.It /var/run/hostated.sock +Unix-domain socket used for communication with +.Xr hostated 8 . +.El +.Sh SEE ALSO +.Xr hostated.conf 5 , +.Xr hostated 8 diff --git a/usr.sbin/hostatectl/hostatectl.c b/usr.sbin/hostatectl/hostatectl.c new file mode 100644 index 00000000000..2865ca78d32 --- /dev/null +++ b/usr.sbin/hostatectl/hostatectl.c @@ -0,0 +1,276 @@ +/* $OpenBSD: hostatectl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 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/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "hostated.h" +#include "parser.h" + +__dead void usage(void); +int show_summary_msg(struct imsg *); +int show_command_output(struct imsg *); +char *print_service_status(int); +char *print_host_status(int, int); +char *print_table_status(int, int); + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname); + exit(1); +} + +/* dummy function so that ospfctl does not need libevent */ +void +imsg_event_add(struct imsgbuf *i) +{ + /* nothing */ +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n; + + /* parse options */ + if ((res = parse(argc - 1, argv + 1)) == NULL) + exit(1); + + /* connect to ospfd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", HOSTATED_SOCKET); + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock, NULL); + done = 0; + + /* process user request */ + switch (res->action) { + case NONE: + usage(); + /* not reached */ + case SHOW_SUM: + imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0); + printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name"); + break; + case SERV_ENABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SERV_DISABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_ENABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_DISABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_ENABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_DISABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SHUTDOWN: + imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0); + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0); + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case SHOW_SUM: + done = show_summary_msg(&imsg); + break; + case SERV_DISABLE: + case SERV_ENABLE: + case TABLE_DISABLE: + case TABLE_ENABLE: + case HOST_DISABLE: + case HOST_ENABLE: + done = show_command_output(&imsg); + break; + case RELOAD: + case SHUTDOWN: + case NONE: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +int +show_summary_msg(struct imsg *imsg) +{ + struct service *service; + struct table *table; + struct host *host; + + switch (imsg->hdr.type) { + case IMSG_CTL_SERVICE: + service = imsg->data; + printf("service\t%4u\t%-16s\t%s\n", + service->id, service->name, + print_service_status(service->flags)); + break; + case IMSG_CTL_TABLE: + table = imsg->data; + printf("table\t%4u\t%-16s\t%s", + table->id, table->name, + print_table_status(table->up, table->flags)); + printf("\n"); + break; + case IMSG_CTL_HOST: + host = imsg->data; + printf("host\t%4u\t%-16s\t%s\n", + host->id, host->name, + print_host_status(host->up, host->flags)); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + break; + } + return (0); +} + +int +show_command_output(struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + printf("command succeeded\n"); + break; + case IMSG_CTL_FAIL: + printf("command failed\n"); + break; + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + } + return (1); +} + +char * +print_service_status(int flags) +{ + if (flags & F_DISABLE) { + return("disabled"); + } else if (flags & F_DOWN) { + return("down"); + } else if (flags & F_BACKUP) { + return("active (using backup table)"); + } else + return("active"); +} + +char * +print_table_status(int up, int fl) +{ + static char buf[1024]; + + bzero(buf, sizeof(buf)); + + if (fl & F_DISABLE) { + snprintf(buf, sizeof(buf) - 1, "disabled"); + } else if (!up) { + snprintf(buf, sizeof(buf) - 1, "empty"); + } else + snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)", + up); + return (buf); +} + +char * +print_host_status(int status, int fl) +{ + if (fl & F_DISABLE) + return("disabled"); + + switch (status) { + case HOST_DOWN: + return("down"); + case HOST_UNKNOWN: + return("unknown"); + case HOST_UP: + return("up"); + default: + errx(1, "invalid status: %d", status); + } +} diff --git a/usr.sbin/hostatectl/parser.c b/usr.sbin/hostatectl/parser.c new file mode 100644 index 00000000000..a2247a907fc --- /dev/null +++ b/usr.sbin/hostatectl/parser.c @@ -0,0 +1,232 @@ +/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.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. + */ + +#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 <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <event.h> + +#include "hostated.h" + +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + HOSTID, + TABLEID, + SERVICEID, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_show[]; +static const struct token t_service[]; +static const struct token t_table[]; +static const struct token t_host[]; +static const struct token t_service_id[]; +static const struct token t_table_id[]; +static const struct token t_host_id[]; + +static const struct token t_main[] = { + {KEYWORD, "show", SHOW_SUM, NULL}, + {KEYWORD, "stop", SHUTDOWN, NULL}, + {KEYWORD, "service", NULL, t_service}, + {KEYWORD, "table", NULL, t_table}, + {KEYWORD, "host", NULL, t_host}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", SERV_DISABLE, t_service_id}, + {KEYWORD, "enable", SERV_ENABLE, t_service_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", TABLE_DISABLE, t_table_id}, + {KEYWORD, "enable", TABLE_ENABLE, t_table_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", HOST_DISABLE, t_host_id}, + {KEYWORD, "enable", HOST_ENABLE, t_host_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service_id[] = { + {SERVICEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table_id[] = { + {TABLEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host_id[] = { + {HOSTID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static struct parse_result res; + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc > 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +const struct token * +match_token(const char *word, const struct token table[]) +{ + u_int i, match; + const struct token *t = NULL; + const char *errstr; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case HOSTID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "host id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case TABLEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "table id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case SERVICEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "service id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " <cr>\n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case SERVICEID: + fprintf(stderr, " <serviceid>\n"); + break; + case TABLEID: + fprintf(stderr, " <tableid>\n"); + break; + case HOSTID: + fprintf(stderr, " <hostid>\n"); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/hostatectl/parser.h b/usr.sbin/hostatectl/parser.h new file mode 100644 index 00000000000..2fef7464faf --- /dev/null +++ b/usr.sbin/hostatectl/parser.h @@ -0,0 +1,39 @@ +/* $OpenBSD: parser.h,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. + */ + +enum actions { + NONE, + SHOW_SUM, + SERV_DISABLE, + SERV_ENABLE, + TABLE_DISABLE, + TABLE_ENABLE, + HOST_DISABLE, + HOST_ENABLE, + SHUTDOWN, + RELOAD +}; + +struct parse_result { + objid_t id; + enum actions action; +}; + +struct parse_result *parse(int, char *[]); +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); diff --git a/usr.sbin/hostated/Makefile b/usr.sbin/hostated/Makefile new file mode 100644 index 00000000000..c04e70f3036 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/buffer.c b/usr.sbin/hostated/buffer.c new file mode 100644 index 00000000000..3abe27b1e62 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/check_http.c b/usr.sbin/hostated/check_http.c new file mode 100644 index 00000000000..3d39ceebe27 --- /dev/null +++ b/usr.sbin/hostated/check_http.c @@ -0,0 +1,144 @@ +/* $OpenBSD: check_http.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 <net/if.h> +#include <sha1.h> +#include <limits.h> +#include <event.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#include "hostated.h" + +struct buf *http_request(struct host *, struct table *, int, const char *); + +struct buf * +http_request(struct host *host, struct table *table, int s, const char *req) +{ + int fl; + ssize_t sz; + char rbuf[1024]; + struct buf *buf; + + if ((fl = fcntl(s, F_GETFL, 0)) == -1) + fatal("http_request: cannot get flags for socket"); + if (fcntl(s, F_SETFL, fl & ~(O_NONBLOCK)) == -1) + fatal("http_request: cannot set blocking socket"); + if ((buf = buf_dynamic(sizeof(rbuf), UINT_MAX)) == NULL) + fatalx("http_request: cannot create dynamic buffer"); + + if (write(s, req, strlen(req)) != (ssize_t) strlen(req)) { + close(s); + return (NULL); + } + for (; (sz = read(s, rbuf, sizeof(rbuf))) != 0; ) { + if (sz == -1) + fatal("http_request: read"); + if (buf_add(buf, rbuf, sz) == -1) + fatal("http_request: buf_add"); + } + return (buf); +} + +int +check_http_code(struct host *host, struct table *table) +{ + int s; + int code; + char scode[4]; + char *req; + char *head; + const char *estr; + struct buf *buf; + + if ((s = tcp_connect(host, table)) <= 0) + return (s); + + asprintf(&req, "HEAD %s HTTP/1.0\r\n\r\n", table->path); + if ((buf = http_request(host, table, s, req)) == NULL) + return (HOST_UNKNOWN); + free(req); + + head = buf->buf; + if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) && + strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) { + log_debug("check_http_code: cannot parse HTTP version"); + close(s); + return (HOST_DOWN); + } + head += strlen("HTTP/1.1 "); + if (strlen(head) < 5) /* code + \r\n */ + return (HOST_DOWN); + strlcpy(scode, head, sizeof(scode)); + code = strtonum(scode, 100, 999, &estr); + if (estr != NULL) { + log_debug("check_http_code: cannot parse HTTP code"); + close(s); + return (HOST_DOWN); + } + if (code != table->retcode) { + log_debug("check_http_code: invalid HTTP code returned"); + close(s); + return (HOST_DOWN); + } + close(s); + return (HOST_UP); +} + +int +check_http_digest(struct host *host, struct table *table) +{ + int s; + char *head; + char *req; + struct buf *buf; + char digest[(SHA1_DIGEST_LENGTH*2)+1]; + + if ((s = tcp_connect(host, table)) <= 0) + return (s); + + asprintf(&req, "GET %s HTTP/1.0\r\n\r\n", table->path); + if ((buf = http_request(host, table, s, req)) == NULL) + return (HOST_UNKNOWN); + free(req); + + head = buf->buf; + if ((head = strstr(head, "\r\n\r\n")) == NULL) { + log_debug("check_http_digest: host %u no end of headers", + host->id); + close(s); + return (HOST_DOWN); + } + head += strlen("\r\n\r\n"); + SHA1Data(head, strlen(head), digest); + close(s); + buf_free(buf); + + if (strcmp(table->digest, digest)) { + log_warnx("check_http_digest: wrong digest for host %u", + host->id); + return(HOST_DOWN); + } + return (HOST_UP); +} diff --git a/usr.sbin/hostated/check_icmp.c b/usr.sbin/hostated/check_icmp.c new file mode 100644 index 00000000000..df92b444af9 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/check_tcp.c b/usr.sbin/hostated/check_tcp.c new file mode 100644 index 00000000000..5ef386731f9 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/control.c b/usr.sbin/hostated/control.c new file mode 100644 index 00000000000..2f994e458f4 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/hce.c b/usr.sbin/hostated/hce.c new file mode 100644 index 00000000000..7144357538b --- /dev/null +++ b/usr.sbin/hostated/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/hostated/hostated.8 b/usr.sbin/hostated/hostated.8 new file mode 100644 index 00000000000..1a74ea55a63 --- /dev/null +++ b/usr.sbin/hostated/hostated.8 @@ -0,0 +1,94 @@ +.\" $OpenBSD: hostated.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/hostated/hostated.c b/usr.sbin/hostated/hostated.c new file mode 100644 index 00000000000..32c0989c4cc --- /dev/null +++ b/usr.sbin/hostated/hostated.c @@ -0,0 +1,377 @@ +/* $OpenBSD: hostated.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/hostated/hostated.conf.5 b/usr.sbin/hostated/hostated.conf.5 new file mode 100644 index 00000000000..c4f7b76ba0f --- /dev/null +++ b/usr.sbin/hostated/hostated.conf.5 @@ -0,0 +1,214 @@ +.\" $OpenBSD: hostated.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/hostated/hostated.h b/usr.sbin/hostated/hostated.h new file mode 100644 index 00000000000..d59a934be48 --- /dev/null +++ b/usr.sbin/hostated/hostated.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); diff --git a/usr.sbin/hostated/imsg.c b/usr.sbin/hostated/imsg.c new file mode 100644 index 00000000000..feafd8700c1 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/log.c b/usr.sbin/hostated/log.c new file mode 100644 index 00000000000..28a7c9a7d4c --- /dev/null +++ b/usr.sbin/hostated/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/hostated/parse.y b/usr.sbin/hostated/parse.y new file mode 100644 index 00000000000..977b7a74cb1 --- /dev/null +++ b/usr.sbin/hostated/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/hostated/pfe.c b/usr.sbin/hostated/pfe.c new file mode 100644 index 00000000000..0a7ff43949f --- /dev/null +++ b/usr.sbin/hostated/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/hostated/pfe_filter.c b/usr.sbin/hostated/pfe_filter.c new file mode 100644 index 00000000000..b7bd7550070 --- /dev/null +++ b/usr.sbin/hostated/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/hoststatectl/Makefile b/usr.sbin/hoststatectl/Makefile new file mode 100644 index 00000000000..cf9a63acd8b --- /dev/null +++ b/usr.sbin/hoststatectl/Makefile @@ -0,0 +1,16 @@ +# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $ + +.PATH: ${.CURDIR}/../hostated + +PROG= hostatectl +SRCS= buffer.c imsg.c log.c hostatectl.c parser.c + +MAN= hostatectl.8 + +CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded + +.include <bsd.prog.mk> diff --git a/usr.sbin/hoststatectl/hoststatectl.8 b/usr.sbin/hoststatectl/hoststatectl.8 new file mode 100644 index 00000000000..0c998053ce6 --- /dev/null +++ b/usr.sbin/hoststatectl/hoststatectl.8 @@ -0,0 +1,69 @@ +.\" $OpenBSD: hoststatectl.8,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 HOSTATECTL 8 +.Os +.Sh NAME +.Nm hostatectl +.Nd control the host status daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr hostated 8 +daemon. +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm show +.It Cm show summary +Show status of services, tables and hosts. +.It Cm service disable id +Disable a service. If it has +.Xr pf 4 +redirection rules installed, remove them. Mark the service's main table and - +if applicable - backup table disabled as well. +.It Cm service enable id +Enable a service. Mark the service's main table and - if applicable - backup +table enabled as well. +.It Cm table disable id +Disable a table. Consider all hosts disabled. If it is a +main table of a service which has a non-empty backup table, +swap the contents of the +.Xr pf 4 +table with those of the backup table. +.It Cm table enable id +Enable a table. Start doing checks for all hosts that aren't +individually disabled again. +.It Cm host disable id +Disable a host. Treat it as though it were always down. +.It Cm host enable id +Enable the host. Start checking its health again. +.El +.Sh FILES +.Bl -tag -width "/var/run/hostated.sockXX" -compact +.It /var/run/hostated.sock +Unix-domain socket used for communication with +.Xr hostated 8 . +.El +.Sh SEE ALSO +.Xr hostated.conf 5 , +.Xr hostated 8 diff --git a/usr.sbin/hoststatectl/hoststatectl.c b/usr.sbin/hoststatectl/hoststatectl.c new file mode 100644 index 00000000000..e0e0999f53d --- /dev/null +++ b/usr.sbin/hoststatectl/hoststatectl.c @@ -0,0 +1,276 @@ +/* $OpenBSD: hoststatectl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 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/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "hostated.h" +#include "parser.h" + +__dead void usage(void); +int show_summary_msg(struct imsg *); +int show_command_output(struct imsg *); +char *print_service_status(int); +char *print_host_status(int, int); +char *print_table_status(int, int); + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname); + exit(1); +} + +/* dummy function so that ospfctl does not need libevent */ +void +imsg_event_add(struct imsgbuf *i) +{ + /* nothing */ +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n; + + /* parse options */ + if ((res = parse(argc - 1, argv + 1)) == NULL) + exit(1); + + /* connect to ospfd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", HOSTATED_SOCKET); + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock, NULL); + done = 0; + + /* process user request */ + switch (res->action) { + case NONE: + usage(); + /* not reached */ + case SHOW_SUM: + imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0); + printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name"); + break; + case SERV_ENABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SERV_DISABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_ENABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_DISABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_ENABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_DISABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SHUTDOWN: + imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0); + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0); + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case SHOW_SUM: + done = show_summary_msg(&imsg); + break; + case SERV_DISABLE: + case SERV_ENABLE: + case TABLE_DISABLE: + case TABLE_ENABLE: + case HOST_DISABLE: + case HOST_ENABLE: + done = show_command_output(&imsg); + break; + case RELOAD: + case SHUTDOWN: + case NONE: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +int +show_summary_msg(struct imsg *imsg) +{ + struct service *service; + struct table *table; + struct host *host; + + switch (imsg->hdr.type) { + case IMSG_CTL_SERVICE: + service = imsg->data; + printf("service\t%4u\t%-16s\t%s\n", + service->id, service->name, + print_service_status(service->flags)); + break; + case IMSG_CTL_TABLE: + table = imsg->data; + printf("table\t%4u\t%-16s\t%s", + table->id, table->name, + print_table_status(table->up, table->flags)); + printf("\n"); + break; + case IMSG_CTL_HOST: + host = imsg->data; + printf("host\t%4u\t%-16s\t%s\n", + host->id, host->name, + print_host_status(host->up, host->flags)); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + break; + } + return (0); +} + +int +show_command_output(struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + printf("command succeeded\n"); + break; + case IMSG_CTL_FAIL: + printf("command failed\n"); + break; + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + } + return (1); +} + +char * +print_service_status(int flags) +{ + if (flags & F_DISABLE) { + return("disabled"); + } else if (flags & F_DOWN) { + return("down"); + } else if (flags & F_BACKUP) { + return("active (using backup table)"); + } else + return("active"); +} + +char * +print_table_status(int up, int fl) +{ + static char buf[1024]; + + bzero(buf, sizeof(buf)); + + if (fl & F_DISABLE) { + snprintf(buf, sizeof(buf) - 1, "disabled"); + } else if (!up) { + snprintf(buf, sizeof(buf) - 1, "empty"); + } else + snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)", + up); + return (buf); +} + +char * +print_host_status(int status, int fl) +{ + if (fl & F_DISABLE) + return("disabled"); + + switch (status) { + case HOST_DOWN: + return("down"); + case HOST_UNKNOWN: + return("unknown"); + case HOST_UP: + return("up"); + default: + errx(1, "invalid status: %d", status); + } +} diff --git a/usr.sbin/hoststatectl/parser.c b/usr.sbin/hoststatectl/parser.c new file mode 100644 index 00000000000..a2247a907fc --- /dev/null +++ b/usr.sbin/hoststatectl/parser.c @@ -0,0 +1,232 @@ +/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.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. + */ + +#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 <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <event.h> + +#include "hostated.h" + +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + HOSTID, + TABLEID, + SERVICEID, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_show[]; +static const struct token t_service[]; +static const struct token t_table[]; +static const struct token t_host[]; +static const struct token t_service_id[]; +static const struct token t_table_id[]; +static const struct token t_host_id[]; + +static const struct token t_main[] = { + {KEYWORD, "show", SHOW_SUM, NULL}, + {KEYWORD, "stop", SHUTDOWN, NULL}, + {KEYWORD, "service", NULL, t_service}, + {KEYWORD, "table", NULL, t_table}, + {KEYWORD, "host", NULL, t_host}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", SERV_DISABLE, t_service_id}, + {KEYWORD, "enable", SERV_ENABLE, t_service_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", TABLE_DISABLE, t_table_id}, + {KEYWORD, "enable", TABLE_ENABLE, t_table_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", HOST_DISABLE, t_host_id}, + {KEYWORD, "enable", HOST_ENABLE, t_host_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service_id[] = { + {SERVICEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table_id[] = { + {TABLEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host_id[] = { + {HOSTID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static struct parse_result res; + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc > 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +const struct token * +match_token(const char *word, const struct token table[]) +{ + u_int i, match; + const struct token *t = NULL; + const char *errstr; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case HOSTID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "host id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case TABLEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "table id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case SERVICEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "service id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " <cr>\n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case SERVICEID: + fprintf(stderr, " <serviceid>\n"); + break; + case TABLEID: + fprintf(stderr, " <tableid>\n"); + break; + case HOSTID: + fprintf(stderr, " <hostid>\n"); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/hoststatectl/parser.h b/usr.sbin/hoststatectl/parser.h new file mode 100644 index 00000000000..2fef7464faf --- /dev/null +++ b/usr.sbin/hoststatectl/parser.h @@ -0,0 +1,39 @@ +/* $OpenBSD: parser.h,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. + */ + +enum actions { + NONE, + SHOW_SUM, + SERV_DISABLE, + SERV_ENABLE, + TABLE_DISABLE, + TABLE_ENABLE, + HOST_DISABLE, + HOST_ENABLE, + SHUTDOWN, + RELOAD +}; + +struct parse_result { + objid_t id; + enum actions action; +}; + +struct parse_result *parse(int, char *[]); +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); diff --git a/usr.sbin/hoststated/Makefile b/usr.sbin/hoststated/Makefile new file mode 100644 index 00000000000..c04e70f3036 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/buffer.c b/usr.sbin/hoststated/buffer.c new file mode 100644 index 00000000000..3abe27b1e62 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/check_http.c b/usr.sbin/hoststated/check_http.c new file mode 100644 index 00000000000..3d39ceebe27 --- /dev/null +++ b/usr.sbin/hoststated/check_http.c @@ -0,0 +1,144 @@ +/* $OpenBSD: check_http.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 <net/if.h> +#include <sha1.h> +#include <limits.h> +#include <event.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#include "hostated.h" + +struct buf *http_request(struct host *, struct table *, int, const char *); + +struct buf * +http_request(struct host *host, struct table *table, int s, const char *req) +{ + int fl; + ssize_t sz; + char rbuf[1024]; + struct buf *buf; + + if ((fl = fcntl(s, F_GETFL, 0)) == -1) + fatal("http_request: cannot get flags for socket"); + if (fcntl(s, F_SETFL, fl & ~(O_NONBLOCK)) == -1) + fatal("http_request: cannot set blocking socket"); + if ((buf = buf_dynamic(sizeof(rbuf), UINT_MAX)) == NULL) + fatalx("http_request: cannot create dynamic buffer"); + + if (write(s, req, strlen(req)) != (ssize_t) strlen(req)) { + close(s); + return (NULL); + } + for (; (sz = read(s, rbuf, sizeof(rbuf))) != 0; ) { + if (sz == -1) + fatal("http_request: read"); + if (buf_add(buf, rbuf, sz) == -1) + fatal("http_request: buf_add"); + } + return (buf); +} + +int +check_http_code(struct host *host, struct table *table) +{ + int s; + int code; + char scode[4]; + char *req; + char *head; + const char *estr; + struct buf *buf; + + if ((s = tcp_connect(host, table)) <= 0) + return (s); + + asprintf(&req, "HEAD %s HTTP/1.0\r\n\r\n", table->path); + if ((buf = http_request(host, table, s, req)) == NULL) + return (HOST_UNKNOWN); + free(req); + + head = buf->buf; + if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) && + strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) { + log_debug("check_http_code: cannot parse HTTP version"); + close(s); + return (HOST_DOWN); + } + head += strlen("HTTP/1.1 "); + if (strlen(head) < 5) /* code + \r\n */ + return (HOST_DOWN); + strlcpy(scode, head, sizeof(scode)); + code = strtonum(scode, 100, 999, &estr); + if (estr != NULL) { + log_debug("check_http_code: cannot parse HTTP code"); + close(s); + return (HOST_DOWN); + } + if (code != table->retcode) { + log_debug("check_http_code: invalid HTTP code returned"); + close(s); + return (HOST_DOWN); + } + close(s); + return (HOST_UP); +} + +int +check_http_digest(struct host *host, struct table *table) +{ + int s; + char *head; + char *req; + struct buf *buf; + char digest[(SHA1_DIGEST_LENGTH*2)+1]; + + if ((s = tcp_connect(host, table)) <= 0) + return (s); + + asprintf(&req, "GET %s HTTP/1.0\r\n\r\n", table->path); + if ((buf = http_request(host, table, s, req)) == NULL) + return (HOST_UNKNOWN); + free(req); + + head = buf->buf; + if ((head = strstr(head, "\r\n\r\n")) == NULL) { + log_debug("check_http_digest: host %u no end of headers", + host->id); + close(s); + return (HOST_DOWN); + } + head += strlen("\r\n\r\n"); + SHA1Data(head, strlen(head), digest); + close(s); + buf_free(buf); + + if (strcmp(table->digest, digest)) { + log_warnx("check_http_digest: wrong digest for host %u", + host->id); + return(HOST_DOWN); + } + return (HOST_UP); +} diff --git a/usr.sbin/hoststated/check_icmp.c b/usr.sbin/hoststated/check_icmp.c new file mode 100644 index 00000000000..df92b444af9 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/check_tcp.c b/usr.sbin/hoststated/check_tcp.c new file mode 100644 index 00000000000..5ef386731f9 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/control.c b/usr.sbin/hoststated/control.c new file mode 100644 index 00000000000..2f994e458f4 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/hce.c b/usr.sbin/hoststated/hce.c new file mode 100644 index 00000000000..7144357538b --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/hoststated.8 b/usr.sbin/hoststated/hoststated.8 new file mode 100644 index 00000000000..6c2ae336645 --- /dev/null +++ b/usr.sbin/hoststated/hoststated.8 @@ -0,0 +1,94 @@ +.\" $OpenBSD: hoststated.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/hoststated/hoststated.c b/usr.sbin/hoststated/hoststated.c new file mode 100644 index 00000000000..3932ab3e723 --- /dev/null +++ b/usr.sbin/hoststated/hoststated.c @@ -0,0 +1,377 @@ +/* $OpenBSD: hoststated.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/hoststated/hoststated.conf.5 b/usr.sbin/hoststated/hoststated.conf.5 new file mode 100644 index 00000000000..1c87944228d --- /dev/null +++ b/usr.sbin/hoststated/hoststated.conf.5 @@ -0,0 +1,214 @@ +.\" $OpenBSD: hoststated.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/hoststated/hoststated.h b/usr.sbin/hoststated/hoststated.h new file mode 100644 index 00000000000..d59a934be48 --- /dev/null +++ b/usr.sbin/hoststated/hoststated.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); diff --git a/usr.sbin/hoststated/imsg.c b/usr.sbin/hoststated/imsg.c new file mode 100644 index 00000000000..feafd8700c1 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/log.c b/usr.sbin/hoststated/log.c new file mode 100644 index 00000000000..28a7c9a7d4c --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/parse.y b/usr.sbin/hoststated/parse.y new file mode 100644 index 00000000000..977b7a74cb1 --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/pfe.c b/usr.sbin/hoststated/pfe.c new file mode 100644 index 00000000000..0a7ff43949f --- /dev/null +++ b/usr.sbin/hoststated/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/hoststated/pfe_filter.c b/usr.sbin/hoststated/pfe_filter.c new file mode 100644 index 00000000000..b7bd7550070 --- /dev/null +++ b/usr.sbin/hoststated/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/relayctl/Makefile b/usr.sbin/relayctl/Makefile new file mode 100644 index 00000000000..cf9a63acd8b --- /dev/null +++ b/usr.sbin/relayctl/Makefile @@ -0,0 +1,16 @@ +# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $ + +.PATH: ${.CURDIR}/../hostated + +PROG= hostatectl +SRCS= buffer.c imsg.c log.c hostatectl.c parser.c + +MAN= hostatectl.8 + +CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded + +.include <bsd.prog.mk> diff --git a/usr.sbin/relayctl/parser.c b/usr.sbin/relayctl/parser.c new file mode 100644 index 00000000000..a2247a907fc --- /dev/null +++ b/usr.sbin/relayctl/parser.c @@ -0,0 +1,232 @@ +/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.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. + */ + +#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 <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <event.h> + +#include "hostated.h" + +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + HOSTID, + TABLEID, + SERVICEID, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_show[]; +static const struct token t_service[]; +static const struct token t_table[]; +static const struct token t_host[]; +static const struct token t_service_id[]; +static const struct token t_table_id[]; +static const struct token t_host_id[]; + +static const struct token t_main[] = { + {KEYWORD, "show", SHOW_SUM, NULL}, + {KEYWORD, "stop", SHUTDOWN, NULL}, + {KEYWORD, "service", NULL, t_service}, + {KEYWORD, "table", NULL, t_table}, + {KEYWORD, "host", NULL, t_host}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", SERV_DISABLE, t_service_id}, + {KEYWORD, "enable", SERV_ENABLE, t_service_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", TABLE_DISABLE, t_table_id}, + {KEYWORD, "enable", TABLE_ENABLE, t_table_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", HOST_DISABLE, t_host_id}, + {KEYWORD, "enable", HOST_ENABLE, t_host_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_service_id[] = { + {SERVICEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table_id[] = { + {TABLEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host_id[] = { + {HOSTID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static struct parse_result res; + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc > 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +const struct token * +match_token(const char *word, const struct token table[]) +{ + u_int i, match; + const struct token *t = NULL; + const char *errstr; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case HOSTID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "host id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case TABLEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "table id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case SERVICEID: + res.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "service id %s is %s", word, errstr); + t = &table[i]; + match++; + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " <cr>\n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case SERVICEID: + fprintf(stderr, " <serviceid>\n"); + break; + case TABLEID: + fprintf(stderr, " <tableid>\n"); + break; + case HOSTID: + fprintf(stderr, " <hostid>\n"); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/relayctl/parser.h b/usr.sbin/relayctl/parser.h new file mode 100644 index 00000000000..2fef7464faf --- /dev/null +++ b/usr.sbin/relayctl/parser.h @@ -0,0 +1,39 @@ +/* $OpenBSD: parser.h,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. + */ + +enum actions { + NONE, + SHOW_SUM, + SERV_DISABLE, + SERV_ENABLE, + TABLE_DISABLE, + TABLE_ENABLE, + HOST_DISABLE, + HOST_ENABLE, + SHUTDOWN, + RELOAD +}; + +struct parse_result { + objid_t id; + enum actions action; +}; + +struct parse_result *parse(int, char *[]); +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); diff --git a/usr.sbin/relayctl/relayctl.8 b/usr.sbin/relayctl/relayctl.8 new file mode 100644 index 00000000000..4a5e8c14b90 --- /dev/null +++ b/usr.sbin/relayctl/relayctl.8 @@ -0,0 +1,69 @@ +.\" $OpenBSD: relayctl.8,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 HOSTATECTL 8 +.Os +.Sh NAME +.Nm hostatectl +.Nd control the host status daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr hostated 8 +daemon. +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm show +.It Cm show summary +Show status of services, tables and hosts. +.It Cm service disable id +Disable a service. If it has +.Xr pf 4 +redirection rules installed, remove them. Mark the service's main table and - +if applicable - backup table disabled as well. +.It Cm service enable id +Enable a service. Mark the service's main table and - if applicable - backup +table enabled as well. +.It Cm table disable id +Disable a table. Consider all hosts disabled. If it is a +main table of a service which has a non-empty backup table, +swap the contents of the +.Xr pf 4 +table with those of the backup table. +.It Cm table enable id +Enable a table. Start doing checks for all hosts that aren't +individually disabled again. +.It Cm host disable id +Disable a host. Treat it as though it were always down. +.It Cm host enable id +Enable the host. Start checking its health again. +.El +.Sh FILES +.Bl -tag -width "/var/run/hostated.sockXX" -compact +.It /var/run/hostated.sock +Unix-domain socket used for communication with +.Xr hostated 8 . +.El +.Sh SEE ALSO +.Xr hostated.conf 5 , +.Xr hostated 8 diff --git a/usr.sbin/relayctl/relayctl.c b/usr.sbin/relayctl/relayctl.c new file mode 100644 index 00000000000..ffb29147dc8 --- /dev/null +++ b/usr.sbin/relayctl/relayctl.c @@ -0,0 +1,276 @@ +/* $OpenBSD: relayctl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 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/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "hostated.h" +#include "parser.h" + +__dead void usage(void); +int show_summary_msg(struct imsg *); +int show_command_output(struct imsg *); +char *print_service_status(int); +char *print_host_status(int, int); +char *print_table_status(int, int); + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname); + exit(1); +} + +/* dummy function so that ospfctl does not need libevent */ +void +imsg_event_add(struct imsgbuf *i) +{ + /* nothing */ +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n; + + /* parse options */ + if ((res = parse(argc - 1, argv + 1)) == NULL) + exit(1); + + /* connect to ospfd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", HOSTATED_SOCKET); + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock, NULL); + done = 0; + + /* process user request */ + switch (res->action) { + case NONE: + usage(); + /* not reached */ + case SHOW_SUM: + imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0); + printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name"); + break; + case SERV_ENABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SERV_DISABLE: + imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_ENABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case TABLE_DISABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_ENABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case HOST_DISABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0, + &res->id, sizeof(res->id)); + break; + case SHUTDOWN: + imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0); + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0); + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case SHOW_SUM: + done = show_summary_msg(&imsg); + break; + case SERV_DISABLE: + case SERV_ENABLE: + case TABLE_DISABLE: + case TABLE_ENABLE: + case HOST_DISABLE: + case HOST_ENABLE: + done = show_command_output(&imsg); + break; + case RELOAD: + case SHUTDOWN: + case NONE: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +int +show_summary_msg(struct imsg *imsg) +{ + struct service *service; + struct table *table; + struct host *host; + + switch (imsg->hdr.type) { + case IMSG_CTL_SERVICE: + service = imsg->data; + printf("service\t%4u\t%-16s\t%s\n", + service->id, service->name, + print_service_status(service->flags)); + break; + case IMSG_CTL_TABLE: + table = imsg->data; + printf("table\t%4u\t%-16s\t%s", + table->id, table->name, + print_table_status(table->up, table->flags)); + printf("\n"); + break; + case IMSG_CTL_HOST: + host = imsg->data; + printf("host\t%4u\t%-16s\t%s\n", + host->id, host->name, + print_host_status(host->up, host->flags)); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + break; + } + return (0); +} + +int +show_command_output(struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + printf("command succeeded\n"); + break; + case IMSG_CTL_FAIL: + printf("command failed\n"); + break; + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + } + return (1); +} + +char * +print_service_status(int flags) +{ + if (flags & F_DISABLE) { + return("disabled"); + } else if (flags & F_DOWN) { + return("down"); + } else if (flags & F_BACKUP) { + return("active (using backup table)"); + } else + return("active"); +} + +char * +print_table_status(int up, int fl) +{ + static char buf[1024]; + + bzero(buf, sizeof(buf)); + + if (fl & F_DISABLE) { + snprintf(buf, sizeof(buf) - 1, "disabled"); + } else if (!up) { + snprintf(buf, sizeof(buf) - 1, "empty"); + } else + snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)", + up); + return (buf); +} + +char * +print_host_status(int status, int fl) +{ + if (fl & F_DISABLE) + return("disabled"); + + switch (status) { + case HOST_DOWN: + return("down"); + case HOST_UNKNOWN: + return("unknown"); + case HOST_UP: + return("up"); + default: + errx(1, "invalid status: %d", status); + } +} 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); |