1 /* $OpenBSD: check_tcp.c,v 1.62 2026/03/02 19:28:01 rsadowski Exp $ */
4 * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include <sys/types.h>
21 #include <sys/socket.h>
23 #include <netinet/in.h>
39 void tcp_write(int, short, void *);
40 void tcp_host_up(struct ctl_tcp_event *);
41 void tcp_close(struct ctl_tcp_event *, int);
42 void tcp_send_req(int, short, void *);
43 void tcp_read_buf(int, short, void *);
45 int check_http_code(struct ctl_tcp_event *);
46 int check_http_digest(struct ctl_tcp_event *);
47 int check_send_expect(struct ctl_tcp_event *);
50 check_tcp(struct ctl_tcp_event *cte)
56 int he = HCE_TCP_SOCKET_OPTION;
58 switch (cte->host->conf.ss.ss_family) {
60 ((struct sockaddr_in *)&cte->host->conf.ss)->sin_port =
61 cte->table->conf.port;
64 ((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port =
65 cte->table->conf.port;
69 len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len;
71 if ((s = socket(cte->host->conf.ss.ss_family,
72 SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) {
73 if (errno == EMFILE || errno == ENFILE)
74 he = HCE_TCP_SOCKET_LIMIT;
76 he = HCE_TCP_SOCKET_ERROR;
82 bzero(&lng, sizeof(lng));
83 if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
86 if (cte->host->conf.ttl > 0)
87 switch (cte->host->conf.ss.ss_family) {
89 if (setsockopt(s, IPPROTO_IP, IP_TTL,
90 &cte->host->conf.ttl, sizeof(int)) == -1)
94 if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
95 &cte->host->conf.ttl, sizeof(int)) == -1)
100 bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
101 if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
102 if (errno != EINPROGRESS) {
103 he = HCE_TCP_CONNECT_FAIL;
109 cte->host->up = HOST_UP;
111 event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
112 event_add(&cte->ev, &tv);
116 tcp_close(cte, HOST_DOWN);
117 hce_notify_done(cte->host, he);
121 tcp_write(int s, short event, void *arg)
123 struct ctl_tcp_event *cte = arg;
127 if (event == EV_TIMEOUT) {
128 tcp_close(cte, HOST_DOWN);
129 hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
134 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
135 fatal("%s: getsockopt", __func__);
137 tcp_close(cte, HOST_DOWN);
138 hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
142 cte->host->up = HOST_UP;
147 tcp_close(struct ctl_tcp_event *cte, int status)
152 cte->host->up = status;
158 tcp_host_up(struct ctl_tcp_event *cte)
160 switch (cte->table->conf.check) {
162 if (cte->table->conf.flags & F_TLS)
165 hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
167 case CHECK_HTTP_CODE:
168 cte->validate_read = NULL;
169 cte->validate_close = check_http_code;
171 case CHECK_HTTP_DIGEST:
172 cte->validate_read = NULL;
173 cte->validate_close = check_http_digest;
175 case CHECK_BINSEND_EXPECT:
176 case CHECK_SEND_EXPECT:
177 cte->validate_read = check_send_expect;
178 cte->validate_close = check_send_expect;
182 if (cte->table->conf.flags & F_TLS) {
187 if (cte->table->sendbuf != NULL || cte->table->sendbinbuf != NULL) {
188 event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
189 &cte->tv_start, &cte->table->conf.timeout, cte);
193 if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
194 fatalx("%s: cannot create dynamic buffer", __func__);
195 event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_READ, tcp_read_buf,
196 &cte->tv_start, &cte->table->conf.timeout, cte);
200 tcp_send_req(int s, short event, void *arg)
202 struct ctl_tcp_event *cte = arg;
207 if (event == EV_TIMEOUT) {
208 tcp_close(cte, HOST_DOWN);
209 hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
213 if (cte->table->sendbinbuf != NULL) {
214 len = ibuf_size(cte->table->sendbinbuf);
215 req = ibuf_data(cte->table->sendbinbuf);
216 log_debug("%s: table %s sending binary", __func__,
217 cte->table->conf.name);
218 print_hex(req, 0, len);
220 len = strlen(cte->table->sendbuf);
221 req = cte->table->sendbuf;
225 bs = write(s, req, len);
227 if (errno == EAGAIN || errno == EINTR)
229 log_warn("%s: cannot send request", __func__);
230 tcp_close(cte, HOST_DOWN);
231 hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
238 if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
239 fatalx("%s: cannot create dynamic buffer", __func__);
240 event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
241 &cte->tv_start, &cte->table->conf.timeout, cte);
245 event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
246 &cte->tv_start, &cte->table->conf.timeout, cte);
250 tcp_read_buf(int s, short event, void *arg)
253 char rbuf[SMALL_READ_BUF_SIZE];
254 struct ctl_tcp_event *cte = arg;
256 if (event == EV_TIMEOUT) {
257 if (ibuf_size(cte->buf))
258 (void)cte->validate_close(cte);
260 cte->host->he = HCE_TCP_READ_TIMEOUT;
261 cte->host->up = HOST_DOWN;
263 tcp_close(cte, cte->host->up == HOST_UP ? 0 : HOST_DOWN);
264 hce_notify_done(cte->host, cte->host->he);
268 bzero(rbuf, sizeof(rbuf));
269 br = read(s, rbuf, sizeof(rbuf) - 1);
272 if (errno == EAGAIN || errno == EINTR)
274 tcp_close(cte, HOST_DOWN);
275 hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
278 cte->host->up = HOST_DOWN;
279 (void)cte->validate_close(cte);
281 hce_notify_done(cte->host, cte->host->he);
284 if (ibuf_add(cte->buf, rbuf, br) == -1)
285 fatal("%s: buf_add error", __func__);
286 if (cte->validate_read != NULL) {
287 if (cte->validate_read(cte) != 0)
290 hce_notify_done(cte->host, cte->host->he);
296 event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
297 &cte->tv_start, &cte->table->conf.timeout, cte);
301 check_send_expect(struct ctl_tcp_event *cte)
305 if (cte->table->conf.check == CHECK_BINSEND_EXPECT) {
308 exlen = strlen(cte->table->conf.exbuf) / 2;
309 log_debug("%s: table %s expecting binary",
310 __func__, cte->table->conf.name);
311 print_hex(cte->table->conf.exbinbuf, 0, exlen);
313 if (ibuf_size(cte->buf) >= exlen && memcmp(ibuf_data(cte->buf),
314 cte->table->conf.exbinbuf, exlen) == 0) {
315 cte->host->he = HCE_SEND_EXPECT_OK;
316 cte->host->up = HOST_UP;
318 } else if (ibuf_size(cte->buf) >= exlen) {
319 log_debug("%s: table %s received mismatching binary",
320 __func__, cte->table->conf.name);
321 print_hex(ibuf_data(cte->buf), 0, ibuf_size(cte->buf));
323 } else if (cte->table->conf.check == CHECK_SEND_EXPECT) {
325 * ensure string is nul-terminated.
327 b = strndup(ibuf_data(cte->buf), ibuf_size(cte->buf));
329 fatal("out of memory");
330 if (fnmatch(cte->table->conf.exbuf, b, 0) == 0) {
331 cte->host->he = HCE_SEND_EXPECT_OK;
332 cte->host->up = HOST_UP;
339 cte->host->he = HCE_SEND_EXPECT_FAIL;
340 cte->host->up = HOST_UNKNOWN;
345 check_http_code(struct ctl_tcp_event *cte)
354 * ensure string is nul-terminated.
356 if (ibuf_add_zero(cte->buf, 1) == -1)
357 fatal("out of memory");
359 head = ibuf_data(cte->buf);
361 host->he = HCE_HTTP_CODE_ERROR;
364 if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
365 strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
366 log_debug("%s: %s failed (cannot parse HTTP version)",
367 __func__, host->conf.name);
368 host->up = HOST_DOWN;
371 head += strlen("HTTP/1.1 ");
372 if (strlen(head) < 5) /* code + \r\n */ {
373 host->up = HOST_DOWN;
376 (void)strlcpy(scode, head, sizeof(scode));
377 code = strtonum(scode, 100, 999, &estr);
379 log_debug("%s: %s failed (cannot parse HTTP code)",
380 __func__, host->conf.name);
381 host->up = HOST_DOWN;
384 if (code != cte->table->conf.retcode) {
385 log_debug("%s: %s failed (invalid HTTP code %d returned)",
386 __func__, host->conf.name, code);
387 host->he = HCE_HTTP_CODE_FAIL;
388 host->up = HOST_DOWN;
391 host->he = HCE_HTTP_CODE_OK;
394 return (!(host->up == HOST_UP));
398 check_http_digest(struct ctl_tcp_event *cte)
401 char digest[SHA1_DIGEST_STRING_LENGTH];
405 * ensure string is nul-terminated.
407 if (ibuf_add_zero(cte->buf, 1) == -1)
408 fatal("out of memory");
410 head = ibuf_data(cte->buf);
412 host->he = HCE_HTTP_DIGEST_ERROR;
414 if ((head = strstr(head, "\r\n\r\n")) == NULL) {
415 log_debug("%s: %s failed (no end of headers)",
416 __func__, host->conf.name);
417 host->up = HOST_DOWN;
420 head += strlen("\r\n\r\n");
422 digeststr(cte->table->conf.digest_type, head, strlen(head), digest);
424 if (strcmp(cte->table->conf.digest, digest)) {
425 log_warnx("%s: %s failed (wrong digest)",
426 __func__, host->conf.name);
427 host->he = HCE_HTTP_DIGEST_FAIL;
428 host->up = HOST_DOWN;
430 host->he = HCE_HTTP_DIGEST_OK;
433 return (!(host->up == HOST_UP));