mirror of
https://github.com/openbsd/src.git
synced 2024-12-22 07:27:59 -08:00
bce5c5dd58
OK tb@
434 lines
10 KiB
C
434 lines
10 KiB
C
/* $OpenBSD: check_tcp.c,v 1.61 2023/07/03 09:38:08 claudio 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.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <limits.h>
|
|
#include <event.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <fnmatch.h>
|
|
#include <sha1.h>
|
|
#include <imsg.h>
|
|
|
|
#include "relayd.h"
|
|
|
|
void tcp_write(int, short, void *);
|
|
void tcp_host_up(struct ctl_tcp_event *);
|
|
void tcp_close(struct ctl_tcp_event *, int);
|
|
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;
|
|
socklen_t len;
|
|
struct timeval tv;
|
|
struct linger lng;
|
|
int he = HCE_TCP_SOCKET_OPTION;
|
|
|
|
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 | SOCK_NONBLOCK, 0)) == -1) {
|
|
if (errno == EMFILE || errno == ENFILE)
|
|
he = HCE_TCP_SOCKET_LIMIT;
|
|
else
|
|
he = HCE_TCP_SOCKET_ERROR;
|
|
goto bad;
|
|
}
|
|
|
|
cte->s = s;
|
|
|
|
bzero(&lng, sizeof(lng));
|
|
if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
|
|
goto bad;
|
|
|
|
if (cte->host->conf.ttl > 0)
|
|
switch (cte->host->conf.ss.ss_family) {
|
|
case AF_INET:
|
|
if (setsockopt(s, IPPROTO_IP, IP_TTL,
|
|
&cte->host->conf.ttl, sizeof(int)) == -1)
|
|
goto bad;
|
|
break;
|
|
case AF_INET6:
|
|
if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
|
|
&cte->host->conf.ttl, sizeof(int)) == -1)
|
|
goto bad;
|
|
break;
|
|
}
|
|
|
|
bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
|
|
if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
|
|
if (errno != EINPROGRESS) {
|
|
he = HCE_TCP_CONNECT_FAIL;
|
|
goto bad;
|
|
}
|
|
}
|
|
|
|
cte->buf = NULL;
|
|
cte->host->up = HOST_UP;
|
|
event_del(&cte->ev);
|
|
event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
|
|
event_add(&cte->ev, &tv);
|
|
return;
|
|
|
|
bad:
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, he);
|
|
}
|
|
|
|
void
|
|
tcp_write(int s, short event, void *arg)
|
|
{
|
|
struct ctl_tcp_event *cte = arg;
|
|
int err;
|
|
socklen_t len;
|
|
|
|
if (event == EV_TIMEOUT) {
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
len = sizeof(err);
|
|
if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
|
|
fatal("%s: getsockopt", __func__);
|
|
if (err != 0) {
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
|
|
return;
|
|
}
|
|
|
|
cte->host->up = HOST_UP;
|
|
tcp_host_up(cte);
|
|
}
|
|
|
|
void
|
|
tcp_close(struct ctl_tcp_event *cte, int status)
|
|
{
|
|
close(cte->s);
|
|
cte->s = -1;
|
|
if (status != 0)
|
|
cte->host->up = status;
|
|
ibuf_free(cte->buf);
|
|
cte->buf = NULL;
|
|
}
|
|
|
|
void
|
|
tcp_host_up(struct ctl_tcp_event *cte)
|
|
{
|
|
switch (cte->table->conf.check) {
|
|
case CHECK_TCP:
|
|
if (cte->table->conf.flags & F_TLS)
|
|
break;
|
|
tcp_close(cte, 0);
|
|
hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
|
|
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_BINSEND_EXPECT:
|
|
case CHECK_SEND_EXPECT:
|
|
cte->validate_read = check_send_expect;
|
|
cte->validate_close = check_send_expect;
|
|
break;
|
|
}
|
|
|
|
if (cte->table->conf.flags & F_TLS) {
|
|
check_tls(cte);
|
|
return;
|
|
}
|
|
|
|
if (cte->table->sendbuf != NULL || cte->table->sendbinbuf != NULL) {
|
|
event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
|
|
&cte->tv_start, &cte->table->conf.timeout, cte);
|
|
return;
|
|
}
|
|
|
|
if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
|
|
fatalx("%s: cannot create dynamic buffer", __func__);
|
|
event_again(&cte->ev, cte->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;
|
|
char *req;
|
|
int bs;
|
|
int len;
|
|
|
|
if (event == EV_TIMEOUT) {
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
if (cte->table->sendbinbuf != NULL) {
|
|
len = ibuf_size(cte->table->sendbinbuf);
|
|
req = ibuf_data(cte->table->sendbinbuf);
|
|
log_debug("%s: table %s sending binary", __func__,
|
|
cte->table->conf.name);
|
|
print_hex(req, 0, len);
|
|
} else {
|
|
len = strlen(cte->table->sendbuf);
|
|
req = cte->table->sendbuf;
|
|
}
|
|
|
|
do {
|
|
bs = write(s, req, len);
|
|
if (bs == -1) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
goto retry;
|
|
log_warn("%s: cannot send request", __func__);
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
|
|
return;
|
|
}
|
|
req += bs;
|
|
len -= bs;
|
|
} while (len > 0);
|
|
|
|
if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
|
|
fatalx("%s: cannot create dynamic buffer", __func__);
|
|
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) {
|
|
if (ibuf_size(cte->buf))
|
|
(void)cte->validate_close(cte);
|
|
else {
|
|
cte->host->he = HCE_TCP_READ_TIMEOUT;
|
|
cte->host->up = HOST_DOWN;
|
|
}
|
|
tcp_close(cte, cte->host->up == HOST_UP ? 0 : HOST_DOWN);
|
|
hce_notify_done(cte->host, cte->host->he);
|
|
return;
|
|
}
|
|
|
|
bzero(rbuf, sizeof(rbuf));
|
|
br = read(s, rbuf, sizeof(rbuf) - 1);
|
|
switch (br) {
|
|
case -1:
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
goto retry;
|
|
tcp_close(cte, HOST_DOWN);
|
|
hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
|
|
return;
|
|
case 0:
|
|
cte->host->up = HOST_DOWN;
|
|
(void)cte->validate_close(cte);
|
|
tcp_close(cte, 0);
|
|
hce_notify_done(cte->host, cte->host->he);
|
|
return;
|
|
default:
|
|
if (ibuf_add(cte->buf, rbuf, br) == -1)
|
|
fatal("%s: buf_add error", __func__);
|
|
if (cte->validate_read != NULL) {
|
|
if (cte->validate_read(cte) != 0)
|
|
goto retry;
|
|
tcp_close(cte, 0);
|
|
hce_notify_done(cte->host, cte->host->he);
|
|
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;
|
|
|
|
if (cte->table->conf.check == CHECK_BINSEND_EXPECT) {
|
|
size_t exlen;
|
|
|
|
exlen = strlen(cte->table->conf.exbuf) / 2;
|
|
log_debug("%s: table %s expecting binary",
|
|
__func__, cte->table->conf.name);
|
|
print_hex(cte->table->conf.exbinbuf, 0, exlen);
|
|
|
|
if (ibuf_size(cte->buf) >= exlen && memcmp(ibuf_data(cte->buf),
|
|
cte->table->conf.exbinbuf, exlen) == 0) {
|
|
cte->host->he = HCE_SEND_EXPECT_OK;
|
|
cte->host->up = HOST_UP;
|
|
return (0);
|
|
} else if (ibuf_size(cte->buf) >= exlen) {
|
|
log_debug("%s: table %s received mismatching binary",
|
|
__func__, cte->table->conf.name);
|
|
print_hex(ibuf_data(cte->buf), 0, ibuf_size(cte->buf));
|
|
}
|
|
} else if (cte->table->conf.check == CHECK_SEND_EXPECT) {
|
|
/*
|
|
* ensure string is nul-terminated.
|
|
*/
|
|
b = strndup(ibuf_data(cte->buf), ibuf_size(cte->buf));
|
|
if (b == NULL)
|
|
fatal("out of memory");
|
|
if (fnmatch(cte->table->conf.exbuf, b, 0) == 0) {
|
|
cte->host->he = HCE_SEND_EXPECT_OK;
|
|
cte->host->up = HOST_UP;
|
|
free(b);
|
|
return (0);
|
|
}
|
|
free(b);
|
|
}
|
|
|
|
cte->host->he = HCE_SEND_EXPECT_FAIL;
|
|
cte->host->up = HOST_UNKNOWN;
|
|
return (1);
|
|
}
|
|
|
|
int
|
|
check_http_code(struct ctl_tcp_event *cte)
|
|
{
|
|
char *head;
|
|
char scode[4];
|
|
const char *estr;
|
|
int code;
|
|
struct host *host;
|
|
|
|
/*
|
|
* ensure string is nul-terminated.
|
|
*/
|
|
if (ibuf_add_zero(cte->buf, 1) == -1)
|
|
fatal("out of memory");
|
|
|
|
head = ibuf_data(cte->buf);
|
|
host = cte->host;
|
|
host->he = HCE_HTTP_CODE_ERROR;
|
|
host->code = 0;
|
|
|
|
if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
|
|
strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
|
|
log_debug("%s: %s failed (cannot parse HTTP version)",
|
|
__func__, 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("%s: %s failed (cannot parse HTTP code)",
|
|
__func__, host->conf.name);
|
|
host->up = HOST_DOWN;
|
|
return (1);
|
|
}
|
|
if (code != cte->table->conf.retcode) {
|
|
log_debug("%s: %s failed (invalid HTTP code %d returned)",
|
|
__func__, host->conf.name, code);
|
|
host->he = HCE_HTTP_CODE_FAIL;
|
|
host->up = HOST_DOWN;
|
|
host->code = code;
|
|
} else {
|
|
host->he = HCE_HTTP_CODE_OK;
|
|
host->up = HOST_UP;
|
|
}
|
|
return (!(host->up == HOST_UP));
|
|
}
|
|
|
|
int
|
|
check_http_digest(struct ctl_tcp_event *cte)
|
|
{
|
|
char *head;
|
|
char digest[SHA1_DIGEST_STRING_LENGTH];
|
|
struct host *host;
|
|
|
|
/*
|
|
* ensure string is nul-terminated.
|
|
*/
|
|
if (ibuf_add_zero(cte->buf, 1) == -1)
|
|
fatal("out of memory");
|
|
|
|
head = ibuf_data(cte->buf);
|
|
host = cte->host;
|
|
host->he = HCE_HTTP_DIGEST_ERROR;
|
|
|
|
if ((head = strstr(head, "\r\n\r\n")) == NULL) {
|
|
log_debug("%s: %s failed (no end of headers)",
|
|
__func__, 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("%s: %s failed (wrong digest)",
|
|
__func__, host->conf.name);
|
|
host->he = HCE_HTTP_DIGEST_FAIL;
|
|
host->up = HOST_DOWN;
|
|
} else {
|
|
host->he = HCE_HTTP_DIGEST_OK;
|
|
host->up = HOST_UP;
|
|
}
|
|
return (!(host->up == HOST_UP));
|
|
}
|