xref: /openbsd/usr.sbin/relayd/check_tcp.c (revision 3d8817e4)
1 /*	$OpenBSD: check_tcp.c,v 1.39 2010/12/20 12:38:06 dhill 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(int, struct ctl_tcp_event *);
42 void	tcp_send_req(int, short, void *);
43 void	tcp_read_buf(int, short, void *);
44 
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 *);
48 
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;
57 
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 	}
68 
69 	len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len;
70 
71 	if ((s = socket(cte->host->conf.ss.ss_family, SOCK_STREAM, 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 	bzero(&lng, sizeof(lng));
80 	if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
81 		goto bad;
82 
83 	if (cte->host->conf.ttl > 0) {
84 		if (setsockopt(s, IPPROTO_IP, IP_TTL,
85 		    &cte->host->conf.ttl, sizeof(int)) == -1)
86 			goto bad;
87 	}
88 
89 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
90 		goto bad;
91 
92 	bcopy(&cte->table->conf.timeout, &tv, sizeof(tv));
93 	if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) {
94 		if (errno != EINPROGRESS) {
95 			he = HCE_TCP_CONNECT_FAIL;
96 			goto bad;
97 		}
98 	}
99 
100 	cte->buf = NULL;
101 	cte->host->up = HOST_UP;
102 	event_del(&cte->ev);
103 	event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte);
104 	event_add(&cte->ev, &tv);
105 	return;
106 
107 bad:
108 	close(s);
109 	cte->host->up = HOST_DOWN;
110 	hce_notify_done(cte->host, he);
111 }
112 
113 void
114 tcp_write(int s, short event, void *arg)
115 {
116 	struct ctl_tcp_event	*cte = arg;
117 	int			 err;
118 	socklen_t		 len;
119 
120 	if (event == EV_TIMEOUT) {
121 		close(s);
122 		cte->host->up = HOST_DOWN;
123 		hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT);
124 		return;
125 	}
126 
127 	len = sizeof(err);
128 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len))
129 		fatal("tcp_write: getsockopt");
130 	if (err != 0) {
131 		close(s);
132 		cte->host->up = 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(s, cte);
139 }
140 
141 void
142 tcp_host_up(int s, struct ctl_tcp_event *cte)
143 {
144 	cte->s = s;
145 
146 	switch (cte->table->conf.check) {
147 	case CHECK_TCP:
148 		if (cte->table->conf.flags & F_SSL)
149 			break;
150 		close(s);
151 		hce_notify_done(cte->host, HCE_TCP_CONNECT_OK);
152 		return;
153 	case CHECK_HTTP_CODE:
154 		cte->validate_read = NULL;
155 		cte->validate_close = check_http_code;
156 		break;
157 	case CHECK_HTTP_DIGEST:
158 		cte->validate_read = NULL;
159 		cte->validate_close = check_http_digest;
160 		break;
161 	case CHECK_SEND_EXPECT:
162 		cte->validate_read = check_send_expect;
163 		cte->validate_close = check_send_expect;
164 		break;
165 	}
166 
167 	if (cte->table->conf.flags & F_SSL) {
168 		ssl_transaction(cte);
169 		return;
170 	}
171 
172 	if (cte->table->sendbuf != NULL) {
173 		cte->req = cte->table->sendbuf;
174 		event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
175 		    &cte->tv_start, &cte->table->conf.timeout, cte);
176 		return;
177 	}
178 
179 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
180 		fatalx("tcp_host_up: cannot create dynamic buffer");
181 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
182 	    &cte->tv_start, &cte->table->conf.timeout, cte);
183 }
184 
185 void
186 tcp_send_req(int s, short event, void *arg)
187 {
188 	struct ctl_tcp_event	*cte = arg;
189 	int			 bs;
190 	int			 len;
191 
192 	if (event == EV_TIMEOUT) {
193 		cte->host->up = HOST_DOWN;
194 		close(cte->s);
195 		hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT);
196 		return;
197 	}
198 	len = strlen(cte->req);
199 	do {
200 		bs = write(s, cte->req, len);
201 		if (bs == -1) {
202 			if (errno == EAGAIN || errno == EINTR)
203 				goto retry;
204 			log_warnx("tcp_send_req: cannot send request");
205 			cte->host->up = HOST_DOWN;
206 			close(cte->s);
207 			hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL);
208 			return;
209 		}
210 		cte->req += bs;
211 		len -= bs;
212 	} while (len > 0);
213 
214 	if ((cte->buf = ibuf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL)
215 		fatalx("tcp_send_req: cannot create dynamic buffer");
216 	event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf,
217 	    &cte->tv_start, &cte->table->conf.timeout, cte);
218 	return;
219 
220  retry:
221 	event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req,
222 	    &cte->tv_start, &cte->table->conf.timeout, cte);
223 }
224 
225 void
226 tcp_read_buf(int s, short event, void *arg)
227 {
228 	ssize_t			 br;
229 	char			 rbuf[SMALL_READ_BUF_SIZE];
230 	struct ctl_tcp_event	*cte = arg;
231 
232 	if (event == EV_TIMEOUT) {
233 		cte->host->up = HOST_DOWN;
234 		ibuf_free(cte->buf);
235 		close(s);
236 		hce_notify_done(cte->host, HCE_TCP_READ_TIMEOUT);
237 		return;
238 	}
239 
240 	bzero(rbuf, sizeof(rbuf));
241 	br = read(s, rbuf, sizeof(rbuf) - 1);
242 	switch (br) {
243 	case -1:
244 		if (errno == EAGAIN || errno == EINTR)
245 			goto retry;
246 		cte->host->up = HOST_DOWN;
247 		ibuf_free(cte->buf);
248 		close(cte->s);
249 		hce_notify_done(cte->host, HCE_TCP_READ_FAIL);
250 		return;
251 	case 0:
252 		cte->host->up = HOST_DOWN;
253 		(void)cte->validate_close(cte);
254 		close(cte->s);
255 		ibuf_free(cte->buf);
256 		hce_notify_done(cte->host, cte->host->he);
257 		return;
258 	default:
259 		if (ibuf_add(cte->buf, rbuf, br) == -1)
260 			fatal("tcp_read_buf: buf_add error");
261 		if (cte->validate_read != NULL) {
262 			if (cte->validate_read(cte) != 0)
263 				goto retry;
264 
265 			close(cte->s);
266 			ibuf_free(cte->buf);
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 
326 	if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
327 	    strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
328 		log_debug("check_http_code: %s failed "
329 		    "(cannot parse HTTP version)", host->conf.name);
330 		host->up = HOST_DOWN;
331 		return (1);
332 	}
333 	head += strlen("HTTP/1.1 ");
334 	if (strlen(head) < 5) /* code + \r\n */ {
335 		host->up = HOST_DOWN;
336 		return (1);
337 	}
338 	(void)strlcpy(scode, head, sizeof(scode));
339 	code = strtonum(scode, 100, 999, &estr);
340 	if (estr != NULL) {
341 		log_debug("check_http_code: %s failed "
342 		    "(cannot parse HTTP code)", host->conf.name);
343 		host->up = HOST_DOWN;
344 		return (1);
345 	}
346 	if (code != cte->table->conf.retcode) {
347 		log_debug("check_http_code: %s failed "
348 		    "(invalid HTTP code returned)", host->conf.name);
349 		host->he = HCE_HTTP_CODE_FAIL;
350 		host->up = HOST_DOWN;
351 	} else {
352 		host->he = HCE_HTTP_CODE_OK;
353 		host->up = HOST_UP;
354 	}
355 	return (!(host->up == HOST_UP));
356 }
357 
358 int
359 check_http_digest(struct ctl_tcp_event *cte)
360 {
361 	char		*head;
362 	u_char		*b;
363 	char		 digest[SHA1_DIGEST_STRING_LENGTH];
364 	struct host	*host;
365 
366 	/*
367 	 * ensure string is nul-terminated.
368 	 */
369 	b = ibuf_reserve(cte->buf, 1);
370 	if (b == NULL)
371 		fatal("out of memory");
372 	*b = '\0';
373 
374 	head = cte->buf->buf;
375 	host = cte->host;
376 	host->he = HCE_HTTP_DIGEST_ERROR;
377 
378 	if ((head = strstr(head, "\r\n\r\n")) == NULL) {
379 		log_debug("check_http_digest: %s failed "
380 		    "(no end of headers)", host->conf.name);
381 		host->up = HOST_DOWN;
382 		return (1);
383 	}
384 	head += strlen("\r\n\r\n");
385 
386 	digeststr(cte->table->conf.digest_type, head, strlen(head), digest);
387 
388 	if (strcmp(cte->table->conf.digest, digest)) {
389 		log_warnx("check_http_digest: %s failed "
390 		    "(wrong digest)", host->conf.name);
391 		host->he = HCE_HTTP_DIGEST_FAIL;
392 		host->up = HOST_DOWN;
393 	} else {
394 		host->he = HCE_HTTP_DIGEST_OK;
395 		host->up = HOST_UP;
396 	}
397 	return (!(host->up == HOST_UP));
398 }
399