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