Blob


1 /* $OpenBSD: check_tcp.c,v 1.62 2026/03/02 19:28:01 rsadowski Exp $ */
3 /*
4 * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
5 *
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.
9 *
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.
17 */
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/socket.h>
23 #include <netinet/in.h>
25 #include <limits.h>
26 #include <event.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <fnmatch.h>
33 #include <sha1.h>
34 #include <imsg.h>
36 #include "relayd.h"
37 #include "log.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 *);
49 void
50 check_tcp(struct ctl_tcp_event *cte)
51 {
52 int s;
53 socklen_t len;
54 struct timeval tv;
55 struct linger lng;
56 int he = HCE_TCP_SOCKET_OPTION;
58 switch (cte->host->conf.ss.ss_family) {
59 case AF_INET:
60 ((struct sockaddr_in *)&cte->host->conf.ss)->sin_port =
61 cte->table->conf.port;
62 break;
63 case AF_INET6:
64 ((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port =
65 cte->table->conf.port;
66 break;
67 }
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;
75 else
76 he = HCE_TCP_SOCKET_ERROR;
77 goto bad;
78 }
80 cte->s = s;
82 bzero(&lng, sizeof(lng));
83 if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
84 goto bad;
86 if (cte->host->conf.ttl > 0)
87 switch (cte->host->conf.ss.ss_family) {
88 case AF_INET:
89 if (setsockopt(s, IPPROTO_IP, IP_TTL,
90 &cte->host->conf.ttl, sizeof(int)) == -1)
91 goto bad;
92 break;
93 case AF_INET6:
94 if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
95 &cte->host->conf.ttl, sizeof(int)) == -1)
96 goto bad;
97 break;
98 }
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;
104 goto bad;
108 cte->buf = NULL;
109 cte->host->up = HOST_UP;
110 event_del(&cte->ev);
111 event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
112 event_add(&cte->ev, &tv);
113 return;
115 bad:
116 tcp_close(cte, HOST_DOWN);
117 hce_notify_done(cte->host, he);
120 void
121 tcp_write(int s, short event, void *arg)
123 struct ctl_tcp_event *cte = arg;
124 int err;
125 socklen_t len;
127 if (event == EV_TIMEOUT) {
128 tcp_close(cte, HOST_DOWN);
129 hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
130 return;
133 len = sizeof(err);
134 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
135 fatal("%s: getsockopt", __func__);
136 if (err != 0) {
137 tcp_close(cte, HOST_DOWN);
138 hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
139 return;
142 cte->host->up = HOST_UP;
143 tcp_host_up(cte);
146 void
147 tcp_close(struct ctl_tcp_event *cte, int status)
149 close(cte->s);
150 cte->s = -1;
151 if (status != 0)
152 cte->host->up = status;
153 ibuf_free(cte->buf);
154 cte->buf = NULL;
157 void
158 tcp_host_up(struct ctl_tcp_event *cte)
160 switch (cte->table->conf.check) {
161 case CHECK_TCP:
162 if (cte->table->conf.flags & F_TLS)
163 break;
164 tcp_close(cte, 0);
165 hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
166 return;
167 case CHECK_HTTP_CODE:
168 cte->validate_read = NULL;
169 cte->validate_close = check_http_code;
170 break;
171 case CHECK_HTTP_DIGEST:
172 cte->validate_read = NULL;
173 cte->validate_close = check_http_digest;
174 break;
175 case CHECK_BINSEND_EXPECT:
176 case CHECK_SEND_EXPECT:
177 cte->validate_read = check_send_expect;
178 cte->validate_close = check_send_expect;
179 break;
182 if (cte->table->conf.flags & F_TLS) {
183 check_tls(cte);
184 return;
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);
190 return;
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);
199 void
200 tcp_send_req(int s, short event, void *arg)
202 struct ctl_tcp_event *cte = arg;
203 char *req;
204 int bs;
205 int len;
207 if (event == EV_TIMEOUT) {
208 tcp_close(cte, HOST_DOWN);
209 hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
210 return;
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);
219 } else {
220 len = strlen(cte->table->sendbuf);
221 req = cte->table->sendbuf;
224 do {
225 bs = write(s, req, len);
226 if (bs == -1) {
227 if (errno == EAGAIN || errno == EINTR)
228 goto retry;
229 log_warn("%s: cannot send request", __func__);
230 tcp_close(cte, HOST_DOWN);
231 hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
232 return;
234 req += bs;
235 len -= bs;
236 } while (len > 0);
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);
242 return;
244 retry:
245 event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
246 &cte->tv_start, &cte->table->conf.timeout, cte);
249 void
250 tcp_read_buf(int s, short event, void *arg)
252 ssize_t br;
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);
259 else {
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);
265 return;
268 bzero(rbuf, sizeof(rbuf));
269 br = read(s, rbuf, sizeof(rbuf) - 1);
270 switch (br) {
271 case -1:
272 if (errno == EAGAIN || errno == EINTR)
273 goto retry;
274 tcp_close(cte, HOST_DOWN);
275 hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
276 return;
277 case 0:
278 cte->host->up = HOST_DOWN;
279 (void)cte->validate_close(cte);
280 tcp_close(cte, 0);
281 hce_notify_done(cte->host, cte->host->he);
282 return;
283 default:
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)
288 goto retry;
289 tcp_close(cte, 0);
290 hce_notify_done(cte->host, cte->host->he);
291 return;
293 break; /* retry */
295 retry:
296 event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
297 &cte->tv_start, &cte->table->conf.timeout, cte);
300 int
301 check_send_expect(struct ctl_tcp_event *cte)
303 u_char *b;
305 if (cte->table->conf.check == CHECK_BINSEND_EXPECT) {
306 size_t exlen;
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;
317 return (0);
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) {
324 /*
325 * ensure string is nul-terminated.
326 */
327 b = strndup(ibuf_data(cte->buf), ibuf_size(cte->buf));
328 if (b == NULL)
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;
333 free(b);
334 return (0);
336 free(b);
339 cte->host->he = HCE_SEND_EXPECT_FAIL;
340 cte->host->up = HOST_UNKNOWN;
341 return (1);
344 int
345 check_http_code(struct ctl_tcp_event *cte)
347 char *head;
348 char scode[4];
349 const char *estr;
350 int code;
351 struct host *host;
353 /*
354 * ensure string is nul-terminated.
355 */
356 if (ibuf_add_zero(cte->buf, 1) == -1)
357 fatal("out of memory");
359 head = ibuf_data(cte->buf);
360 host = cte->host;
361 host->he = HCE_HTTP_CODE_ERROR;
362 host->code = 0;
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;
369 return (1);
371 head += strlen("HTTP/1.1 ");
372 if (strlen(head) < 5) /* code + \r\n */ {
373 host->up = HOST_DOWN;
374 return (1);
376 (void)strlcpy(scode, head, sizeof(scode));
377 code = strtonum(scode, 100, 999, &estr);
378 if (estr != NULL) {
379 log_debug("%s: %s failed (cannot parse HTTP code)",
380 __func__, host->conf.name);
381 host->up = HOST_DOWN;
382 return (1);
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;
389 host->code = code;
390 } else {
391 host->he = HCE_HTTP_CODE_OK;
392 host->up = HOST_UP;
394 return (!(host->up == HOST_UP));
397 int
398 check_http_digest(struct ctl_tcp_event *cte)
400 char *head;
401 char digest[SHA1_DIGEST_STRING_LENGTH];
402 struct host *host;
404 /*
405 * ensure string is nul-terminated.
406 */
407 if (ibuf_add_zero(cte->buf, 1) == -1)
408 fatal("out of memory");
410 head = ibuf_data(cte->buf);
411 host = cte->host;
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;
418 return (1);
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;
429 } else {
430 host->he = HCE_HTTP_DIGEST_OK;
431 host->up = HOST_UP;
433 return (!(host->up == HOST_UP));