1 /* $OpenBSD: pfe.c,v 1.92 2026/03/02 19:28:01 rsadowski Exp $ */
4 * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
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.
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.
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
24 #include <sys/ioctl.h>
26 #include <net/pfvar.h>
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);
43 void pfe_statistics(int, short, void *);
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 *);
49 static struct relayd *env = NULL;
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 }
58 pfe(struct privsep *ps, struct privsep_proc *p)
61 struct pf_status status;
65 if ((s = open(PF_SOCKET, O_RDWR)) == -1) {
66 fatal("%s: cannot open pf socket", __func__);
68 if (env->sc_pf == NULL) {
69 if ((env->sc_pf = calloc(1, sizeof(*(env->sc_pf)))) == NULL)
73 if (ioctl(env->sc_pf->dev, DIOCGETSTATUS, &status) == -1)
74 fatal("%s: DIOCGETSTATUS", __func__);
76 fatalx("%s: pf is disabled", __func__);
77 log_debug("%s: filter init done", __func__);
79 proc_run(ps, p, procs, nitems(procs), pfe_init, NULL);
83 pfe_init(struct privsep *ps, struct privsep_proc *p, void *arg)
85 if (config_init(ps->ps_env) == -1)
86 fatal("failed to initialize configuration");
88 if (pledge("stdio recvfd unix pf", NULL) == -1)
91 p->p_shutdown = pfe_shutdown;
98 config_purge(env, CONFIG_ALL);
102 pfe_setup_events(void)
106 /* Schedule statistics timer */
107 if (!event_initialized(&env->sc_statev)) {
108 evtimer_set(&env->sc_statev, pfe_statistics, NULL);
109 bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv));
110 evtimer_add(&env->sc_statev, &tv);
115 pfe_disable_events(void)
117 event_del(&env->sc_statev);
121 pfe_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg)
125 struct ctl_status st;
127 control_imsg_forward(p->p_ps, imsg);
129 switch (imsg->hdr.type) {
130 case IMSG_HOST_STATUS:
131 IMSG_SIZE_CHECK(imsg, &st);
132 memcpy(&st, imsg->data, sizeof(st));
133 if ((host = host_find(env, st.id)) == NULL)
134 fatalx("%s: invalid host id", __func__);
136 if (host->flags & F_DISABLE)
138 host->retry_cnt = st.retry_cnt;
139 if (st.up != HOST_UNKNOWN) {
141 if (st.up == HOST_UP)
144 if (host->check_cnt != st.check_cnt) {
145 log_debug("%s: host %d => %d", __func__,
146 host->conf.id, host->up);
147 fatalx("%s: desynchronized", __func__);
150 if (host->up == st.up)
153 /* Forward to relay engine(s) */
154 proc_compose(env->sc_ps, PROC_RELAY,
155 IMSG_HOST_STATUS, &st, sizeof(st));
157 if ((table = table_find(env, host->conf.tableid))
159 fatalx("%s: invalid table id", __func__);
161 log_debug("%s: state %d for host %u %s", __func__,
162 st.up, host->conf.id, host->conf.name);
164 /* XXX Readd hosttrap code later */
166 snmp_hosttrap(env, table, host);
170 * Do not change the table state when the host
171 * state switches between UNKNOWN and DOWN.
173 if (HOST_ISUP(st.up)) {
174 table->conf.flags |= F_CHANGED;
176 host->flags |= F_ADD;
177 host->flags &= ~(F_DEL);
178 } else if (HOST_ISUP(host->up)) {
180 table->conf.flags |= F_CHANGED;
181 host->flags |= F_DEL;
182 host->flags &= ~(F_ADD);
200 pfe_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
202 switch (imsg->hdr.type) {
204 config_gettable(env, imsg);
207 config_gethost(env, imsg);
210 config_getrdr(env, imsg);
213 config_getvirt(env, imsg);
215 case IMSG_CFG_ROUTER:
216 config_getrt(env, imsg);
219 config_getroute(env, imsg);
222 config_getproto(env, imsg);
225 config_getrelay(env, imsg);
227 case IMSG_CFG_RELAY_TABLE:
228 config_getrelaytable(env, imsg);
231 config_getcfg(env, imsg);
240 config_getreset(env, imsg);
242 case IMSG_AGENTXSOCK:
243 agentx_getsock(imsg);
253 pfe_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
255 struct ctl_natlook cnl;
256 struct ctl_stats crs;
259 struct rsession con, *s, *t;
263 switch (imsg->hdr.type) {
265 IMSG_SIZE_CHECK(imsg, &cnl);
266 bcopy(imsg->data, &cnl, sizeof(cnl));
267 if (cnl.proc > env->sc_conf.prefork_relay)
268 fatalx("%s: invalid relay proc", __func__);
269 if (natlook(env, &cnl) != 0)
271 proc_compose_imsg(env->sc_ps, PROC_RELAY, cnl.proc,
272 IMSG_NATLOOK, -1, -1, &cnl, sizeof(cnl));
274 case IMSG_STATISTICS:
275 IMSG_SIZE_CHECK(imsg, &crs);
276 bcopy(imsg->data, &crs, sizeof(crs));
277 if (crs.proc > env->sc_conf.prefork_relay)
278 fatalx("%s: invalid relay proc", __func__);
279 if ((rlay = relay_find(env, crs.id)) == NULL)
280 fatalx("%s: invalid relay id", __func__);
281 bcopy(&crs, &rlay->rl_stats[crs.proc], sizeof(crs));
282 rlay->rl_stats[crs.proc].interval =
283 env->sc_conf.statinterval.tv_sec;
285 case IMSG_CTL_SESSION:
286 IMSG_SIZE_CHECK(imsg, &con);
287 memcpy(&con, imsg->data, sizeof(con));
288 if ((c = control_connbyfd(con.se_cid)) == NULL) {
289 log_debug("%s: control connection %d not found",
290 __func__, con.se_cid);
293 imsg_compose_event(&c->iev,
294 IMSG_CTL_SESSION, 0, 0, -1,
298 IMSG_SIZE_CHECK(imsg, &cid);
299 memcpy(&cid, imsg->data, sizeof(cid));
300 if ((c = control_connbyfd(cid)) == NULL) {
301 log_debug("%s: control connection %d not found",
305 if (c->waiting == 0) {
306 log_debug("%s: no pending control requests", __func__);
308 } else if (--c->waiting == 0) {
309 /* Last ack for a previous request */
310 imsg_compose_event(&c->iev, IMSG_CTL_END,
314 case IMSG_SESS_PUBLISH:
315 IMSG_SIZE_CHECK(imsg, s);
316 if ((s = calloc(1, sizeof(*s))) == NULL)
317 return (0); /* XXX */
318 memcpy(s, imsg->data, sizeof(*s));
319 TAILQ_FOREACH(t, &env->sc_sessions, se_entry) {
320 /* duplicate registration */
321 if (t->se_id == s->se_id) {
325 if (t->se_id > s->se_id)
329 TAILQ_INSERT_BEFORE(t, s, se_entry);
331 TAILQ_INSERT_TAIL(&env->sc_sessions, s, se_entry);
333 case IMSG_SESS_UNPUBLISH:
334 IMSG_SIZE_CHECK(imsg, &sid);
335 memcpy(&sid, imsg->data, sizeof(sid));
336 TAILQ_FOREACH(s, &env->sc_sessions, se_entry)
340 TAILQ_REMOVE(&env->sc_sessions, s, se_entry);
343 DPRINTF("removal of unpublished session %i", sid);
354 show(struct ctl_conn *c)
361 struct relay_table *rlt;
363 if (env->sc_rdrs == NULL)
365 TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
366 imsg_compose_event(&c->iev, IMSG_CTL_RDR, 0, 0, -1,
368 if (rdr->conf.flags & F_DISABLE)
371 imsg_compose_event(&c->iev, IMSG_CTL_RDR_STATS, 0, 0, -1,
372 &rdr->stats, sizeof(rdr->stats));
374 imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
375 rdr->table, sizeof(*rdr->table));
376 if (!(rdr->table->conf.flags & F_DISABLE))
377 TAILQ_FOREACH(host, &rdr->table->hosts, entry)
378 imsg_compose_event(&c->iev, IMSG_CTL_HOST,
379 0, 0, -1, host, sizeof(*host));
381 if (rdr->backup->conf.id == EMPTY_TABLE)
383 imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
384 rdr->backup, sizeof(*rdr->backup));
385 if (!(rdr->backup->conf.flags & F_DISABLE))
386 TAILQ_FOREACH(host, &rdr->backup->hosts, entry)
387 imsg_compose_event(&c->iev, IMSG_CTL_HOST,
388 0, 0, -1, host, sizeof(*host));
391 if (env->sc_relays == NULL)
393 TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
394 rlay->rl_stats[env->sc_conf.prefork_relay].id = EMPTY_ID;
395 imsg_compose_event(&c->iev, IMSG_CTL_RELAY, 0, 0, -1,
396 rlay, sizeof(*rlay));
397 imsg_compose_event(&c->iev, IMSG_CTL_RELAY_STATS, 0, 0, -1,
398 &rlay->rl_stats, sizeof(rlay->rl_stats));
400 TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) {
401 imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
402 rlt->rlt_table, sizeof(*rlt->rlt_table));
403 if (!(rlt->rlt_table->conf.flags & F_DISABLE))
405 &rlt->rlt_table->hosts, entry)
406 imsg_compose_event(&c->iev,
407 IMSG_CTL_HOST, 0, 0, -1,
408 host, sizeof(*host));
413 if (env->sc_rts == NULL)
415 TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
416 imsg_compose_event(&c->iev, IMSG_CTL_ROUTER, 0, 0, -1,
418 if (rt->rt_conf.flags & F_DISABLE)
421 TAILQ_FOREACH(nr, &rt->rt_netroutes, nr_entry)
422 imsg_compose_event(&c->iev, IMSG_CTL_NETROUTE,
423 0, 0, -1, nr, sizeof(*nr));
424 imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1,
425 rt->rt_gwtable, sizeof(*rt->rt_gwtable));
426 if (!(rt->rt_gwtable->conf.flags & F_DISABLE))
427 TAILQ_FOREACH(host, &rt->rt_gwtable->hosts, entry)
428 imsg_compose_event(&c->iev, IMSG_CTL_HOST,
429 0, 0, -1, host, sizeof(*host));
433 imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0);
437 show_sessions(struct ctl_conn *c)
441 for (proc = 0; proc < env->sc_conf.prefork_relay; proc++) {
442 cid = c->iev.ibuf.fd;
445 * Request all the running sessions from the process
447 proc_compose_imsg(env->sc_ps, PROC_RELAY, proc,
448 IMSG_CTL_SESSION, -1, -1, &cid, sizeof(cid));
454 disable_rdr(struct ctl_conn *c, struct ctl_id *id)
458 if (id->id == EMPTY_ID)
459 rdr = rdr_findbyname(env, id->name);
461 rdr = rdr_find(env, id->id);
464 id->id = rdr->conf.id;
466 if (rdr->conf.flags & F_DISABLE)
469 rdr->conf.flags |= F_DISABLE;
470 rdr->conf.flags &= ~(F_ADD);
471 rdr->conf.flags |= F_DEL;
472 rdr->table->conf.flags |= F_DISABLE;
473 log_debug("%s: redirect %d", __func__, rdr->conf.id);
479 enable_rdr(struct ctl_conn *c, struct ctl_id *id)
484 if (id->id == EMPTY_ID)
485 rdr = rdr_findbyname(env, id->name);
487 rdr = rdr_find(env, id->id);
490 id->id = rdr->conf.id;
492 if (!(rdr->conf.flags & F_DISABLE))
495 rdr->conf.flags &= ~(F_DISABLE);
496 rdr->conf.flags &= ~(F_DEL);
497 rdr->conf.flags |= F_ADD;
498 log_debug("%s: redirect %d", __func__, rdr->conf.id);
500 bzero(&eid, sizeof(eid));
502 /* XXX: we're syncing twice */
503 eid.id = rdr->table->conf.id;
504 if (enable_table(c, &eid) == -1)
506 if (rdr->backup->conf.id == EMPTY_ID)
508 eid.id = rdr->backup->conf.id;
509 if (enable_table(c, &eid) == -1)
515 disable_table(struct ctl_conn *c, struct ctl_id *id)
520 if (id->id == EMPTY_ID)
521 table = table_findbyname(env, id->name);
523 table = table_find(env, id->id);
526 id->id = table->conf.id;
527 if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
528 fatalx("%s: desynchronised", __func__);
530 if (table->conf.flags & F_DISABLE)
532 table->conf.flags |= (F_DISABLE|F_CHANGED);
534 TAILQ_FOREACH(host, &table->hosts, entry)
535 host->up = HOST_UNKNOWN;
536 proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_DISABLE,
537 &table->conf.id, sizeof(table->conf.id));
539 /* Forward to relay engine(s) */
540 proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_DISABLE,
541 &table->conf.id, sizeof(table->conf.id));
543 log_debug("%s: table %d", __func__, table->conf.id);
549 enable_table(struct ctl_conn *c, struct ctl_id *id)
554 if (id->id == EMPTY_ID)
555 table = table_findbyname(env, id->name);
557 table = table_find(env, id->id);
560 id->id = table->conf.id;
562 if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL)
563 fatalx("%s: desynchronised", __func__);
565 if (!(table->conf.flags & F_DISABLE))
567 table->conf.flags &= ~(F_DISABLE);
568 table->conf.flags |= F_CHANGED;
570 TAILQ_FOREACH(host, &table->hosts, entry)
571 host->up = HOST_UNKNOWN;
572 proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_ENABLE,
573 &table->conf.id, sizeof(table->conf.id));
575 /* Forward to relay engine(s) */
576 proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_ENABLE,
577 &table->conf.id, sizeof(table->conf.id));
579 log_debug("%s: table %d", __func__, table->conf.id);
585 disable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
588 struct table *table, *t;
592 if (id->id == EMPTY_ID) {
593 host = host_findbyname(env, id->name);
597 host = host_find(env, id->id);
598 if (host == NULL || host->conf.parentid)
601 id->id = host->conf.id;
603 if (host->flags & F_DISABLE)
606 if (host->up == HOST_UP) {
607 if ((table = table_find(env, host->conf.tableid)) == NULL)
608 fatalx("%s: invalid table id", __func__);
610 table->conf.flags |= F_CHANGED;
613 host->up = HOST_UNKNOWN;
614 host->flags |= F_DISABLE;
615 host->flags |= F_DEL;
616 host->flags &= ~(F_ADD);
620 proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_DISABLE,
621 &host->conf.id, sizeof(host->conf.id));
623 /* Forward to relay engine(s) */
624 proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_DISABLE,
625 &host->conf.id, sizeof(host->conf.id));
626 log_debug("%s: host %d", __func__, host->conf.id);
628 if (!host->conf.parentid) {
629 /* Disable all children */
630 SLIST_FOREACH(h, &host->children, child)
631 disable_host(c, id, h);
633 /* Disable hosts with same name on all tables */
635 TAILQ_FOREACH(t, env->sc_tables, entry)
636 TAILQ_FOREACH(h, &t->hosts, entry)
637 if (strcmp(h->conf.name,
638 host->conf.name) == 0 &&
639 h->conf.id != host->conf.id &&
641 disable_host(c, id, h);
648 enable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host)
656 if (id->id == EMPTY_ID) {
657 host = host_findbyname(env, id->name);
661 host = host_find(env, id->id);
662 if (host == NULL || host->conf.parentid)
665 id->id = host->conf.id;
667 if (!(host->flags & F_DISABLE))
670 host->up = HOST_UNKNOWN;
671 host->flags &= ~(F_DISABLE);
672 host->flags &= ~(F_DEL);
673 host->flags &= ~(F_ADD);
675 proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_ENABLE,
676 &host->conf.id, sizeof (host->conf.id));
678 /* Forward to relay engine(s) */
679 proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_ENABLE,
680 &host->conf.id, sizeof(host->conf.id));
682 log_debug("%s: host %d", __func__, host->conf.id);
684 if (!host->conf.parentid) {
685 /* Enable all children */
686 SLIST_FOREACH(h, &host->children, child)
687 enable_host(c, id, h);
689 /* Enable hosts with same name on all tables */
691 TAILQ_FOREACH(t, env->sc_tables, entry)
692 TAILQ_FOREACH(h, &t->hosts, entry)
693 if (strcmp(h->conf.name,
694 host->conf.name) == 0 &&
695 h->conf.id != host->conf.id &&
697 enable_host(c, id, h);
707 struct table *active;
711 struct ctl_demote demote;
714 bzero(&id, sizeof(id));
715 bzero(&imsg, sizeof(imsg));
716 TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
717 rdr->conf.flags &= ~(F_BACKUP);
718 rdr->conf.flags &= ~(F_DOWN);
720 if (rdr->conf.flags & F_DISABLE ||
721 (rdr->table->up == 0 && rdr->backup->up == 0)) {
722 rdr->conf.flags |= F_DOWN;
724 } else if (rdr->table->up == 0 && rdr->backup->up > 0) {
725 rdr->conf.flags |= F_BACKUP;
726 active = rdr->backup;
727 active->conf.flags |=
728 rdr->table->conf.flags & F_CHANGED;
729 active->conf.flags |=
730 rdr->backup->conf.flags & F_CHANGED;
734 if (active != NULL && active->conf.flags & F_CHANGED) {
735 id.id = active->conf.id;
736 imsg.hdr.type = IMSG_CTL_TABLE_CHANGED;
737 imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
739 sync_table(env, rdr, active);
740 control_imsg_forward(env->sc_ps, &imsg);
743 if (rdr->conf.flags & F_DOWN) {
744 if (rdr->conf.flags & F_ACTIVE_RULESET) {
745 flush_table(env, rdr);
746 log_debug("%s: disabling ruleset", __func__);
747 rdr->conf.flags &= ~(F_ACTIVE_RULESET);
748 id.id = rdr->conf.id;
749 imsg.hdr.type = IMSG_CTL_PULL_RULESET;
750 imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
752 sync_ruleset(env, rdr, 0);
753 control_imsg_forward(env->sc_ps, &imsg);
755 } else if (!(rdr->conf.flags & F_ACTIVE_RULESET)) {
756 log_debug("%s: enabling ruleset", __func__);
757 rdr->conf.flags |= F_ACTIVE_RULESET;
758 id.id = rdr->conf.id;
759 imsg.hdr.type = IMSG_CTL_PUSH_RULESET;
760 imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE;
762 sync_ruleset(env, rdr, 1);
763 control_imsg_forward(env->sc_ps, &imsg);
767 TAILQ_FOREACH(rt, env->sc_rts, rt_entry) {
768 rt->rt_conf.flags &= ~(F_BACKUP);
769 rt->rt_conf.flags &= ~(F_DOWN);
771 if ((rt->rt_gwtable->conf.flags & F_CHANGED))
772 sync_routes(env, rt);
775 TAILQ_FOREACH(table, env->sc_tables, entry) {
776 if (table->conf.check == CHECK_NOCHECK)
780 * clean up change flag.
782 table->conf.flags &= ~(F_CHANGED);
787 if ((table->conf.flags & F_DEMOTE) == 0)
790 if (table->up && table->conf.flags & F_DEMOTED) {
792 table->conf.flags &= ~F_DEMOTED;
794 else if (!table->up && !(table->conf.flags & F_DEMOTED)) {
796 table->conf.flags |= F_DEMOTED;
798 if (demote.level == 0)
800 log_debug("%s: demote %d table '%s' group '%s'", __func__,
801 demote.level, table->conf.name, table->conf.demote_group);
802 (void)strlcpy(demote.group, table->conf.demote_group,
803 sizeof(demote.group));
804 proc_compose(env->sc_ps, PROC_PARENT, IMSG_DEMOTE,
805 &demote, sizeof(demote));
810 pfe_statistics(int fd, short events, void *arg)
813 struct ctl_stats *cur;
814 struct timeval tv, tv_now;
815 int resethour, resetday;
819 getmonotime(&tv_now);
821 TAILQ_FOREACH(rdr, env->sc_rdrs, entry) {
822 cnt = check_table(env, rdr, rdr->table);
823 if (rdr->conf.backup_id != EMPTY_TABLE)
824 cnt += check_table(env, rdr, rdr->backup);
826 resethour = resetday = 0;
829 cur->last = cnt > cur->cnt ? cnt - cur->cnt : 0;
833 cur->avg = (cur->last + cur->avg) / 2;
834 cur->last_hour += cur->last;
836 (3600 / env->sc_conf.statinterval.tv_sec)) == 0) {
837 cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2;
840 cur->last_day += cur->last;
842 (86400 / env->sc_conf.statinterval.tv_sec)) == 0) {
843 cur->avg_day = (cur->last_day + cur->avg_day) / 2;
851 rdr->stats.interval = env->sc_conf.statinterval.tv_sec;
854 /* Schedule statistics timer */
855 evtimer_set(&env->sc_statev, pfe_statistics, NULL);
856 bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv));
857 evtimer_add(&env->sc_statev, &tv);