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