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