xref: /openbsd-src/usr.sbin/relayd/pfe.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: pfe.c,v 1.70 2011/05/20 09:43:53 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/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_imsg: 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_imsg: 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_imsg: 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 		pfe_setup_events();
207 		pfe_sync();
208 		break;
209 	case IMSG_CTL_RESET:
210 		config_getreset(env, imsg);
211 		break;
212 	default:
213 		return (-1);
214 	}
215 
216 	return (0);
217 }
218 
219 int
220 pfe_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
221 {
222 	struct ctl_natlook	 cnl;
223 	struct ctl_stats	 crs;
224 	struct relay		*rlay;
225 	struct ctl_conn		*c;
226 	struct rsession		 con;
227 	int			 cid;
228 
229 	switch (imsg->hdr.type) {
230 	case IMSG_NATLOOK:
231 		IMSG_SIZE_CHECK(imsg, &cnl);
232 		bcopy(imsg->data, &cnl, sizeof(cnl));
233 		if (cnl.proc > env->sc_prefork_relay)
234 			fatalx("pfe_dispatch_relay: "
235 			    "invalid relay proc");
236 		if (natlook(env, &cnl) != 0)
237 			cnl.in = -1;
238 		proc_compose_imsg(env->sc_ps, PROC_RELAY, cnl.proc,
239 		    IMSG_NATLOOK, -1, &cnl, sizeof(cnl));
240 		break;
241 	case IMSG_STATISTICS:
242 		IMSG_SIZE_CHECK(imsg, &crs);
243 		bcopy(imsg->data, &crs, sizeof(crs));
244 		if (crs.proc > env->sc_prefork_relay)
245 			fatalx("pfe_dispatch_relay: "
246 			    "invalid relay proc");
247 		if ((rlay = relay_find(env, crs.id)) == NULL)
248 			fatalx("pfe_dispatch_relay: invalid relay id");
249 		bcopy(&crs, &rlay->rl_stats[crs.proc], sizeof(crs));
250 		rlay->rl_stats[crs.proc].interval =
251 		    env->sc_statinterval.tv_sec;
252 		break;
253 	case IMSG_CTL_SESSION:
254 		IMSG_SIZE_CHECK(imsg, &con);
255 		memcpy(&con, imsg->data, sizeof(con));
256 		if ((c = control_connbyfd(con.se_cid)) == NULL) {
257 			log_debug("%s: control connection %d not found",
258 			    __func__, con.se_cid);
259 			return (0);
260 		}
261 		imsg_compose_event(&c->iev,
262 		    IMSG_CTL_SESSION, 0, 0, -1,
263 		    &con, sizeof(con));
264 		break;
265 	case IMSG_CTL_END:
266 		IMSG_SIZE_CHECK(imsg, &cid);
267 		memcpy(&cid, imsg->data, sizeof(cid));
268 		if ((c = control_connbyfd(cid)) == NULL) {
269 			log_debug("%s: control connection %d not found",
270 			    __func__, cid);
271 			return (0);
272 		}
273 		if (c->waiting == 0) {
274 			log_debug("%s: no pending control requests", __func__);
275 			return (0);
276 		} else if (--c->waiting == 0) {
277 			/* Last ack for a previous request */
278 			imsg_compose_event(&c->iev, IMSG_CTL_END,
279 			    0, 0, -1, NULL, 0);
280 		}
281 		break;
282 	default:
283 		return (-1);
284 	}
285 
286 	return (0);
287 }
288 
289 void
290 show(struct ctl_conn *c)
291 {
292 	struct rdr	*rdr;
293 	struct host	*host;
294 	struct relay	*rlay;
295 	struct router	*rt;
296 	struct netroute	*nr;
297 
298 	if (env->sc_rdrs == NULL)
299 		goto relays;
300 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
301 		imsg_compose_event(&c->iev, IMSG_CTL_RDR, 0, 0, -1,
302 		    rdr, sizeof(*rdr));
303 		if (rdr->conf.flags & F_DISABLE)
304 			continue;
305 
306 		imsg_compose_event(&c->iev, IMSG_CTL_RDR_STATS, 0, 0, -1,
307 		    &rdr->stats, sizeof(rdr->stats));
308 
309 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
310 		    rdr->table, sizeof(*rdr->table));
311 		if (!(rdr->table->conf.flags & F_DISABLE))
312 			TAILQ_FOREACH(host, &rdr->table->hosts, entry)
313 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
314 				    0, 0, -1, host, sizeof(*host));
315 
316 		if (rdr->backup->conf.id == EMPTY_TABLE)
317 			continue;
318 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
319 		    rdr->backup, sizeof(*rdr->backup));
320 		if (!(rdr->backup->conf.flags & F_DISABLE))
321 			TAILQ_FOREACH(host, &rdr->backup->hosts, entry)
322 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
323 				    0, 0, -1, host, sizeof(*host));
324 	}
325 relays:
326 	if (env->sc_relays == NULL)
327 		goto routers;
328 	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
329 		rlay->rl_stats[env->sc_prefork_relay].id = EMPTY_ID;
330 		imsg_compose_event(&c->iev, IMSG_CTL_RELAY, 0, 0, -1,
331 		    rlay, sizeof(*rlay));
332 		imsg_compose_event(&c->iev, IMSG_CTL_RELAY_STATS, 0, 0, -1,
333 		    &rlay->rl_stats, sizeof(rlay->rl_stats));
334 
335 		if (rlay->rl_dsttable == NULL)
336 			continue;
337 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
338 		    rlay->rl_dsttable, sizeof(*rlay->rl_dsttable));
339 		if (!(rlay->rl_dsttable->conf.flags & F_DISABLE))
340 			TAILQ_FOREACH(host, &rlay->rl_dsttable->hosts, entry)
341 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
342 				    0, 0, -1, host, sizeof(*host));
343 
344 		if (rlay->rl_conf.backuptable == EMPTY_TABLE)
345 			continue;
346 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
347 		    rlay->rl_backuptable, sizeof(*rlay->rl_backuptable));
348 		if (!(rlay->rl_backuptable->conf.flags & F_DISABLE))
349 			TAILQ_FOREACH(host, &rlay->rl_backuptable->hosts, entry)
350 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
351 				    0, 0, -1, host, sizeof(*host));
352 	}
353 
354 routers:
355 	if (env->sc_rts == NULL)
356 		goto end;
357 	TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
358 		imsg_compose_event(&c->iev, IMSG_CTL_ROUTER, 0, 0, -1,
359 		    rt, sizeof(*rt));
360 		if (rt->rt_conf.flags & F_DISABLE)
361 			continue;
362 
363 		TAILQ_FOREACH(nr, &rt->rt_netroutes, nr_entry)
364 			imsg_compose_event(&c->iev, IMSG_CTL_NETROUTE,
365 			    0, 0, -1, nr, sizeof(*nr));
366 		imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
367 		    rt->rt_gwtable, sizeof(*rt->rt_gwtable));
368 		if (!(rt->rt_gwtable->conf.flags & F_DISABLE))
369 			TAILQ_FOREACH(host, &rt->rt_gwtable->hosts, entry)
370 				imsg_compose_event(&c->iev, IMSG_CTL_HOST,
371 				    0, 0, -1, host, sizeof(*host));
372 	}
373 
374 end:
375 	imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0);
376 }
377 
378 void
379 show_sessions(struct ctl_conn *c)
380 {
381 	int			 proc, cid;
382 
383 	for (proc = 0; proc < env->sc_prefork_relay; proc++) {
384 		cid = c->iev.ibuf.fd;
385 
386 		/*
387 		 * Request all the running sessions from the process
388 		 */
389 		proc_compose_imsg(env->sc_ps, PROC_RELAY, proc,
390 		    IMSG_CTL_SESSION, -1, &cid, sizeof(cid));
391 		c->waiting++;
392 	}
393 }
394 
395 int
396 disable_rdr(struct ctl_conn *c, struct ctl_id *id)
397 {
398 	struct rdr	*rdr;
399 
400 	if (id->id == EMPTY_ID)
401 		rdr = rdr_findbyname(env, id->name);
402 	else
403 		rdr = rdr_find(env, id->id);
404 	if (rdr == NULL)
405 		return (-1);
406 	id->id = rdr->conf.id;
407 
408 	if (rdr->conf.flags & F_DISABLE)
409 		return (0);
410 
411 	rdr->conf.flags |= F_DISABLE;
412 	rdr->conf.flags &= ~(F_ADD);
413 	rdr->conf.flags |= F_DEL;
414 	rdr->table->conf.flags |= F_DISABLE;
415 	log_debug("%s: redirect %d", __func__, rdr->conf.id);
416 	pfe_sync();
417 	return (0);
418 }
419 
420 int
421 enable_rdr(struct ctl_conn *c, struct ctl_id *id)
422 {
423 	struct rdr	*rdr;
424 	struct ctl_id	 eid;
425 
426 	if (id->id == EMPTY_ID)
427 		rdr = rdr_findbyname(env, id->name);
428 	else
429 		rdr = rdr_find(env, id->id);
430 	if (rdr == NULL)
431 		return (-1);
432 	id->id = rdr->conf.id;
433 
434 	if (!(rdr->conf.flags & F_DISABLE))
435 		return (0);
436 
437 	rdr->conf.flags &= ~(F_DISABLE);
438 	rdr->conf.flags &= ~(F_DEL);
439 	rdr->conf.flags |= F_ADD;
440 	log_debug("%s: redirect %d", __func__, rdr->conf.id);
441 
442 	bzero(&eid, sizeof(eid));
443 
444 	/* XXX: we're syncing twice */
445 	eid.id = rdr->table->conf.id;
446 	if (enable_table(c, &eid) == -1)
447 		return (-1);
448 	if (rdr->backup->conf.id == EMPTY_ID)
449 		return (0);
450 	eid.id = rdr->backup->conf.id;
451 	if (enable_table(c, &eid) == -1)
452 		return (-1);
453 	return (0);
454 }
455 
456 int
457 disable_table(struct ctl_conn *c, struct ctl_id *id)
458 {
459 	struct table	*table;
460 	struct host	*host;
461 
462 	if (id->id == EMPTY_ID)
463 		table = table_findbyname(env, id->name);
464 	else
465 		table = table_find(env, id->id);
466 	if (table == NULL)
467 		return (-1);
468 	id->id = table->conf.id;
469 	if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
470 		fatalx("disable_table: desynchronised");
471 
472 	if (table->conf.flags & F_DISABLE)
473 		return (0);
474 	table->conf.flags |= (F_DISABLE|F_CHANGED);
475 	table->up = 0;
476 	TAILQ_FOREACH(host, &table->hosts, entry)
477 		host->up = HOST_UNKNOWN;
478 	proc_compose_imsg(env->sc_ps, PROC_HCE, -1, IMSG_TABLE_DISABLE, -1,
479 	    &table->conf.id, sizeof(table->conf.id));
480 
481 	/* Forward to relay engine(s) */
482 	proc_compose_imsg(env->sc_ps, PROC_RELAY, -1, IMSG_TABLE_DISABLE, -1,
483 	    &table->conf.id, sizeof(table->conf.id));
484 
485 	log_debug("%s: table %d", __func__, table->conf.id);
486 	pfe_sync();
487 	return (0);
488 }
489 
490 int
491 enable_table(struct ctl_conn *c, struct ctl_id *id)
492 {
493 	struct table	*table;
494 	struct host	*host;
495 
496 	if (id->id == EMPTY_ID)
497 		table = table_findbyname(env, id->name);
498 	else
499 		table = table_find(env, id->id);
500 	if (table == NULL)
501 		return (-1);
502 	id->id = table->conf.id;
503 
504 	if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
505 		fatalx("enable_table: desynchronised");
506 
507 	if (!(table->conf.flags & F_DISABLE))
508 		return (0);
509 	table->conf.flags &= ~(F_DISABLE);
510 	table->conf.flags |= F_CHANGED;
511 	table->up = 0;
512 	TAILQ_FOREACH(host, &table->hosts, entry)
513 		host->up = HOST_UNKNOWN;
514 	proc_compose_imsg(env->sc_ps, PROC_HCE, -1, IMSG_TABLE_ENABLE, -1,
515 	    &table->conf.id, sizeof(table->conf.id));
516 
517 	/* Forward to relay engine(s) */
518 	proc_compose_imsg(env->sc_ps, PROC_RELAY, -1, IMSG_TABLE_ENABLE, -1,
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 disable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
528 {
529 	struct host	*h;
530 	struct table	*table;
531 
532 	if (host == NULL) {
533 		if (id->id == EMPTY_ID)
534 			host = host_findbyname(env, id->name);
535 		else
536 			host = host_find(env, id->id);
537 		if (host == NULL || host->conf.parentid)
538 			return (-1);
539 	}
540 	id->id = host->conf.id;
541 
542 	if (host->flags & F_DISABLE)
543 		return (0);
544 
545 	if (host->up == HOST_UP) {
546 		if ((table = table_find(env, host->conf.tableid)) == NULL)
547 			fatalx("disable_host: invalid table id");
548 		table->up--;
549 		table->conf.flags |= F_CHANGED;
550 	}
551 
552 	host->up = HOST_UNKNOWN;
553 	host->flags |= F_DISABLE;
554 	host->flags |= F_DEL;
555 	host->flags &= ~(F_ADD);
556 	host->check_cnt = 0;
557 	host->up_cnt = 0;
558 
559 	proc_compose_imsg(env->sc_ps, PROC_HCE, -1, IMSG_HOST_DISABLE, -1,
560 	    &host->conf.id, sizeof(host->conf.id));
561 
562 	/* Forward to relay engine(s) */
563 	proc_compose_imsg(env->sc_ps, PROC_RELAY, -1, IMSG_HOST_DISABLE, -1,
564 	    &host->conf.id, sizeof(host->conf.id));
565 	log_debug("%s: host %d", __func__, host->conf.id);
566 
567 	if (!host->conf.parentid) {
568 		/* Disable all children */
569 		SLIST_FOREACH(h, &host->children, child)
570 			disable_host(c, id, h);
571 		pfe_sync();
572 	}
573 	return (0);
574 }
575 
576 int
577 enable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
578 {
579 	struct host	*h;
580 
581 	if (host == NULL) {
582 		if (id->id == EMPTY_ID)
583 			host = host_findbyname(env, id->name);
584 		else
585 			host = host_find(env, id->id);
586 		if (host == NULL || host->conf.parentid)
587 			return (-1);
588 	}
589 	id->id = host->conf.id;
590 
591 	if (!(host->flags & F_DISABLE))
592 		return (0);
593 
594 	host->up = HOST_UNKNOWN;
595 	host->flags &= ~(F_DISABLE);
596 	host->flags &= ~(F_DEL);
597 	host->flags &= ~(F_ADD);
598 
599 	proc_compose_imsg(env->sc_ps, PROC_HCE, -1, IMSG_HOST_ENABLE, -1,
600 	    &host->conf.id, sizeof (host->conf.id));
601 
602 	/* Forward to relay engine(s) */
603 	proc_compose_imsg(env->sc_ps, PROC_RELAY, -1, IMSG_HOST_ENABLE, -1,
604 	    &host->conf.id, sizeof(host->conf.id));
605 
606 	log_debug("%s: host %d", __func__, host->conf.id);
607 
608 	if (!host->conf.parentid) {
609 		/* Enable all children */
610 		SLIST_FOREACH(h, &host->children, child)
611 			enable_host(c, id, h);
612 		pfe_sync();
613 	}
614 	return (0);
615 }
616 
617 void
618 pfe_sync(void)
619 {
620 	struct rdr		*rdr;
621 	struct table		*active;
622 	struct table		*table;
623 	struct ctl_id		 id;
624 	struct imsg		 imsg;
625 	struct ctl_demote	 demote;
626 	struct router		*rt;
627 
628 	bzero(&id, sizeof(id));
629 	bzero(&imsg, sizeof(imsg));
630 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
631 		rdr->conf.flags &= ~(F_BACKUP);
632 		rdr->conf.flags &= ~(F_DOWN);
633 
634 		if (rdr->conf.flags & F_DISABLE ||
635 		    (rdr->table->up == 0 && rdr->backup->up == 0)) {
636 			rdr->conf.flags |= F_DOWN;
637 			active = NULL;
638 		} else if (rdr->table->up == 0 && rdr->backup->up > 0) {
639 			rdr->conf.flags |= F_BACKUP;
640 			active = rdr->backup;
641 			active->conf.flags |=
642 			    rdr->table->conf.flags & F_CHANGED;
643 			active->conf.flags |=
644 			    rdr->backup->conf.flags & F_CHANGED;
645 		} else
646 			active = rdr->table;
647 
648 		if (active != NULL && active->conf.flags & F_CHANGED) {
649 			id.id = active->conf.id;
650 			imsg.hdr.type = IMSG_CTL_TABLE_CHANGED;
651 			imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
652 			imsg.data = &id;
653 			sync_table(env, rdr, active);
654 			control_imsg_forward(&imsg);
655 		}
656 
657 		if (rdr->conf.flags & F_DOWN) {
658 			if (rdr->conf.flags & F_ACTIVE_RULESET) {
659 				flush_table(env, rdr);
660 				log_debug("%s: disabling ruleset", __func__);
661 				rdr->conf.flags &= ~(F_ACTIVE_RULESET);
662 				id.id = rdr->conf.id;
663 				imsg.hdr.type = IMSG_CTL_PULL_RULESET;
664 				imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
665 				imsg.data = &id;
666 				sync_ruleset(env, rdr, 0);
667 				control_imsg_forward(&imsg);
668 			}
669 		} else if (!(rdr->conf.flags & F_ACTIVE_RULESET)) {
670 			log_debug("%s: enabling ruleset", __func__);
671 			rdr->conf.flags |= F_ACTIVE_RULESET;
672 			id.id = rdr->conf.id;
673 			imsg.hdr.type = IMSG_CTL_PUSH_RULESET;
674 			imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
675 			imsg.data = &id;
676 			sync_ruleset(env, rdr, 1);
677 			control_imsg_forward(&imsg);
678 		}
679 	}
680 
681 	TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
682 		rt->rt_conf.flags &= ~(F_BACKUP);
683 		rt->rt_conf.flags &= ~(F_DOWN);
684 
685 		if ((rt->rt_gwtable->conf.flags & F_CHANGED))
686 			sync_routes(env, rt);
687 	}
688 
689 	TAILQ_FOREACH(table, env->sc_tables, entry) {
690 		if (table->conf.check == CHECK_NOCHECK)
691 			continue;
692 
693 		/*
694 		 * clean up change flag.
695 		 */
696 		table->conf.flags &= ~(F_CHANGED);
697 
698 		/*
699 		 * handle demotion.
700 		 */
701 		if ((table->conf.flags & F_DEMOTE) == 0)
702 			continue;
703 		demote.level = 0;
704 		if (table->up && table->conf.flags & F_DEMOTED) {
705 			demote.level = -1;
706 			table->conf.flags &= ~F_DEMOTED;
707 		}
708 		else if (!table->up && !(table->conf.flags & F_DEMOTED)) {
709 			demote.level = 1;
710 			table->conf.flags |= F_DEMOTED;
711 		}
712 		if (demote.level == 0)
713 			continue;
714 		log_debug("%s: demote %d table '%s' group '%s'", __func__,
715 		    demote.level, table->conf.name, table->conf.demote_group);
716 		(void)strlcpy(demote.group, table->conf.demote_group,
717 		    sizeof(demote.group));
718 		proc_compose_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_DEMOTE, -1,
719 		    &demote, sizeof(demote));
720 	}
721 }
722 
723 void
724 pfe_statistics(int fd, short events, void *arg)
725 {
726 	struct rdr		*rdr;
727 	struct ctl_stats	*cur;
728 	struct timeval		 tv, tv_now;
729 	int			 resethour, resetday;
730 	u_long			 cnt;
731 
732 	timerclear(&tv);
733 	if (gettimeofday(&tv_now, NULL) == -1)
734 		fatal("pfe_statistics: gettimeofday");
735 
736 	TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
737 		cnt = check_table(env, rdr, rdr->table);
738 		if (rdr->conf.backup_id != EMPTY_TABLE)
739 			cnt += check_table(env, rdr, rdr->backup);
740 
741 		resethour = resetday = 0;
742 
743 		cur = &rdr->stats;
744 		cur->last = cnt > cur->cnt ? cnt - cur->cnt : 0;
745 
746 		cur->cnt = cnt;
747 		cur->tick++;
748 		cur->avg = (cur->last + cur->avg) / 2;
749 		cur->last_hour += cur->last;
750 		if ((cur->tick % (3600 / env->sc_statinterval.tv_sec)) == 0) {
751 			cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2;
752 			resethour++;
753 		}
754 		cur->last_day += cur->last;
755 		if ((cur->tick % (86400 / env->sc_statinterval.tv_sec)) == 0) {
756 			cur->avg_day = (cur->last_day + cur->avg_day) / 2;
757 			resethour++;
758 		}
759 		if (resethour)
760 			cur->last_hour = 0;
761 		if (resetday)
762 			cur->last_day = 0;
763 
764 		rdr->stats.interval = env->sc_statinterval.tv_sec;
765 	}
766 
767 	/* Schedule statistics timer */
768 	evtimer_set(&env->sc_statev, pfe_statistics, NULL);
769 	bcopy(&env->sc_statinterval, &tv, sizeof(tv));
770 	evtimer_add(&env->sc_statev, &tv);
771 }
772