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