xref: /openbsd-src/usr.sbin/relayd/check_tcp.c (revision d59bb9942320b767f2a19aaa7690c8c6e30b724c)
1 /*	$OpenBSD: check_tcp.c,v 1.52 2017/02/09 11:16:22 reyk 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 		if (setsockopt(s, IPPROTO_IP, IP_TTL,
87 		    &cte->host->conf.ttl, sizeof(int)) == -1)
88 			goto bad;
89 	}
90 
91 	bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
92 	if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
93 		if (errno != EINPROGRESS) {
94 			he = HCE_TCP_CONNECT_FAIL;
95 			goto bad;
96 		}
97 	}
98 
99 	cte->buf = NULL;
100 	cte->host->up = HOST_UP;
101 	event_del(&cte->ev);
102 	event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
103 	event_add(&cte->ev, &tv);
104 	return;
105 
106 bad:
107 	tcp_close(cte, HOST_DOWN);
108 	hce_notify_done(cte->host, he);
109 }
110 
111 void
112 tcp_write(int s, short event, void *arg)
113 {
114 	struct ctl_tcp_event	*cte = arg;
115 	int			 err;
116 	socklen_t		 len;
117 
118 	if (event == EV_TIMEOUT) {
119 		tcp_close(cte, HOST_DOWN);
120 		hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
121 		return;
122 	}
123 
124 	len = sizeof(err);
125 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
126 		fatal("tcp_write: getsockopt");
127 	if (err != 0) {
128 		tcp_close(cte, HOST_DOWN);
129 		hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
130 		return;
131 	}
132 
133 	cte->host->up = HOST_UP;
134 	tcp_host_up(cte);
135 }
136 
137 void
138 tcp_close(struct ctl_tcp_event *cte, int status)
139 {
140 	close(cte->s);
141 	cte->s = -1;
142 	if (status != 0)
143 		cte->host->up = status;
144 	ibuf_free(cte->buf);
145 	cte->buf = NULL;
146 }
147 
148 void
149 tcp_host_up(struct ctl_tcp_event *cte)
150 {
151 	switch (cte->table->conf.check) {
152 	case CHECK_TCP:
153 		if (cte->table->conf.flags & F_TLS)
154 			break;
155 		tcp_close(cte, 0);
156 		hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
157 		return;
158 	case CHECK_HTTP_CODE:
159 		cte->validate_read = NULL;
160 		cte->validate_close = check_http_code;
161 		break;
162 	case CHECK_HTTP_DIGEST:
163 		cte->validate_read = NULL;
164 		cte->validate_close = check_http_digest;
165 		break;
166 	case CHECK_SEND_EXPECT:
167 		cte->validate_read = check_send_expect;
168 		cte->validate_close = check_send_expect;
169 		break;
170 	}
171 
172 	if (cte->table->conf.flags & F_TLS) {
173 		ssl_transaction(cte);
174 		return;
175 	}
176 
177 	if (cte->table->sendbuf != NULL) {
178 		cte->req = cte->table->sendbuf;
179 		event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
180 		    &cte->tv_start, &cte->table->conf.timeout, cte);
181 		return;
182 	}
183 
184 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
185 		fatalx("tcp_host_up: cannot create dynamic buffer");
186 	event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_READ, tcp_read_buf,
187 	    &cte->tv_start, &cte->table->conf.timeout, cte);
188 }
189 
190 void
191 tcp_send_req(int s, short event, void *arg)
192 {
193 	struct ctl_tcp_event	*cte = arg;
194 	int			 bs;
195 	int			 len;
196 
197 	if (event == EV_TIMEOUT) {
198 		tcp_close(cte, HOST_DOWN);
199 		hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
200 		return;
201 	}
202 	len = strlen(cte->req);
203 	do {
204 		bs = write(s, cte->req, len);
205 		if (bs == -1) {
206 			if (errno == EAGAIN || errno == EINTR)
207 				goto retry;
208 			log_warn("%s: cannot send request", __func__);
209 			tcp_close(cte, HOST_DOWN);
210 			hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
211 			return;
212 		}
213 		cte->req += bs;
214 		len -= bs;
215 	} while (len > 0);
216 
217 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
218 		fatalx("tcp_send_req: cannot create dynamic buffer");
219 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
220 	    &cte->tv_start, &cte->table->conf.timeout, cte);
221 	return;
222 
223  retry:
224 	event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
225 	    &cte->tv_start, &cte->table->conf.timeout, cte);
226 }
227 
228 void
229 tcp_read_buf(int s, short event, void *arg)
230 {
231 	ssize_t			 br;
232 	char			 rbuf[SMALL_READ_BUF_SIZE];
233 	struct ctl_tcp_event	*cte = arg;
234 
235 	if (event == EV_TIMEOUT) {
236 		if (ibuf_size(cte->buf))
237 			(void)cte->validate_close(cte);
238 		else
239 			cte->host->he = HCE_TCP_READ_TIMEOUT;
240 		tcp_close(cte, cte->host->up == HOST_UP ? 0 : HOST_DOWN);
241 		hce_notify_done(cte->host, cte->host->he);
242 		return;
243 	}
244 
245 	bzero(rbuf, sizeof(rbuf));
246 	br = read(s, rbuf, sizeof(rbuf) - 1);
247 	switch (br) {
248 	case -1:
249 		if (errno == EAGAIN || errno == EINTR)
250 			goto retry;
251 		tcp_close(cte, HOST_DOWN);
252 		hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
253 		return;
254 	case 0:
255 		cte->host->up = HOST_DOWN;
256 		(void)cte->validate_close(cte);
257 		tcp_close(cte, 0);
258 		hce_notify_done(cte->host, cte->host->he);
259 		return;
260 	default:
261 		if (ibuf_add(cte->buf, rbuf, br) == -1)
262 			fatal("tcp_read_buf: buf_add error");
263 		if (cte->validate_read != NULL) {
264 			if (cte->validate_read(cte) != 0)
265 				goto retry;
266 			tcp_close(cte, 0);
267 			hce_notify_done(cte->host, cte->host->he);
268 			return;
269 		}
270 		break; /* retry */
271 	}
272 retry:
273 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
274 	    &cte->tv_start, &cte->table->conf.timeout, cte);
275 }
276 
277 int
278 check_send_expect(struct ctl_tcp_event *cte)
279 {
280 	u_char	*b;
281 
282 	/*
283 	 * ensure string is nul-terminated.
284 	 */
285 	b = ibuf_reserve(cte->buf, 1);
286 	if (b == NULL)
287 		fatal("out of memory");
288 	*b = '\0';
289 	if (fnmatch(cte->table->conf.exbuf, cte->buf->buf, 0) == 0) {
290 		cte->host->he = HCE_SEND_EXPECT_OK;
291 		cte->host->up = HOST_UP;
292 		return (0);
293 	}
294 	cte->host->he = HCE_SEND_EXPECT_FAIL;
295 	cte->host->up = HOST_UNKNOWN;
296 
297 	/*
298 	 * go back to original position.
299 	 */
300 	cte->buf->wpos--;
301 	return (1);
302 }
303 
304 int
305 check_http_code(struct ctl_tcp_event *cte)
306 {
307 	char		*head;
308 	char		 scode[4];
309 	const char	*estr;
310 	u_char		*b;
311 	int		 code;
312 	struct host	*host;
313 
314 	/*
315 	 * ensure string is nul-terminated.
316 	 */
317 	b = ibuf_reserve(cte->buf, 1);
318 	if (b == NULL)
319 		fatal("out of memory");
320 	*b = '\0';
321 
322 	head = cte->buf->buf;
323 	host = cte->host;
324 	host->he = HCE_HTTP_CODE_ERROR;
325 	host->code = 0;
326 
327 	if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
328 	    strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
329 		log_debug("%s: %s failed (cannot parse HTTP version)",
330 		    __func__, host->conf.name);
331 		host->up = HOST_DOWN;
332 		return (1);
333 	}
334 	head += strlen("HTTP/1.1 ");
335 	if (strlen(head) < 5) /* code + \r\n */ {
336 		host->up = HOST_DOWN;
337 		return (1);
338 	}
339 	(void)strlcpy(scode, head, sizeof(scode));
340 	code = strtonum(scode, 100, 999, &estr);
341 	if (estr != NULL) {
342 		log_debug("%s: %s failed (cannot parse HTTP code)",
343 		    __func__, host->conf.name);
344 		host->up = HOST_DOWN;
345 		return (1);
346 	}
347 	if (code != cte->table->conf.retcode) {
348 		log_debug("%s: %s failed (invalid HTTP code %d returned)",
349 		    __func__, host->conf.name, code);
350 		host->he = HCE_HTTP_CODE_FAIL;
351 		host->up = HOST_DOWN;
352 		host->code = code;
353 	} else {
354 		host->he = HCE_HTTP_CODE_OK;
355 		host->up = HOST_UP;
356 	}
357 	return (!(host->up == HOST_UP));
358 }
359 
360 int
361 check_http_digest(struct ctl_tcp_event *cte)
362 {
363 	char		*head;
364 	u_char		*b;
365 	char		 digest[SHA1_DIGEST_STRING_LENGTH];
366 	struct host	*host;
367 
368 	/*
369 	 * ensure string is nul-terminated.
370 	 */
371 	b = ibuf_reserve(cte->buf, 1);
372 	if (b == NULL)
373 		fatal("out of memory");
374 	*b = '\0';
375 
376 	head = cte->buf->buf;
377 	host = cte->host;
378 	host->he = HCE_HTTP_DIGEST_ERROR;
379 
380 	if ((head = strstr(head, "\r\n\r\n")) == NULL) {
381 		log_debug("%s: %s failed (no end of headers)",
382 		    __func__, host->conf.name);
383 		host->up = HOST_DOWN;
384 		return (1);
385 	}
386 	head += strlen("\r\n\r\n");
387 
388 	digeststr(cte->table->conf.digest_type, head, strlen(head), digest);
389 
390 	if (strcmp(cte->table->conf.digest, digest)) {
391 		log_warnx("%s: %s failed (wrong digest)",
392 		    __func__, host->conf.name);
393 		host->he = HCE_HTTP_DIGEST_FAIL;
394 		host->up = HOST_DOWN;
395 	} else {
396 		host->he = HCE_HTTP_DIGEST_OK;
397 		host->up = HOST_UP;
398 	}
399 	return (!(host->up == HOST_UP));
400 }
401