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