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