xref: /openbsd-src/usr.sbin/relayd/hce.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: hce.c,v 1.47 2009/04/17 09:47:06 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/time.h>
22 #include <sys/stat.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25 
26 #include <net/if.h>
27 #include <netinet/in_systm.h>
28 #include <netinet/in.h>
29 #include <netinet/ip.h>
30 
31 #include <errno.h>
32 #include <event.h>
33 #include <fcntl.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <err.h>
38 #include <pwd.h>
39 
40 #include <openssl/ssl.h>
41 
42 #include "relayd.h"
43 
44 __dead void hce_shutdown(void);
45 void	hce_sig_handler(int sig, short, void *);
46 void	hce_dispatch_imsg(int, short, void *);
47 void	hce_dispatch_parent(int, short, void *);
48 void	hce_launch_checks(int, short, void *);
49 void	hce_setup_events(void);
50 void	hce_disable_events(void);
51 
52 static struct relayd *env = NULL;
53 struct imsgbuf		*ibuf_pfe;
54 struct imsgbuf		*ibuf_main;
55 int			 running = 0;
56 
57 void
58 hce_sig_handler(int sig, short event, void *arg)
59 {
60 	switch (sig) {
61 	case SIGINT:
62 	case SIGTERM:
63 		hce_shutdown();
64 		break;
65 	default:
66 		fatalx("hce_sig_handler: unexpected signal");
67 	}
68 }
69 
70 pid_t
71 hce(struct relayd *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
72     int pipe_parent2relay[RELAY_MAXPROC][2], int pipe_pfe2hce[2],
73     int pipe_pfe2relay[RELAY_MAXPROC][2])
74 {
75 	pid_t		 pid;
76 	struct passwd	*pw;
77 	int		 i;
78 	struct event	 ev_sigint;
79 	struct event	 ev_sigterm;
80 
81 	switch (pid = fork()) {
82 	case -1:
83 		fatal("hce: cannot fork");
84 	case 0:
85 		break;
86 	default:
87 		return (pid);
88 	}
89 
90 	env = x_env;
91 	purge_config(env, PURGE_RDRS|PURGE_RELAYS|PURGE_PROTOS);
92 
93 	if ((pw = getpwnam(RELAYD_USER)) == NULL)
94 		fatal("hce: getpwnam");
95 
96 #ifndef DEBUG
97 	if (chroot(pw->pw_dir) == -1)
98 		fatal("hce: chroot");
99 	if (chdir("/") == -1)
100 		fatal("hce: chdir(\"/\")");
101 #else
102 #warning disabling privilege revocation and chroot in DEBUG mode
103 #endif
104 
105 	setproctitle("host check engine");
106 	relayd_process = PROC_HCE;
107 
108 	/* this is needed for icmp tests */
109 	icmp_init(env);
110 
111 #ifndef DEBUG
112 	if (setgroups(1, &pw->pw_gid) ||
113 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
114 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
115 		fatal("hce: can't drop privileges");
116 #endif
117 
118 	event_init();
119 
120 	if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
121 	    (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
122 		fatal("hce");
123 	imsg_init(ibuf_pfe, pipe_pfe2hce[0], hce_dispatch_imsg);
124 	imsg_init(ibuf_main, pipe_parent2hce[1], hce_dispatch_parent);
125 
126 	ibuf_pfe->events = EV_READ;
127 	event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
128 	    ibuf_pfe->handler, ibuf_pfe);
129 	event_add(&ibuf_pfe->ev, NULL);
130 
131 	ibuf_main->events = EV_READ;
132 	event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
133 	    ibuf_main->handler, ibuf_main);
134 	event_add(&ibuf_main->ev, NULL);
135 
136 	signal_set(&ev_sigint, SIGINT, hce_sig_handler, NULL);
137 	signal_set(&ev_sigterm, SIGTERM, hce_sig_handler, NULL);
138 	signal_add(&ev_sigint, NULL);
139 	signal_add(&ev_sigterm, NULL);
140 	signal(SIGPIPE, SIG_IGN);
141 	signal(SIGHUP, SIG_IGN);
142 
143 	/* setup pipes */
144 	close(pipe_pfe2hce[1]);
145 	close(pipe_parent2hce[0]);
146 	close(pipe_parent2pfe[0]);
147 	close(pipe_parent2pfe[1]);
148 	for (i = 0; i < env->sc_prefork_relay; i++) {
149 		close(pipe_parent2relay[i][0]);
150 		close(pipe_parent2relay[i][1]);
151 		close(pipe_pfe2relay[i][0]);
152 		close(pipe_pfe2relay[i][1]);
153 	}
154 
155 	hce_setup_events();
156 	event_dispatch();
157 	hce_shutdown();
158 
159 	return (0);
160 }
161 
162 void
163 hce_setup_events(void)
164 {
165 	struct timeval	 tv;
166 	struct table	*table;
167 
168 	snmp_init(env, ibuf_main);
169 
170 	if (!TAILQ_EMPTY(env->sc_tables)) {
171 		evtimer_set(&env->sc_ev, hce_launch_checks, env);
172 		bzero(&tv, sizeof(tv));
173 		evtimer_add(&env->sc_ev, &tv);
174 	}
175 
176 	if (env->sc_flags & F_SSL) {
177 		ssl_init(env);
178 		TAILQ_FOREACH(table, env->sc_tables, entry) {
179 			if (!(table->conf.flags & F_SSL))
180 				continue;
181 			table->ssl_ctx = ssl_ctx_create(env);
182 		}
183 	}
184 }
185 
186 void
187 hce_disable_events(void)
188 {
189 	struct table	*table;
190 	struct host	*host;
191 
192 	evtimer_del(&env->sc_ev);
193 	TAILQ_FOREACH(table, env->sc_tables, entry) {
194 		TAILQ_FOREACH(host, &table->hosts, entry) {
195 			host->he = HCE_ABORT;
196 			event_del(&host->cte.ev);
197 			close(host->cte.s);
198 		}
199 	}
200 	if (env->sc_has_icmp) {
201 		event_del(&env->sc_icmp_send.ev);
202 		event_del(&env->sc_icmp_recv.ev);
203 	}
204 	if (env->sc_has_icmp6) {
205 		event_del(&env->sc_icmp6_send.ev);
206 		event_del(&env->sc_icmp6_recv.ev);
207 	}
208 }
209 
210 void
211 hce_launch_checks(int fd, short event, void *arg)
212 {
213 	struct host		*host;
214 	struct table		*table;
215 	struct timeval		 tv;
216 
217 	/*
218 	 * notify pfe checks are done and schedule next check
219 	 */
220 	imsg_compose(ibuf_pfe, IMSG_SYNC, 0, 0, -1, NULL, 0);
221 	TAILQ_FOREACH(table, env->sc_tables, entry) {
222 		TAILQ_FOREACH(host, &table->hosts, entry) {
223 			if ((host->flags & F_CHECK_DONE) == 0)
224 				host->he = HCE_INTERVAL_TIMEOUT;
225 			host->flags &= ~(F_CHECK_SENT|F_CHECK_DONE);
226 			event_del(&host->cte.ev);
227 		}
228 	}
229 
230 	if (gettimeofday(&tv, NULL) == -1)
231 		fatal("hce_launch_checks: gettimeofday");
232 
233 	TAILQ_FOREACH(table, env->sc_tables, entry) {
234 		if (table->conf.flags & F_DISABLE)
235 			continue;
236 		if (table->conf.skip_cnt) {
237 			if (table->skipped++ > table->conf.skip_cnt)
238 				table->skipped = 0;
239 			if (table->skipped != 1)
240 				continue;
241 		}
242 		if (table->conf.check == CHECK_NOCHECK)
243 			fatalx("hce_launch_checks: unknown check type");
244 
245 		TAILQ_FOREACH(host, &table->hosts, entry) {
246 			if (host->flags & F_DISABLE || host->conf.parentid)
247 				continue;
248 			switch (table->conf.check) {
249 			case CHECK_ICMP:
250 				schedule_icmp(env, host);
251 				break;
252 			case CHECK_SCRIPT:
253 				check_script(host);
254 				break;
255 			default:
256 				/* Any other TCP-style checks */
257 				bzero(&host->cte, sizeof(host->cte));
258 				host->last_up = host->up;
259 				host->cte.host = host;
260 				host->cte.table = table;
261 				bcopy(&tv, &host->cte.tv_start,
262 				    sizeof(host->cte.tv_start));
263 				check_tcp(&host->cte);
264 				break;
265 			}
266 		}
267 	}
268 	check_icmp(env, &tv);
269 
270 	bcopy(&env->sc_interval, &tv, sizeof(tv));
271 	evtimer_add(&env->sc_ev, &tv);
272 }
273 
274 void
275 hce_notify_done(struct host *host, enum host_error he)
276 {
277 	struct table		*table;
278 	struct ctl_status	 st;
279 	struct timeval		 tv_now, tv_dur;
280 	u_long			 duration;
281 	u_int			 logopt;
282 	struct host		*h;
283 	int			 hostup;
284 	const char		*msg;
285 
286 	hostup = host->up;
287 	host->he = he;
288 
289 	if (host->up == HOST_DOWN && host->retry_cnt) {
290 		log_debug("hce_notify_done: host %s retry %d",
291 		    host->conf.name, host->retry_cnt);
292 		host->up = host->last_up;
293 		host->retry_cnt--;
294 	} else
295 		host->retry_cnt = host->conf.retry;
296 	if (host->up != HOST_UNKNOWN) {
297 		host->check_cnt++;
298 		if (host->up == HOST_UP)
299 			host->up_cnt++;
300 	}
301 	st.id = host->conf.id;
302 	st.up = host->up;
303 	st.check_cnt = host->check_cnt;
304 	st.retry_cnt = host->retry_cnt;
305 	st.he = he;
306 	host->flags |= (F_CHECK_SENT|F_CHECK_DONE);
307 	msg = host_error(he);
308 	if (msg)
309 		log_debug("hce_notify_done: %s (%s)", host->conf.name, msg);
310 
311 	imsg_compose(ibuf_pfe, IMSG_HOST_STATUS, 0, 0, -1, &st, sizeof(st));
312 	if (host->up != host->last_up)
313 		logopt = RELAYD_OPT_LOGUPDATE;
314 	else
315 		logopt = RELAYD_OPT_LOGNOTIFY;
316 
317 	if (gettimeofday(&tv_now, NULL) == -1)
318 		fatal("hce_notify_done: gettimeofday");
319 	timersub(&tv_now, &host->cte.tv_start, &tv_dur);
320 	if (timercmp(&host->cte.tv_start, &tv_dur, >))
321 		duration = (tv_dur.tv_sec * 1000) + (tv_dur.tv_usec / 1000.0);
322 	else
323 		duration = 0;
324 
325 	if ((table = table_find(env, host->conf.tableid)) == NULL)
326 		fatalx("hce_notify_done: invalid table id");
327 
328 	if (env->sc_opts & logopt) {
329 		log_info("host %s, check %s%s (%lums), state %s -> %s, "
330 		    "availability %s",
331 		    host->conf.name, table_check(table->conf.check),
332 		    (table->conf.flags & F_SSL) ? " use ssl" : "", duration,
333 		    host_status(host->last_up), host_status(host->up),
334 		    print_availability(host->check_cnt, host->up_cnt));
335 	}
336 
337 	if (host->last_up != host->up)
338 		snmp_hosttrap(table, host);
339 
340 	host->last_up = host->up;
341 
342 	if (SLIST_EMPTY(&host->children))
343 		return;
344 
345 	/* Notify for all other hosts that inherit the state from this one */
346 	SLIST_FOREACH(h, &host->children, child) {
347 		h->up = hostup;
348 		hce_notify_done(h, he);
349 	}
350 }
351 
352 void
353 hce_shutdown(void)
354 {
355 	log_info("host check engine exiting");
356 	_exit(0);
357 }
358 
359 void
360 hce_dispatch_imsg(int fd, short event, void *ptr)
361 {
362 	struct imsgbuf		*ibuf;
363 	struct imsg		 imsg;
364 	ssize_t			 n;
365 	objid_t			 id;
366 	struct host		*host;
367 	struct table		*table;
368 
369 	ibuf = ptr;
370 	switch (event) {
371 	case EV_READ:
372 		if ((n = imsg_read(ibuf)) == -1)
373 			fatal("hce_dispatch_imsg: imsg_read_error");
374 		if (n == 0) {
375 			/* this pipe is dead, so remove the event handler */
376 			event_del(&ibuf->ev);
377 			event_loopexit(NULL);
378 			return;
379 		}
380 		break;
381 	case EV_WRITE:
382 		if (msgbuf_write(&ibuf->w) == -1)
383 			fatal("hce_dispatch_imsg: msgbuf_write");
384 		imsg_event_add(ibuf);
385 		return;
386 	default:
387 		fatalx("hce_dispatch_imsg: unknown event");
388 	}
389 
390 	for (;;) {
391 		if ((n = imsg_get(ibuf, &imsg)) == -1)
392 			fatal("hce_dispatch_imsg: imsg_read error");
393 		if (n == 0)
394 			break;
395 
396 		switch (imsg.hdr.type) {
397 		case IMSG_HOST_DISABLE:
398 			memcpy(&id, imsg.data, sizeof(id));
399 			if ((host = host_find(env, id)) == NULL)
400 				fatalx("hce_dispatch_imsg: desynchronized");
401 			host->flags |= F_DISABLE;
402 			host->up = HOST_UNKNOWN;
403 			host->check_cnt = 0;
404 			host->up_cnt = 0;
405 			host->he = HCE_NONE;
406 			break;
407 		case IMSG_HOST_ENABLE:
408 			memcpy(&id, imsg.data, sizeof(id));
409 			if ((host = host_find(env, id)) == NULL)
410 				fatalx("hce_dispatch_imsg: desynchronized");
411 			host->flags &= ~(F_DISABLE);
412 			host->up = HOST_UNKNOWN;
413 			host->he = HCE_NONE;
414 			break;
415 		case IMSG_TABLE_DISABLE:
416 			memcpy(&id, imsg.data, sizeof(id));
417 			if ((table = table_find(env, id)) == NULL)
418 				fatalx("hce_dispatch_imsg: desynchronized");
419 			table->conf.flags |= F_DISABLE;
420 			TAILQ_FOREACH(host, &table->hosts, entry)
421 				host->up = HOST_UNKNOWN;
422 			break;
423 		case IMSG_TABLE_ENABLE:
424 			memcpy(&id, imsg.data, sizeof(id));
425 			if ((table = table_find(env, id)) == NULL)
426 				fatalx("hce_dispatch_imsg: desynchronized");
427 			table->conf.flags &= ~(F_DISABLE);
428 			TAILQ_FOREACH(host, &table->hosts, entry)
429 				host->up = HOST_UNKNOWN;
430 			break;
431 		case IMSG_CTL_POLL:
432 			evtimer_del(&env->sc_ev);
433 			TAILQ_FOREACH(table, env->sc_tables, entry)
434 				table->skipped = 0;
435 			hce_launch_checks(-1, EV_TIMEOUT, env);
436 			break;
437 		default:
438 			log_debug("hce_dispatch_msg: unexpected imsg %d",
439 			    imsg.hdr.type);
440 			break;
441 		}
442 		imsg_free(&imsg);
443 	}
444 	imsg_event_add(ibuf);
445 }
446 
447 void
448 hce_dispatch_parent(int fd, short event, void * ptr)
449 {
450 	struct imsgbuf		*ibuf;
451 	struct imsg		 imsg;
452 	struct ctl_script	 scr;
453 	ssize_t			 n;
454 	size_t			 len;
455 	static struct table	*table = NULL;
456 	struct host		*host, *parent;
457 
458 	ibuf = ptr;
459 	switch (event) {
460 	case EV_READ:
461 		if ((n = imsg_read(ibuf)) == -1)
462 			fatal("hce_dispatch_parent: imsg_read error");
463 		if (n == 0) {
464 			/* this pipe is dead, so remove the event handler */
465 			event_del(&ibuf->ev);
466 			event_loopexit(NULL);
467 			return;
468 		}
469 		break;
470 	case EV_WRITE:
471 		if (msgbuf_write(&ibuf->w) == -1)
472 			fatal("hce_dispatch_parent: msgbuf_write");
473 		imsg_event_add(ibuf);
474 		return;
475 	default:
476 		fatalx("hce_dispatch_parent: unknown event");
477 	}
478 
479 	for (;;) {
480 		if ((n = imsg_get(ibuf, &imsg)) == -1)
481 			fatal("hce_dispatch_parent: imsg_read error");
482 		if (n == 0)
483 			break;
484 
485 		switch (imsg.hdr.type) {
486 		case IMSG_SCRIPT:
487 			if (imsg.hdr.len - IMSG_HEADER_SIZE !=
488 			    sizeof(scr))
489 				fatalx("hce_dispatch_parent: "
490 				    "invalid size of script request");
491 			bcopy(imsg.data, &scr, sizeof(scr));
492 			script_done(env, &scr);
493 			break;
494 		case IMSG_RECONF:
495 			log_debug("hce: reloading configuration");
496 			if (imsg.hdr.len !=
497 			    sizeof(struct relayd) + IMSG_HEADER_SIZE)
498 				fatalx("corrupted reload data");
499 			hce_disable_events();
500 			purge_config(env, PURGE_TABLES);
501 			merge_config(env, (struct relayd *)imsg.data);
502 
503 			env->sc_tables = calloc(1, sizeof(*env->sc_tables));
504 			if (env->sc_tables == NULL)
505 				fatal(NULL);
506 
507 			TAILQ_INIT(env->sc_tables);
508 			break;
509 		case IMSG_RECONF_TABLE:
510 			if ((table = calloc(1, sizeof(*table))) == NULL)
511 				fatal(NULL);
512 			memcpy(&table->conf, imsg.data, sizeof(table->conf));
513 			TAILQ_INIT(&table->hosts);
514 			TAILQ_INSERT_TAIL(env->sc_tables, table, entry);
515 			break;
516 		case IMSG_RECONF_SENDBUF:
517 			len = imsg.hdr.len - IMSG_HEADER_SIZE;
518 			table->sendbuf = calloc(1, len);
519 			(void)strlcpy(table->sendbuf, (char *)imsg.data, len);
520 			break;
521 		case IMSG_RECONF_HOST:
522 			if ((host = calloc(1, sizeof(*host))) == NULL)
523 				fatal(NULL);
524 			memcpy(&host->conf, imsg.data, sizeof(host->conf));
525 			host->tablename = table->conf.name;
526 			TAILQ_INSERT_TAIL(&table->hosts, host, entry);
527 			if (host->conf.parentid) {
528 				parent = host_find(env, host->conf.parentid);
529 				SLIST_INSERT_HEAD(&parent->children,
530 				    host, child);
531 			}
532 			break;
533 		case IMSG_RECONF_END:
534 			log_warnx("hce: configuration reloaded");
535 			hce_setup_events();
536 			break;
537 		default:
538 			log_debug("hce_dispatch_parent: unexpected imsg %d",
539 			    imsg.hdr.type);
540 			break;
541 		}
542 		imsg_free(&imsg);
543 	}
544 	imsg_event_add(ibuf);
545 }
546