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