xref: /openbsd/usr.sbin/relayd/pfe.c (revision 5af055cd)
1 /*	$OpenBSD: pfe.c,v 1.83 2015/12/03 16:12:16 benno 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/types.h>
20 #include <sys/queue.h>
21 #include <sys/time.h>
22 #include <sys/uio.h>
23 
24 #include <event.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <imsg.h>
29 
30 #include "relayd.h"
31 
32 void	 pfe_init(struct privsep *, struct privsep_proc *p, void *);
33 void	 pfe_shutdown(void);
34 void	 pfe_setup_events(void);
35 void	 pfe_disable_events(void);
36 void	 pfe_sync(void);
37 void	 pfe_statistics(int, short, void *);
38 
39 int	 pfe_dispatch_parent(int, struct privsep_proc *, struct imsg *);
40 int	 pfe_dispatch_hce(int, struct privsep_proc *, struct imsg *);
41 int	 pfe_dispatch_relay(int, struct privsep_proc *, struct imsg *);
42 
43 static struct relayd		*env = NULL;
44 
45 static struct privsep_proc procs[] = {
46 	{ "parent",	PROC_PARENT,	pfe_dispatch_parent },
47 	{ "relay",	PROC_RELAY,	pfe_dispatch_relay },
48 	{ "hce",	PROC_HCE,	pfe_dispatch_hce }
49 };
50 
51 pid_t
52 pfe(struct privsep *ps, struct privsep_proc *p)
53 {
54 	env = ps->ps_env;
55 
56 	return (proc_run(ps, p, procs, nitems(procs), pfe_init, NULL));
57 }
58 
59 void
60 pfe_init(struct privsep *ps, struct privsep_proc *p, void *arg)
61 {
62 	if (config_init(ps->ps_env) == -1)
63 		fatal("failed to initialize configuration");
64 
65 	snmp_init(env, PROC_PARENT);
66 
67 	if (pledge("stdio recvfd unix pf", NULL) == -1)
68 		fatal("pledge");
69 
70 	p->p_shutdown = pfe_shutdown;
71 }
72 
73 void
74 pfe_shutdown(void)
75 {
76 	flush_rulesets(env);
77 	config_purge(env, CONFIG_ALL);
78 }
79 
80 void
81 pfe_setup_events(void)
82 {
83 	struct timeval	 tv;
84 
85 	/* Schedule statistics timer */
86 	if (!event_initialized(&env->sc_statev)) {
87 		evtimer_set(&env->sc_statev, pfe_statistics, NULL);
88 		bcopy(&env->sc_statinterval, &tv, sizeof(tv));
89 		evtimer_add(&env->sc_statev, &tv);
90 	}
91 }
92 
93 void
94 pfe_disable_events(void)
95 {
96 	event_del(&env->sc_statev);
97 }
98 
99 int
100 pfe_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg)
101 {
102 	struct host		*host;
103 	struct table		*table;
104 	struct ctl_status	 st;
105 
106 	control_imsg_forward(imsg);
107 
108 	switch (imsg->hdr.type) {
109 	case IMSG_HOST_STATUS:
110 		IMSG_SIZE_CHECK(imsg, &st);
111 		memcpy(&st, imsg->data, sizeof(st));
112 		if ((host = host_find(env, st.id)) == NULL)
113 			fatalx("pfe_dispatch_hce: invalid host id");
114 		host->he = st.he;
115 		if (host->flags & F_DISABLE)
116 			break;
117 		host->retry_cnt = st.retry_cnt;
118 		if (st.up != HOST_UNKNOWN) {
119 			host->check_cnt++;
120 			if (st.up == HOST_UP)
121 				host->up_cnt++;
122 		}
123 		if (host->check_cnt != st.check_cnt) {
124 			log_debug("%s: host %d => %d", __func__,
125 			    host->conf.id, host->up);
126 			fatalx("pfe_dispatch_hce: desynchronized");
127 		}
128 
129 		if (host->up == st.up)
130 			break;
131 
132 		/* Forward to relay engine(s) */
133 		proc_compose(env->sc_ps, PROC_RELAY,
134 		    IMSG_HOST_STATUS, &st, sizeof(st));
135 
136 		if ((table = table_find(env, host->conf.tableid))
137 		    == NULL)
138 			fatalx("pfe_dispatch_hce: invalid table id");
139 
140 		log_debug("%s: state %d for host %u %s", __func__,
141 		    st.up, host->conf.id, host->conf.name);
142 
143 		snmp_hosttrap(env, table, host);
144 
145 		/*
146 		 * Do not change the table state when the host
147 		 * state switches between UNKNOWN and DOWN.
148 		 */
149 		if (HOST_ISUP(st.up)) {
150 			table->conf.flags |= F_CHANGED;
151 			table->up++;
152 			host->flags |= F_ADD;
153 			host->flags &= ~(F_DEL);
154 		} else if (HOST_ISUP(host->up)) {
155 			table->up--;
156 			table->conf.flags |= F_CHANGED;
157 			host->flags |= F_DEL;
158 			host->flags &= ~(F_ADD);
159 			host->up = st.up;
160 			pfe_sync();
161 		}
162 
163 		host->up = st.up;
164 		break;
165 	case IMSG_SYNC:
166 		pfe_sync();
167 		break;
168 	default:
169 		return (-1);
170 	}
171 
172 	return (0);
173 }
174 
175 int
176 pfe_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
177 {
178 	switch (imsg->hdr.type) {
179 	case IMSG_CFG_TABLE:
180 		config_gettable(env, imsg);
181 		break;
182 	case IMSG_CFG_HOST:
183 		config_gethost(env, imsg);
184 		break;
185 	case IMSG_CFG_RDR:
186 		config_getrdr(env, imsg);
187 		break;
188 	case IMSG_CFG_VIRT:
189 		config_getvirt(env, imsg);
190 		break;
191 	case IMSG_CFG_ROUTER:
192 		config_getrt(env, imsg);
193 		break;
194 	case IMSG_CFG_ROUTE:
195 		config_getroute(env, imsg);
196 		break;
197 	case IMSG_CFG_PROTO:
198 		config_getproto(env, imsg);
199 		break;
200 	case IMSG_CFG_RELAY:
201 		config_getrelay(env, imsg);
202 		break;
203 	case IMSG_CFG_RELAY_TABLE:
204 		config_getrelaytable(env, imsg);
205 		break;
206 	case IMSG_CFG_DONE:
207 		config_getcfg(env, imsg);
208 		init_filter(env, imsg->fd);
209 		init_tables(env);
210 		break;
211 	case IMSG_CTL_START:
212 		pfe_setup_events();
213 		pfe_sync();
214 		break;
215 	case IMSG_CTL_RESET:
216 		config_getreset(env, imsg);
217 		break;
218 	case IMSG_SNMPSOCK:
219 		snmp_getsock(env, imsg);
220 		break;
221 	default:
222 		return (-1);
223 	}
224 
225 	return (0);
226 }
227 
228 int
229 pfe_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
230 {
231 	struct ctl_natlook	 cnl;
232 	struct ctl_stats	 crs;
233 	struct relay		*rlay;
234 	struct ctl_conn		*c;
235 	struct rsession		 con, *s, *t;
236 	int			 cid;
237 	objid_t			 sid;
238 
239 	switch (imsg->hdr.type) {
240 	case IMSG_NATLOOK:
241 		IMSG_SIZE_CHECK(imsg, &cnl);
242 		bcopy(imsg->data, &cnl, sizeof(cnl));
243 		if (cnl.proc > env->sc_prefork_relay)
244 			fatalx("pfe_dispatch_relay: "
245 			    "invalid relay proc");
246 		if (natlook(env, &cnl) != 0)
247 			cnl.in = -1;
248 		proc_compose_imsg(env->sc_ps, PROC_RELAY, cnl.proc,
249 		    IMSG_NATLOOK, -1, -1, &cnl, sizeof(cnl));
250 		break;
251 	case IMSG_STATISTICS:
252 		IMSG_SIZE_CHECK(imsg, &crs);
253 		bcopy(imsg->data, &crs, sizeof(crs));
254 		if (crs.proc > env->sc_prefork_relay)
255 			fatalx("pfe_dispatch_relay: "
256 			    "invalid relay proc");
257 		if ((rlay = relay_find(env, crs.id)) == NULL)
258 			fatalx("pfe_dispatch_relay: invalid relay id");
259 		bcopy(&crs, &rlay->rl_stats[crs.proc], sizeof(crs));
260 		rlay->rl_stats[crs.proc].interval =
261 		    env->sc_statinterval.tv_sec;
262 		break;
263 	case IMSG_CTL_SESSION:
264 		IMSG_SIZE_CHECK(imsg, &con);
265 		memcpy(&con, imsg->data, sizeof(con));
266 		if ((c = control_connbyfd(con.se_cid)) == NULL) {
267 			log_debug("%s: control connection %d not found",
268 			    __func__, con.se_cid);
269 			return (0);
270 		}
271 		imsg_compose_event(&c->iev,
272 		    IMSG_CTL_SESSION, 0, 0, -1,
273 		    &con, sizeof(con));
274 		break;
275 	case IMSG_CTL_END:
276 		IMSG_SIZE_CHECK(imsg, &cid);
277 		memcpy(&cid, imsg->data, sizeof(cid));
278 		if ((c = control_connbyfd(cid)) == NULL) {
279 			log_debug("%s: control connection %d not found",
280 			    __func__, cid);
281 			return (0);
282 		}
283 		if (c->waiting == 0) {
284 			log_debug("%s: no pending control requests", __func__);
285 			return (0);
286 		} else if (--c->waiting == 0) {
287 			/* Last ack for a previous request */
288 			imsg_compose_event(&c->iev, IMSG_CTL_END,
289 			    0, 0, -1, NULL, 0);
290 		}
291 		break;
292 	case IMSG_SESS_PUBLISH:
293 		IMSG_SIZE_CHECK(imsg, s);
294 		if ((s = calloc(1, sizeof(*s))) == NULL)
295 			return (0);		/* XXX */
296 		memcpy(s, imsg->data, sizeof(*s));
297 		TAILQ_FOREACH(t, &env->sc_sessions, se_entry) {
298 			/* duplicate registration */
299 			if (t->se_id == s->se_id) {
300 				free(s);
301 				return (0);
302 			}
303 			if (t->se_id > s->se_id)
304 				break;
305 		}
306 		if (t)
307 			TAILQ_INSERT_BEFORE(t, s, se_entry);
308 		else
309 			TAILQ_INSERT_TAIL(&env->sc_sessions, s, se_entry);
310 		break;
311 	case IMSG_SESS_UNPUBLISH:
312 		IMSG_SIZE_CHECK(imsg, &sid);
313 		memcpy(&sid, imsg->data, sizeof(sid));
314 		TAILQ_FOREACH(s, &env->sc_sessions, se_entry)
315 			if (s->se_id == sid)
316 				break;
317 		if (s) {
318 			TAILQ_REMOVE(&env->sc_sessions, s, se_entry);
319 			free(s);
320 		} else {
321 			DPRINTF("removal of unpublished session %i", sid);
322 		}
323 		break;
324 	default:
325 		return (-1);
326 	}
327 
328 	return (0);
329 }
330 
331 void
332 show(struct ctl_conn *c)
333 {
334 	struct rdr		*rdr;
335 	struct host		*host;
336 	struct relay		*rlay;
337 	struct router		*rt;
338 	struct netroute		*nr;
339 	struct relay_table	*rlt;
340 
341 	if (env->sc_rdrs == NULL)
342 		goto relays;
343 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
344 		imsg_compose_event(&c->iev, IMSG_CTL_RDR, 0, 0, -1,
345 		    rdr, sizeof(*rdr));
346 		if (rdr->conf.flags & F_DISABLE)
347 			continue;
348 
349 		imsg_compose_event(&c->iev, IMSG_CTL_RDR_STATS, 0, 0, -1,
350 		    &rdr->stats, sizeof(rdr->stats));
351 
352 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
353 		    rdr->table, sizeof(*rdr->table));
354 		if (!(rdr->table->conf.flags & F_DISABLE))
355 			TAILQ_FOREACH(host, &rdr->table->hosts, entry)
356 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
357 				    0, 0, -1, host, sizeof(*host));
358 
359 		if (rdr->backup->conf.id == EMPTY_TABLE)
360 			continue;
361 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
362 		    rdr->backup, sizeof(*rdr->backup));
363 		if (!(rdr->backup->conf.flags & F_DISABLE))
364 			TAILQ_FOREACH(host, &rdr->backup->hosts, entry)
365 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
366 				    0, 0, -1, host, sizeof(*host));
367 	}
368 relays:
369 	if (env->sc_relays == NULL)
370 		goto routers;
371 	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
372 		rlay->rl_stats[env->sc_prefork_relay].id = EMPTY_ID;
373 		imsg_compose_event(&c->iev, IMSG_CTL_RELAY, 0, 0, -1,
374 		    rlay, sizeof(*rlay));
375 		imsg_compose_event(&c->iev, IMSG_CTL_RELAY_STATS, 0, 0, -1,
376 		    &rlay->rl_stats, sizeof(rlay->rl_stats));
377 
378 		TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) {
379 			imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
380 			    rlt->rlt_table, sizeof(*rlt->rlt_table));
381 			if (!(rlt->rlt_table->conf.flags & F_DISABLE))
382 				TAILQ_FOREACH(host,
383 				    &rlt->rlt_table->hosts, entry)
384 					imsg_compose_event(&c->iev,
385 					    IMSG_CTL_HOST, 0, 0, -1,
386 					    host, sizeof(*host));
387 		}
388 	}
389 
390 routers:
391 	if (env->sc_rts == NULL)
392 		goto end;
393 	TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
394 		imsg_compose_event(&c->iev, IMSG_CTL_ROUTER, 0, 0, -1,
395 		    rt, sizeof(*rt));
396 		if (rt->rt_conf.flags & F_DISABLE)
397 			continue;
398 
399 		TAILQ_FOREACH(nr, &rt->rt_netroutes, nr_entry)
400 			imsg_compose_event(&c->iev, IMSG_CTL_NETROUTE,
401 			    0, 0, -1, nr, sizeof(*nr));
402 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
403 		    rt->rt_gwtable, sizeof(*rt->rt_gwtable));
404 		if (!(rt->rt_gwtable->conf.flags & F_DISABLE))
405 			TAILQ_FOREACH(host, &rt->rt_gwtable->hosts, entry)
406 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
407 				    0, 0, -1, host, sizeof(*host));
408 	}
409 
410 end:
411 	imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0);
412 }
413 
414 void
415 show_sessions(struct ctl_conn *c)
416 {
417 	int			 proc, cid;
418 
419 	for (proc = 0; proc < env->sc_prefork_relay; proc++) {
420 		cid = c->iev.ibuf.fd;
421 
422 		/*
423 		 * Request all the running sessions from the process
424 		 */
425 		proc_compose_imsg(env->sc_ps, PROC_RELAY, proc,
426 		    IMSG_CTL_SESSION, -1, -1, &cid, sizeof(cid));
427 		c->waiting++;
428 	}
429 }
430 
431 int
432 disable_rdr(struct ctl_conn *c, struct ctl_id *id)
433 {
434 	struct rdr	*rdr;
435 
436 	if (id->id == EMPTY_ID)
437 		rdr = rdr_findbyname(env, id->name);
438 	else
439 		rdr = rdr_find(env, id->id);
440 	if (rdr == NULL)
441 		return (-1);
442 	id->id = rdr->conf.id;
443 
444 	if (rdr->conf.flags & F_DISABLE)
445 		return (0);
446 
447 	rdr->conf.flags |= F_DISABLE;
448 	rdr->conf.flags &= ~(F_ADD);
449 	rdr->conf.flags |= F_DEL;
450 	rdr->table->conf.flags |= F_DISABLE;
451 	log_debug("%s: redirect %d", __func__, rdr->conf.id);
452 	pfe_sync();
453 	return (0);
454 }
455 
456 int
457 enable_rdr(struct ctl_conn *c, struct ctl_id *id)
458 {
459 	struct rdr	*rdr;
460 	struct ctl_id	 eid;
461 
462 	if (id->id == EMPTY_ID)
463 		rdr = rdr_findbyname(env, id->name);
464 	else
465 		rdr = rdr_find(env, id->id);
466 	if (rdr == NULL)
467 		return (-1);
468 	id->id = rdr->conf.id;
469 
470 	if (!(rdr->conf.flags & F_DISABLE))
471 		return (0);
472 
473 	rdr->conf.flags &= ~(F_DISABLE);
474 	rdr->conf.flags &= ~(F_DEL);
475 	rdr->conf.flags |= F_ADD;
476 	log_debug("%s: redirect %d", __func__, rdr->conf.id);
477 
478 	bzero(&eid, sizeof(eid));
479 
480 	/* XXX: we're syncing twice */
481 	eid.id = rdr->table->conf.id;
482 	if (enable_table(c, &eid) == -1)
483 		return (-1);
484 	if (rdr->backup->conf.id == EMPTY_ID)
485 		return (0);
486 	eid.id = rdr->backup->conf.id;
487 	if (enable_table(c, &eid) == -1)
488 		return (-1);
489 	return (0);
490 }
491 
492 int
493 disable_table(struct ctl_conn *c, struct ctl_id *id)
494 {
495 	struct table	*table;
496 	struct host	*host;
497 
498 	if (id->id == EMPTY_ID)
499 		table = table_findbyname(env, id->name);
500 	else
501 		table = table_find(env, id->id);
502 	if (table == NULL)
503 		return (-1);
504 	id->id = table->conf.id;
505 	if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
506 		fatalx("disable_table: desynchronised");
507 
508 	if (table->conf.flags & F_DISABLE)
509 		return (0);
510 	table->conf.flags |= (F_DISABLE|F_CHANGED);
511 	table->up = 0;
512 	TAILQ_FOREACH(host, &table->hosts, entry)
513 		host->up = HOST_UNKNOWN;
514 	proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_DISABLE,
515 	    &table->conf.id, sizeof(table->conf.id));
516 
517 	/* Forward to relay engine(s) */
518 	proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_DISABLE,
519 	    &table->conf.id, sizeof(table->conf.id));
520 
521 	log_debug("%s: table %d", __func__, table->conf.id);
522 	pfe_sync();
523 	return (0);
524 }
525 
526 int
527 enable_table(struct ctl_conn *c, struct ctl_id *id)
528 {
529 	struct table	*table;
530 	struct host	*host;
531 
532 	if (id->id == EMPTY_ID)
533 		table = table_findbyname(env, id->name);
534 	else
535 		table = table_find(env, id->id);
536 	if (table == NULL)
537 		return (-1);
538 	id->id = table->conf.id;
539 
540 	if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
541 		fatalx("enable_table: desynchronised");
542 
543 	if (!(table->conf.flags & F_DISABLE))
544 		return (0);
545 	table->conf.flags &= ~(F_DISABLE);
546 	table->conf.flags |= F_CHANGED;
547 	table->up = 0;
548 	TAILQ_FOREACH(host, &table->hosts, entry)
549 		host->up = HOST_UNKNOWN;
550 	proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_ENABLE,
551 	    &table->conf.id, sizeof(table->conf.id));
552 
553 	/* Forward to relay engine(s) */
554 	proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_ENABLE,
555 	    &table->conf.id, sizeof(table->conf.id));
556 
557 	log_debug("%s: table %d", __func__, table->conf.id);
558 	pfe_sync();
559 	return (0);
560 }
561 
562 int
563 disable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
564 {
565 	struct host	*h;
566 	struct table	*table;
567 
568 	if (host == NULL) {
569 		if (id->id == EMPTY_ID)
570 			host = host_findbyname(env, id->name);
571 		else
572 			host = host_find(env, id->id);
573 		if (host == NULL || host->conf.parentid)
574 			return (-1);
575 	}
576 	id->id = host->conf.id;
577 
578 	if (host->flags & F_DISABLE)
579 		return (0);
580 
581 	if (host->up == HOST_UP) {
582 		if ((table = table_find(env, host->conf.tableid)) == NULL)
583 			fatalx("disable_host: invalid table id");
584 		table->up--;
585 		table->conf.flags |= F_CHANGED;
586 	}
587 
588 	host->up = HOST_UNKNOWN;
589 	host->flags |= F_DISABLE;
590 	host->flags |= F_DEL;
591 	host->flags &= ~(F_ADD);
592 	host->check_cnt = 0;
593 	host->up_cnt = 0;
594 
595 	proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_DISABLE,
596 	    &host->conf.id, sizeof(host->conf.id));
597 
598 	/* Forward to relay engine(s) */
599 	proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_DISABLE,
600 	    &host->conf.id, sizeof(host->conf.id));
601 	log_debug("%s: host %d", __func__, host->conf.id);
602 
603 	if (!host->conf.parentid) {
604 		/* Disable all children */
605 		SLIST_FOREACH(h, &host->children, child)
606 			disable_host(c, id, h);
607 		pfe_sync();
608 	}
609 	return (0);
610 }
611 
612 int
613 enable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
614 {
615 	struct host	*h;
616 
617 	if (host == NULL) {
618 		if (id->id == EMPTY_ID)
619 			host = host_findbyname(env, id->name);
620 		else
621 			host = host_find(env, id->id);
622 		if (host == NULL || host->conf.parentid)
623 			return (-1);
624 	}
625 	id->id = host->conf.id;
626 
627 	if (!(host->flags & F_DISABLE))
628 		return (0);
629 
630 	host->up = HOST_UNKNOWN;
631 	host->flags &= ~(F_DISABLE);
632 	host->flags &= ~(F_DEL);
633 	host->flags &= ~(F_ADD);
634 
635 	proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_ENABLE,
636 	    &host->conf.id, sizeof (host->conf.id));
637 
638 	/* Forward to relay engine(s) */
639 	proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_ENABLE,
640 	    &host->conf.id, sizeof(host->conf.id));
641 
642 	log_debug("%s: host %d", __func__, host->conf.id);
643 
644 	if (!host->conf.parentid) {
645 		/* Enable all children */
646 		SLIST_FOREACH(h, &host->children, child)
647 			enable_host(c, id, h);
648 		pfe_sync();
649 	}
650 	return (0);
651 }
652 
653 void
654 pfe_sync(void)
655 {
656 	struct rdr		*rdr;
657 	struct table		*active;
658 	struct table		*table;
659 	struct ctl_id		 id;
660 	struct imsg		 imsg;
661 	struct ctl_demote	 demote;
662 	struct router		*rt;
663 
664 	bzero(&id, sizeof(id));
665 	bzero(&imsg, sizeof(imsg));
666 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
667 		rdr->conf.flags &= ~(F_BACKUP);
668 		rdr->conf.flags &= ~(F_DOWN);
669 
670 		if (rdr->conf.flags & F_DISABLE ||
671 		    (rdr->table->up == 0 && rdr->backup->up == 0)) {
672 			rdr->conf.flags |= F_DOWN;
673 			active = NULL;
674 		} else if (rdr->table->up == 0 && rdr->backup->up > 0) {
675 			rdr->conf.flags |= F_BACKUP;
676 			active = rdr->backup;
677 			active->conf.flags |=
678 			    rdr->table->conf.flags & F_CHANGED;
679 			active->conf.flags |=
680 			    rdr->backup->conf.flags & F_CHANGED;
681 		} else
682 			active = rdr->table;
683 
684 		if (active != NULL && active->conf.flags & F_CHANGED) {
685 			id.id = active->conf.id;
686 			imsg.hdr.type = IMSG_CTL_TABLE_CHANGED;
687 			imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
688 			imsg.data = &id;
689 			sync_table(env, rdr, active);
690 			control_imsg_forward(&imsg);
691 		}
692 
693 		if (rdr->conf.flags & F_DOWN) {
694 			if (rdr->conf.flags & F_ACTIVE_RULESET) {
695 				flush_table(env, rdr);
696 				log_debug("%s: disabling ruleset", __func__);
697 				rdr->conf.flags &= ~(F_ACTIVE_RULESET);
698 				id.id = rdr->conf.id;
699 				imsg.hdr.type = IMSG_CTL_PULL_RULESET;
700 				imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
701 				imsg.data = &id;
702 				sync_ruleset(env, rdr, 0);
703 				control_imsg_forward(&imsg);
704 			}
705 		} else if (!(rdr->conf.flags & F_ACTIVE_RULESET)) {
706 			log_debug("%s: enabling ruleset", __func__);
707 			rdr->conf.flags |= F_ACTIVE_RULESET;
708 			id.id = rdr->conf.id;
709 			imsg.hdr.type = IMSG_CTL_PUSH_RULESET;
710 			imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
711 			imsg.data = &id;
712 			sync_ruleset(env, rdr, 1);
713 			control_imsg_forward(&imsg);
714 		}
715 	}
716 
717 	TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
718 		rt->rt_conf.flags &= ~(F_BACKUP);
719 		rt->rt_conf.flags &= ~(F_DOWN);
720 
721 		if ((rt->rt_gwtable->conf.flags & F_CHANGED))
722 			sync_routes(env, rt);
723 	}
724 
725 	TAILQ_FOREACH(table, env->sc_tables, entry) {
726 		if (table->conf.check == CHECK_NOCHECK)
727 			continue;
728 
729 		/*
730 		 * clean up change flag.
731 		 */
732 		table->conf.flags &= ~(F_CHANGED);
733 
734 		/*
735 		 * handle demotion.
736 		 */
737 		if ((table->conf.flags & F_DEMOTE) == 0)
738 			continue;
739 		demote.level = 0;
740 		if (table->up && table->conf.flags & F_DEMOTED) {
741 			demote.level = -1;
742 			table->conf.flags &= ~F_DEMOTED;
743 		}
744 		else if (!table->up && !(table->conf.flags & F_DEMOTED)) {
745 			demote.level = 1;
746 			table->conf.flags |= F_DEMOTED;
747 		}
748 		if (demote.level == 0)
749 			continue;
750 		log_debug("%s: demote %d table '%s' group '%s'", __func__,
751 		    demote.level, table->conf.name, table->conf.demote_group);
752 		(void)strlcpy(demote.group, table->conf.demote_group,
753 		    sizeof(demote.group));
754 		proc_compose(env->sc_ps, PROC_PARENT, IMSG_DEMOTE,
755 		    &demote, sizeof(demote));
756 	}
757 }
758 
759 void
760 pfe_statistics(int fd, short events, void *arg)
761 {
762 	struct rdr		*rdr;
763 	struct ctl_stats	*cur;
764 	struct timeval		 tv, tv_now;
765 	int			 resethour, resetday;
766 	u_long			 cnt;
767 
768 	timerclear(&tv);
769 	getmonotime(&tv_now);
770 
771 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
772 		cnt = check_table(env, rdr, rdr->table);
773 		if (rdr->conf.backup_id != EMPTY_TABLE)
774 			cnt += check_table(env, rdr, rdr->backup);
775 
776 		resethour = resetday = 0;
777 
778 		cur = &rdr->stats;
779 		cur->last = cnt > cur->cnt ? cnt - cur->cnt : 0;
780 
781 		cur->cnt = cnt;
782 		cur->tick++;
783 		cur->avg = (cur->last + cur->avg) / 2;
784 		cur->last_hour += cur->last;
785 		if ((cur->tick % (3600 / env->sc_statinterval.tv_sec)) == 0) {
786 			cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2;
787 			resethour++;
788 		}
789 		cur->last_day += cur->last;
790 		if ((cur->tick % (86400 / env->sc_statinterval.tv_sec)) == 0) {
791 			cur->avg_day = (cur->last_day + cur->avg_day) / 2;
792 			resethour++;
793 		}
794 		if (resethour)
795 			cur->last_hour = 0;
796 		if (resetday)
797 			cur->last_day = 0;
798 
799 		rdr->stats.interval = env->sc_statinterval.tv_sec;
800 	}
801 
802 	/* Schedule statistics timer */
803 	evtimer_set(&env->sc_statev, pfe_statistics, NULL);
804 	bcopy(&env->sc_statinterval, &tv, sizeof(tv));
805 	evtimer_add(&env->sc_statev, &tv);
806 }
807