xref: /openbsd/usr.sbin/relayd/check_tcp.c (revision 274d7c50)
1 /*	$OpenBSD: check_tcp.c,v 1.57 2019/09/15 19:23:29 rob Exp $	*/
2 
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  */
18 
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/socket.h>
22 
23 #include <netinet/in.h>
24 
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>
35 
36 #include "relayd.h"
37 
38 void	tcp_write(int, short, void *);
39 void	tcp_host_up(struct ctl_tcp_event *);
40 void	tcp_close(struct ctl_tcp_event *, int);
41 void	tcp_send_req(int, short, void *);
42 void	tcp_read_buf(int, short, void *);
43 
44 int	check_http_code(struct ctl_tcp_event *);
45 int	check_http_digest(struct ctl_tcp_event *);
46 int	check_send_expect(struct ctl_tcp_event *);
47 
48 void
49 check_tcp(struct ctl_tcp_event *cte)
50 {
51 	int			 s;
52 	socklen_t		 len;
53 	struct timeval		 tv;
54 	struct linger		 lng;
55 	int			 he = HCE_TCP_SOCKET_OPTION;
56 
57 	switch (cte->host->conf.ss.ss_family) {
58 	case AF_INET:
59 		((struct sockaddr_in *)&cte->host->conf.ss)->sin_port =
60 			cte->table->conf.port;
61 		break;
62 	case AF_INET6:
63 		((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port =
64 			cte->table->conf.port;
65 		break;
66 	}
67 
68 	len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len;
69 
70 	if ((s = socket(cte->host->conf.ss.ss_family,
71 	    SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) {
72 		if (errno == EMFILE || errno == ENFILE)
73 			he = HCE_TCP_SOCKET_LIMIT;
74 		else
75 			he = HCE_TCP_SOCKET_ERROR;
76 		goto bad;
77 	}
78 
79 	cte->s = s;
80 
81 	bzero(&lng, sizeof(lng));
82 	if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
83 		goto bad;
84 
85 	if (cte->host->conf.ttl > 0)
86 		switch (cte->host->conf.ss.ss_family) {
87 		case AF_INET:
88 			if (setsockopt(s, IPPROTO_IP, IP_TTL,
89 			    &cte->host->conf.ttl, sizeof(int)) == -1)
90 				goto bad;
91 			break;
92 		case AF_INET6:
93 			if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
94 			    &cte->host->conf.ttl, sizeof(int)) == -1)
95 				goto bad;
96 			break;
97 		}
98 
99 	bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
100 	if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
101 		if (errno != EINPROGRESS) {
102 			he = HCE_TCP_CONNECT_FAIL;
103 			goto bad;
104 		}
105 	}
106 
107 	cte->buf = NULL;
108 	cte->host->up = HOST_UP;
109 	event_del(&cte->ev);
110 	event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
111 	event_add(&cte->ev, &tv);
112 	return;
113 
114 bad:
115 	tcp_close(cte, HOST_DOWN);
116 	hce_notify_done(cte->host, he);
117 }
118 
119 void
120 tcp_write(int s, short event, void *arg)
121 {
122 	struct ctl_tcp_event	*cte = arg;
123 	int			 err;
124 	socklen_t		 len;
125 
126 	if (event == EV_TIMEOUT) {
127 		tcp_close(cte, HOST_DOWN);
128 		hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
129 		return;
130 	}
131 
132 	len = sizeof(err);
133 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
134 		fatal("%s: getsockopt", __func__);
135 	if (err != 0) {
136 		tcp_close(cte, HOST_DOWN);
137 		hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
138 		return;
139 	}
140 
141 	cte->host->up = HOST_UP;
142 	tcp_host_up(cte);
143 }
144 
145 void
146 tcp_close(struct ctl_tcp_event *cte, int status)
147 {
148 	close(cte->s);
149 	cte->s = -1;
150 	if (status != 0)
151 		cte->host->up = status;
152 	ibuf_free(cte->buf);
153 	cte->buf = NULL;
154 }
155 
156 void
157 tcp_host_up(struct ctl_tcp_event *cte)
158 {
159 	switch (cte->table->conf.check) {
160 	case CHECK_TCP:
161 		if (cte->table->conf.flags & F_TLS)
162 			break;
163 		tcp_close(cte, 0);
164 		hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
165 		return;
166 	case CHECK_HTTP_CODE:
167 		cte->validate_read = NULL;
168 		cte->validate_close = check_http_code;
169 		break;
170 	case CHECK_HTTP_DIGEST:
171 		cte->validate_read = NULL;
172 		cte->validate_close = check_http_digest;
173 		break;
174 	case CHECK_BINSEND_EXPECT:
175 	case CHECK_SEND_EXPECT:
176 		cte->validate_read = check_send_expect;
177 		cte->validate_close = check_send_expect;
178 		break;
179 	}
180 
181 	if (cte->table->conf.flags & F_TLS) {
182 		check_tls(cte);
183 		return;
184 	}
185 
186 	if (cte->table->sendbuf != NULL && cte->table->sendbinbuf == NULL) {
187 		cte->req = cte->table->sendbuf;
188 	} else if (cte->table->sendbinbuf != NULL)
189 		cte->req = cte->table->sendbinbuf->buf;
190 	if (cte->table->sendbuf != NULL || cte->table->sendbinbuf != NULL) {
191 		event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
192 		    &cte->tv_start, &cte->table->conf.timeout, cte);
193 		return;
194 	}
195 
196 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
197 		fatalx("%s: cannot create dynamic buffer", __func__);
198 	event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_READ, tcp_read_buf,
199 	    &cte->tv_start, &cte->table->conf.timeout, cte);
200 }
201 
202 void
203 tcp_send_req(int s, short event, void *arg)
204 {
205 	struct ctl_tcp_event	*cte = arg;
206 	int			 bs;
207 	int			 len;
208 
209 	if (event == EV_TIMEOUT) {
210 		tcp_close(cte, HOST_DOWN);
211 		hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
212 		return;
213 	}
214 
215 	if (cte->table->sendbinbuf != NULL) {
216 		len = ibuf_size(cte->table->sendbinbuf);
217 		log_debug("%s: table %s sending binary", __func__,
218 		    cte->table->conf.name);
219 		print_hex(cte->table->sendbinbuf->buf, 0, len);
220 	} else
221 		len = strlen(cte->req);
222 
223 	do {
224 		bs = write(s, cte->req, len);
225 		if (bs == -1) {
226 			if (errno == EAGAIN || errno == EINTR)
227 				goto retry;
228 			log_warn("%s: cannot send request", __func__);
229 			tcp_close(cte, HOST_DOWN);
230 			hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
231 			return;
232 		}
233 		cte->req += bs;
234 		len -= bs;
235 	} while (len > 0);
236 
237 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
238 		fatalx("%s: cannot create dynamic buffer", __func__);
239 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
240 	    &cte->tv_start, &cte->table->conf.timeout, cte);
241 	return;
242 
243  retry:
244 	event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
245 	    &cte->tv_start, &cte->table->conf.timeout, cte);
246 }
247 
248 void
249 tcp_read_buf(int s, short event, void *arg)
250 {
251 	ssize_t			 br;
252 	char			 rbuf[SMALL_READ_BUF_SIZE];
253 	struct ctl_tcp_event	*cte = arg;
254 
255 	if (event == EV_TIMEOUT) {
256 		if (ibuf_size(cte->buf))
257 			(void)cte->validate_close(cte);
258 		else {
259 			cte->host->he = HCE_TCP_READ_TIMEOUT;
260 			cte->host->up = HOST_DOWN;
261 		}
262 		tcp_close(cte, cte->host->up == HOST_UP ? 0 : HOST_DOWN);
263 		hce_notify_done(cte->host, cte->host->he);
264 		return;
265 	}
266 
267 	bzero(rbuf, sizeof(rbuf));
268 	br = read(s, rbuf, sizeof(rbuf) - 1);
269 	switch (br) {
270 	case -1:
271 		if (errno == EAGAIN || errno == EINTR)
272 			goto retry;
273 		tcp_close(cte, HOST_DOWN);
274 		hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
275 		return;
276 	case 0:
277 		cte->host->up = HOST_DOWN;
278 		(void)cte->validate_close(cte);
279 		tcp_close(cte, 0);
280 		hce_notify_done(cte->host, cte->host->he);
281 		return;
282 	default:
283 		if (ibuf_add(cte->buf, rbuf, br) == -1)
284 			fatal("%s: buf_add error", __func__);
285 		if (cte->validate_read != NULL) {
286 			if (cte->validate_read(cte) != 0)
287 				goto retry;
288 			tcp_close(cte, 0);
289 			hce_notify_done(cte->host, cte->host->he);
290 			return;
291 		}
292 		break; /* retry */
293 	}
294 retry:
295 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
296 	    &cte->tv_start, &cte->table->conf.timeout, cte);
297 }
298 
299 int
300 check_send_expect(struct ctl_tcp_event *cte)
301 {
302 	u_char	*b;
303 
304 	if (cte->table->conf.check == CHECK_BINSEND_EXPECT) {
305 		log_debug("%s: table %s expecting binary",
306 		    __func__, cte->table->conf.name);
307 		print_hex(cte->table->conf.exbinbuf, 0,
308 		    strlen(cte->table->conf.exbuf) / 2);
309 
310 		if (memcmp(cte->table->conf.exbinbuf, cte->buf->buf,
311 		    strlen(cte->table->conf.exbuf) / 2) == 0) {
312 			cte->host->he = HCE_SEND_EXPECT_OK;
313 			cte->host->up = HOST_UP;
314 			return (0);
315 		} else {
316 			log_debug("%s: table %s received mismatching binary",
317 			    __func__, cte->table->conf.name);
318 			print_hex(cte->buf->buf, 0, ibuf_size(cte->buf));
319 		}
320 	} else if (cte->table->conf.check == CHECK_SEND_EXPECT) {
321 		/*
322 		 * ensure string is nul-terminated.
323 		 */
324 		b = ibuf_reserve(cte->buf, 1);
325 		if (b == NULL)
326 			fatal("out of memory");
327 		*b = '\0';
328 		if (fnmatch(cte->table->conf.exbuf, cte->buf->buf, 0) == 0) {
329 			cte->host->he = HCE_SEND_EXPECT_OK;
330 			cte->host->up = HOST_UP;
331 			return (0);
332 		}
333 	}
334 	cte->host->he = HCE_SEND_EXPECT_FAIL;
335 	cte->host->up = HOST_UNKNOWN;
336 
337 	/*
338 	 * go back to original position.
339 	 */
340 	cte->buf->wpos--;
341 	return (1);
342 }
343 
344 int
345 check_http_code(struct ctl_tcp_event *cte)
346 {
347 	char		*head;
348 	char		 scode[4];
349 	const char	*estr;
350 	u_char		*b;
351 	int		 code;
352 	struct host	*host;
353 
354 	/*
355 	 * ensure string is nul-terminated.
356 	 */
357 	b = ibuf_reserve(cte->buf, 1);
358 	if (b == NULL)
359 		fatal("out of memory");
360 	*b = '\0';
361 
362 	head = cte->buf->buf;
363 	host = cte->host;
364 	host->he = HCE_HTTP_CODE_ERROR;
365 	host->code = 0;
366 
367 	if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
368 	    strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
369 		log_debug("%s: %s failed (cannot parse HTTP version)",
370 		    __func__, host->conf.name);
371 		host->up = HOST_DOWN;
372 		return (1);
373 	}
374 	head += strlen("HTTP/1.1 ");
375 	if (strlen(head) < 5) /* code + \r\n */ {
376 		host->up = HOST_DOWN;
377 		return (1);
378 	}
379 	(void)strlcpy(scode, head, sizeof(scode));
380 	code = strtonum(scode, 100, 999, &estr);
381 	if (estr != NULL) {
382 		log_debug("%s: %s failed (cannot parse HTTP code)",
383 		    __func__, host->conf.name);
384 		host->up = HOST_DOWN;
385 		return (1);
386 	}
387 	if (code != cte->table->conf.retcode) {
388 		log_debug("%s: %s failed (invalid HTTP code %d returned)",
389 		    __func__, host->conf.name, code);
390 		host->he = HCE_HTTP_CODE_FAIL;
391 		host->up = HOST_DOWN;
392 		host->code = code;
393 	} else {
394 		host->he = HCE_HTTP_CODE_OK;
395 		host->up = HOST_UP;
396 	}
397 	return (!(host->up == HOST_UP));
398 }
399 
400 int
401 check_http_digest(struct ctl_tcp_event *cte)
402 {
403 	char		*head;
404 	u_char		*b;
405 	char		 digest[SHA1_DIGEST_STRING_LENGTH];
406 	struct host	*host;
407 
408 	/*
409 	 * ensure string is nul-terminated.
410 	 */
411 	b = ibuf_reserve(cte->buf, 1);
412 	if (b == NULL)
413 		fatal("out of memory");
414 	*b = '\0';
415 
416 	head = cte->buf->buf;
417 	host = cte->host;
418 	host->he = HCE_HTTP_DIGEST_ERROR;
419 
420 	if ((head = strstr(head, "\r\n\r\n")) == NULL) {
421 		log_debug("%s: %s failed (no end of headers)",
422 		    __func__, host->conf.name);
423 		host->up = HOST_DOWN;
424 		return (1);
425 	}
426 	head += strlen("\r\n\r\n");
427 
428 	digeststr(cte->table->conf.digest_type, head, strlen(head), digest);
429 
430 	if (strcmp(cte->table->conf.digest, digest)) {
431 		log_warnx("%s: %s failed (wrong digest)",
432 		    __func__, host->conf.name);
433 		host->he = HCE_HTTP_DIGEST_FAIL;
434 		host->up = HOST_DOWN;
435 	} else {
436 		host->he = HCE_HTTP_DIGEST_OK;
437 		host->up = HOST_UP;
438 	}
439 	return (!(host->up == HOST_UP));
440 }
441