/* $OpenBSD: check_tcp.c,v 1.32 2008/03/03 16:58:41 reyk Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "relayd.h" void tcp_write(int, short, void *); void tcp_host_up(int, struct ctl_tcp_event *); void tcp_send_req(int, short, void *); void tcp_read_buf(int, short, void *); int check_http_code(struct ctl_tcp_event *); int check_http_digest(struct ctl_tcp_event *); int check_send_expect(struct ctl_tcp_event *); void check_tcp(struct ctl_tcp_event *cte) { int s; int type; socklen_t len; struct timeval tv; struct linger lng; switch (cte->host->conf.ss.ss_family) { case AF_INET: ((struct sockaddr_in *)&cte->host->conf.ss)->sin_port = cte->table->conf.port; break; case AF_INET6: ((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port = cte->table->conf.port; break; } len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len; if ((s = socket(cte->host->conf.ss.ss_family, SOCK_STREAM, 0)) == -1) goto bad; bzero(&lng, sizeof(lng)); if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) goto bad; type = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &type, sizeof(type)) == -1) goto bad; if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) goto bad; bcopy(&cte->table->conf.timeout, &tv, sizeof(tv)); if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) { if (errno != EINPROGRESS) goto bad; } cte->host->up = HOST_UP; event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte); event_add(&cte->ev, &tv); return; bad: close(s); cte->host->up = HOST_DOWN; hce_notify_done(cte->host, "check_tcp: cannot connect"); } void tcp_write(int s, short event, void *arg) { struct ctl_tcp_event *cte = arg; int err; socklen_t len; if (event == EV_TIMEOUT) { close(s); cte->host->up = HOST_DOWN; hce_notify_done(cte->host, "tcp_write: connect timed out"); return; } len = sizeof(err); if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len)) fatal("tcp_write: getsockopt"); if (err != 0) { close(s); cte->host->up = HOST_DOWN; hce_notify_done(cte->host, "tcp_write: connect failed"); return; } cte->host->up = HOST_UP; tcp_host_up(s, cte); } void tcp_host_up(int s, struct ctl_tcp_event *cte) { cte->s = s; switch (cte->table->conf.check) { case CHECK_TCP: if (cte->table->conf.flags & F_SSL) break; close(s); hce_notify_done(cte->host, "tcp_host_up: connect successful"); return; case CHECK_HTTP_CODE: cte->validate_read = NULL; cte->validate_close = check_http_code; break; case CHECK_HTTP_DIGEST: cte->validate_read = NULL; cte->validate_close = check_http_digest; break; case CHECK_SEND_EXPECT: cte->validate_read = check_send_expect; cte->validate_close = check_send_expect; break; } if (cte->table->conf.flags & F_SSL) { ssl_transaction(cte); return; } if (cte->table->sendbuf != NULL) { cte->req = cte->table->sendbuf; event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req, &cte->tv_start, &cte->table->conf.timeout, cte); return; } if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) fatalx("tcp_host_up: cannot create dynamic buffer"); event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, &cte->tv_start, &cte->table->conf.timeout, cte); } void tcp_send_req(int s, short event, void *arg) { struct ctl_tcp_event *cte = arg; int bs; int len; if (event == EV_TIMEOUT) { cte->host->up = HOST_DOWN; close(cte->s); hce_notify_done(cte->host, "tcp_send_req: timeout"); return; } len = strlen(cte->req); do { bs = write(s, cte->req, len); if (bs == -1) { if (errno == EAGAIN || errno == EINTR) goto retry; log_warnx("tcp_send_req: cannot send request"); cte->host->up = HOST_DOWN; close(cte->s); hce_notify_done(cte->host, "tcp_send_req: write"); return; } cte->req += bs; len -= bs; } while (len > 0); if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) fatalx("tcp_send_req: cannot create dynamic buffer"); event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, &cte->tv_start, &cte->table->conf.timeout, cte); return; retry: event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req, &cte->tv_start, &cte->table->conf.timeout, cte); } void tcp_read_buf(int s, short event, void *arg) { ssize_t br; char rbuf[SMALL_READ_BUF_SIZE]; struct ctl_tcp_event *cte = arg; if (event == EV_TIMEOUT) { cte->host->up = HOST_DOWN; buf_free(cte->buf); close(s); hce_notify_done(cte->host, "tcp_read_buf: timeout"); return; } bzero(rbuf, sizeof(rbuf)); br = read(s, rbuf, sizeof(rbuf) - 1); switch (br) { case -1: if (errno == EAGAIN || errno == EINTR) goto retry; cte->host->up = HOST_DOWN; buf_free(cte->buf); close(cte->s); hce_notify_done(cte->host, "tcp_read_buf: read failed"); return; case 0: cte->host->up = HOST_DOWN; (void)cte->validate_close(cte); close(cte->s); buf_free(cte->buf); if (cte->host->up == HOST_UP) hce_notify_done(cte->host, "tcp_read_buf: check succeeded"); else hce_notify_done(cte->host, "tcp_read_buf: check failed"); return; default: if (buf_add(cte->buf, rbuf, br) == -1) fatal("tcp_read_buf: buf_add error"); if (cte->validate_read != NULL) { if (cte->validate_read(cte) != 0) goto retry; close(cte->s); buf_free(cte->buf); if (cte->host->up == HOST_UP) hce_notify_done(cte->host, "tcp_read_buf: check succeeded"); else hce_notify_done(cte->host, "tcp_read_buf: check failed"); return; } break; /* retry */ } retry: event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, &cte->tv_start, &cte->table->conf.timeout, cte); } int check_send_expect(struct ctl_tcp_event *cte) { u_char *b; /* * ensure string is nul-terminated. */ b = buf_reserve(cte->buf, 1); if (b == NULL) fatal("out of memory"); *b = '\0'; if (fnmatch(cte->table->conf.exbuf, cte->buf->buf, 0) == 0) { cte->host->up = HOST_UP; return (0); } cte->host->up = HOST_UNKNOWN; /* * go back to original position. */ cte->buf->wpos--; return (1); } int check_http_code(struct ctl_tcp_event *cte) { char *head; char scode[4]; const char *estr; u_char *b; int code; struct host *host; /* * ensure string is nul-terminated. */ b = buf_reserve(cte->buf, 1); if (b == NULL) fatal("out of memory"); *b = '\0'; head = cte->buf->buf; host = cte->host; if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) && strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) { log_debug("check_http_code: %s failed " "(cannot parse HTTP version)", host->conf.name); host->up = HOST_DOWN; return (1); } head += strlen("HTTP/1.1 "); if (strlen(head) < 5) /* code + \r\n */ { host->up = HOST_DOWN; return (1); } (void)strlcpy(scode, head, sizeof(scode)); code = strtonum(scode, 100, 999, &estr); if (estr != NULL) { log_debug("check_http_code: %s failed " "(cannot parse HTTP code)", host->conf.name); host->up = HOST_DOWN; return (1); } if (code != cte->table->conf.retcode) { log_debug("check_http_code: %s failed " "(invalid HTTP code returned)", host->conf.name); host->up = HOST_DOWN; } else host->up = HOST_UP; return (!(host->up == HOST_UP)); } int check_http_digest(struct ctl_tcp_event *cte) { char *head; u_char *b; char digest[SHA1_DIGEST_STRING_LENGTH]; struct host *host; /* * ensure string is nul-terminated. */ b = buf_reserve(cte->buf, 1); if (b == NULL) fatal("out of memory"); *b = '\0'; head = cte->buf->buf; host = cte->host; if ((head = strstr(head, "\r\n\r\n")) == NULL) { log_debug("check_http_digest: %s failed " "(no end of headers)", host->conf.name); host->up = HOST_DOWN; return (1); } head += strlen("\r\n\r\n"); digeststr(cte->table->conf.digest_type, head, strlen(head), digest); if (strcmp(cte->table->conf.digest, digest)) { log_warnx("check_http_digest: %s failed " "(wrong digest)", host->conf.name); host->up = HOST_DOWN; } else host->up = HOST_UP; return (!(host->up == HOST_UP)); }