xref: /openbsd-src/usr.sbin/relayd/check_tcp.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: check_tcp.c,v 1.42 2011/06/17 14:36:51 jsg 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/param.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 
23 #include <net/if.h>
24 #include <netinet/in.h>
25 
26 #include <limits.h>
27 #include <event.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <fnmatch.h>
34 #include <sha1.h>
35 
36 #include <openssl/ssl.h>
37 
38 #include "relayd.h"
39 
40 void	tcp_write(int, short, void *);
41 void	tcp_host_up(struct ctl_tcp_event *);
42 void	tcp_close(struct ctl_tcp_event *, int);
43 void	tcp_send_req(int, short, void *);
44 void	tcp_read_buf(int, short, void *);
45 
46 int	check_http_code(struct ctl_tcp_event *);
47 int	check_http_digest(struct ctl_tcp_event *);
48 int	check_send_expect(struct ctl_tcp_event *);
49 
50 void
51 check_tcp(struct ctl_tcp_event *cte)
52 {
53 	int			 s;
54 	socklen_t		 len;
55 	struct timeval		 tv;
56 	struct linger		 lng;
57 	int			 he = HCE_TCP_SOCKET_OPTION;
58 
59 	switch (cte->host->conf.ss.ss_family) {
60 	case AF_INET:
61 		((struct sockaddr_in *)&cte->host->conf.ss)->sin_port =
62 			cte->table->conf.port;
63 		break;
64 	case AF_INET6:
65 		((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port =
66 			cte->table->conf.port;
67 		break;
68 	}
69 
70 	len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len;
71 
72 	if ((s = socket(cte->host->conf.ss.ss_family, SOCK_STREAM, 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 	}
79 
80 	cte->s = s;
81 
82 	bzero(&lng, sizeof(lng));
83 	if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
84 		goto bad;
85 
86 	if (cte->host->conf.ttl > 0) {
87 		if (setsockopt(s, IPPROTO_IP, IP_TTL,
88 		    &cte->host->conf.ttl, sizeof(int)) == -1)
89 			goto bad;
90 	}
91 
92 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
93 		goto bad;
94 
95 	bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
96 	if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
97 		if (errno != EINPROGRESS) {
98 			he = HCE_TCP_CONNECT_FAIL;
99 			goto bad;
100 		}
101 	}
102 
103 	cte->buf = NULL;
104 	cte->host->up = HOST_UP;
105 	event_del(&cte->ev);
106 	event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
107 	event_add(&cte->ev, &tv);
108 	return;
109 
110 bad:
111 	tcp_close(cte, HOST_DOWN);
112 	hce_notify_done(cte->host, he);
113 }
114 
115 void
116 tcp_write(int s, short event, void *arg)
117 {
118 	struct ctl_tcp_event	*cte = arg;
119 	int			 err;
120 	socklen_t		 len;
121 
122 	if (event == EV_TIMEOUT) {
123 		tcp_close(cte, HOST_DOWN);
124 		hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
125 		return;
126 	}
127 
128 	len = sizeof(err);
129 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
130 		fatal("tcp_write: getsockopt");
131 	if (err != 0) {
132 		tcp_close(cte, HOST_DOWN);
133 		hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL);
134 		return;
135 	}
136 
137 	cte->host->up = HOST_UP;
138 	tcp_host_up(cte);
139 }
140 
141 void
142 tcp_close(struct ctl_tcp_event *cte, int status)
143 {
144 	close(cte->s);
145 	cte->s = -1;
146 	if (status != 0)
147 		cte->host->up = status;
148 	if (cte->buf) {
149 		ibuf_free(cte->buf);
150 		cte->buf = NULL;
151 	}
152 }
153 
154 void
155 tcp_host_up(struct ctl_tcp_event *cte)
156 {
157 	switch (cte->table->conf.check) {
158 	case CHECK_TCP:
159 		if (cte->table->conf.flags & F_SSL)
160 			break;
161 		tcp_close(cte, 0);
162 		hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
163 		return;
164 	case CHECK_HTTP_CODE:
165 		cte->validate_read = NULL;
166 		cte->validate_close = check_http_code;
167 		break;
168 	case CHECK_HTTP_DIGEST:
169 		cte->validate_read = NULL;
170 		cte->validate_close = check_http_digest;
171 		break;
172 	case CHECK_SEND_EXPECT:
173 		cte->validate_read = check_send_expect;
174 		cte->validate_close = check_send_expect;
175 		break;
176 	}
177 
178 	if (cte->table->conf.flags & F_SSL) {
179 		ssl_transaction(cte);
180 		return;
181 	}
182 
183 	if (cte->table->sendbuf != NULL) {
184 		cte->req = cte->table->sendbuf;
185 		event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
186 		    &cte->tv_start, &cte->table->conf.timeout, cte);
187 		return;
188 	}
189 
190 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
191 		fatalx("tcp_host_up: cannot create dynamic buffer");
192 	event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_READ, tcp_read_buf,
193 	    &cte->tv_start, &cte->table->conf.timeout, cte);
194 }
195 
196 void
197 tcp_send_req(int s, short event, void *arg)
198 {
199 	struct ctl_tcp_event	*cte = arg;
200 	int			 bs;
201 	int			 len;
202 
203 	if (event == EV_TIMEOUT) {
204 		tcp_close(cte, HOST_DOWN);
205 		hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
206 		return;
207 	}
208 	len = strlen(cte->req);
209 	do {
210 		bs = write(s, cte->req, len);
211 		if (bs == -1) {
212 			if (errno == EAGAIN || errno == EINTR)
213 				goto retry;
214 			log_warnx("%s: cannot send request", __func__);
215 			tcp_close(cte, HOST_DOWN);
216 			hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
217 			return;
218 		}
219 		cte->req += bs;
220 		len -= bs;
221 	} while (len > 0);
222 
223 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
224 		fatalx("tcp_send_req: cannot create dynamic buffer");
225 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
226 	    &cte->tv_start, &cte->table->conf.timeout, cte);
227 	return;
228 
229  retry:
230 	event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
231 	    &cte->tv_start, &cte->table->conf.timeout, cte);
232 }
233 
234 void
235 tcp_read_buf(int s, short event, void *arg)
236 {
237 	ssize_t			 br;
238 	char			 rbuf[SMALL_READ_BUF_SIZE];
239 	struct ctl_tcp_event	*cte = arg;
240 
241 	if (event == EV_TIMEOUT) {
242 		tcp_close(cte, HOST_DOWN);
243 		hce_notify_done(cte->host, HCE_TCP_READ_TIMEOUT);
244 		return;
245 	}
246 
247 	bzero(rbuf, sizeof(rbuf));
248 	br = read(s, rbuf, sizeof(rbuf) - 1);
249 	switch (br) {
250 	case -1:
251 		if (errno == EAGAIN || errno == EINTR)
252 			goto retry;
253 		tcp_close(cte, HOST_DOWN);
254 		hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
255 		return;
256 	case 0:
257 		cte->host->up = HOST_DOWN;
258 		(void)cte->validate_close(cte);
259 		tcp_close(cte, 0);
260 		hce_notify_done(cte->host, cte->host->he);
261 		return;
262 	default:
263 		if (ibuf_add(cte->buf, rbuf, br) == -1)
264 			fatal("tcp_read_buf: buf_add error");
265 		if (cte->validate_read != NULL) {
266 			if (cte->validate_read(cte) != 0)
267 				goto retry;
268 			tcp_close(cte, 0);
269 			hce_notify_done(cte->host, cte->host->he);
270 			return;
271 		}
272 		break; /* retry */
273 	}
274 retry:
275 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
276 	    &cte->tv_start, &cte->table->conf.timeout, cte);
277 }
278 
279 int
280 check_send_expect(struct ctl_tcp_event *cte)
281 {
282 	u_char	*b;
283 
284 	/*
285 	 * ensure string is nul-terminated.
286 	 */
287 	b = ibuf_reserve(cte->buf, 1);
288 	if (b == NULL)
289 		fatal("out of memory");
290 	*b = '\0';
291 	if (fnmatch(cte->table->conf.exbuf, cte->buf->buf, 0) == 0) {
292 		cte->host->he = HCE_SEND_EXPECT_OK;
293 		cte->host->up = HOST_UP;
294 		return (0);
295 	}
296 	cte->host->he = HCE_SEND_EXPECT_FAIL;
297 	cte->host->up = HOST_UNKNOWN;
298 
299 	/*
300 	 * go back to original position.
301 	 */
302 	cte->buf->wpos--;
303 	return (1);
304 }
305 
306 int
307 check_http_code(struct ctl_tcp_event *cte)
308 {
309 	char		*head;
310 	char		 scode[4];
311 	const char	*estr;
312 	u_char		*b;
313 	int		 code;
314 	struct host	*host;
315 
316 	/*
317 	 * ensure string is nul-terminated.
318 	 */
319 	b = ibuf_reserve(cte->buf, 1);
320 	if (b == NULL)
321 		fatal("out of memory");
322 	*b = '\0';
323 
324 	head = cte->buf->buf;
325 	host = cte->host;
326 	host->he = HCE_HTTP_CODE_ERROR;
327 
328 	if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
329 	    strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
330 		log_debug("%s: %s failed (cannot parse HTTP version)",
331 		    __func__, host->conf.name);
332 		host->up = HOST_DOWN;
333 		return (1);
334 	}
335 	head += strlen("HTTP/1.1 ");
336 	if (strlen(head) < 5) /* code + \r\n */ {
337 		host->up = HOST_DOWN;
338 		return (1);
339 	}
340 	(void)strlcpy(scode, head, sizeof(scode));
341 	code = strtonum(scode, 100, 999, &estr);
342 	if (estr != NULL) {
343 		log_debug("%s: %s failed (cannot parse HTTP code)",
344 		    __func__, host->conf.name);
345 		host->up = HOST_DOWN;
346 		return (1);
347 	}
348 	if (code != cte->table->conf.retcode) {
349 		log_debug("%s: %s failed (invalid HTTP code returned)",
350 		    __func__, host->conf.name);
351 		host->he = HCE_HTTP_CODE_FAIL;
352 		host->up = HOST_DOWN;
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