/* $OpenBSD: pfe.c,v 1.90 2020/09/14 11:30:25 martijn Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "relayd.h" void pfe_init(struct privsep *, struct privsep_proc *p, void *); void pfe_shutdown(void); void pfe_setup_events(void); void pfe_disable_events(void); void pfe_sync(void); void pfe_statistics(int, short, void *); int pfe_dispatch_parent(int, struct privsep_proc *, struct imsg *); int pfe_dispatch_hce(int, struct privsep_proc *, struct imsg *); int pfe_dispatch_relay(int, struct privsep_proc *, struct imsg *); static struct relayd *env = NULL; static struct privsep_proc procs[] = { { "parent", PROC_PARENT, pfe_dispatch_parent }, { "relay", PROC_RELAY, pfe_dispatch_relay }, { "hce", PROC_HCE, pfe_dispatch_hce } }; void pfe(struct privsep *ps, struct privsep_proc *p) { int s; struct pf_status status; env = ps->ps_env; if ((s = open(PF_SOCKET, O_RDWR)) == -1) { fatal("%s: cannot open pf socket", __func__); } if (env->sc_pf == NULL) { if ((env->sc_pf = calloc(1, sizeof(*(env->sc_pf)))) == NULL) fatal("calloc"); env->sc_pf->dev = s; } if (ioctl(env->sc_pf->dev, DIOCGETSTATUS, &status) == -1) fatal("%s: DIOCGETSTATUS", __func__); if (!status.running) fatalx("%s: pf is disabled", __func__); log_debug("%s: filter init done", __func__); proc_run(ps, p, procs, nitems(procs), pfe_init, NULL); } void pfe_init(struct privsep *ps, struct privsep_proc *p, void *arg) { if (config_init(ps->ps_env) == -1) fatal("failed to initialize configuration"); if (pledge("stdio recvfd unix pf", NULL) == -1) fatal("pledge"); p->p_shutdown = pfe_shutdown; } void pfe_shutdown(void) { flush_rulesets(env); config_purge(env, CONFIG_ALL); } void pfe_setup_events(void) { struct timeval tv; /* Schedule statistics timer */ if (!event_initialized(&env->sc_statev)) { evtimer_set(&env->sc_statev, pfe_statistics, NULL); bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv)); evtimer_add(&env->sc_statev, &tv); } } void pfe_disable_events(void) { event_del(&env->sc_statev); } int pfe_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg) { struct host *host; struct table *table; struct ctl_status st; control_imsg_forward(p->p_ps, imsg); switch (imsg->hdr.type) { case IMSG_HOST_STATUS: IMSG_SIZE_CHECK(imsg, &st); memcpy(&st, imsg->data, sizeof(st)); if ((host = host_find(env, st.id)) == NULL) fatalx("%s: invalid host id", __func__); host->he = st.he; if (host->flags & F_DISABLE) break; host->retry_cnt = st.retry_cnt; if (st.up != HOST_UNKNOWN) { host->check_cnt++; if (st.up == HOST_UP) host->up_cnt++; } if (host->check_cnt != st.check_cnt) { log_debug("%s: host %d => %d", __func__, host->conf.id, host->up); fatalx("%s: desynchronized", __func__); } if (host->up == st.up) break; /* Forward to relay engine(s) */ proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_STATUS, &st, sizeof(st)); if ((table = table_find(env, host->conf.tableid)) == NULL) fatalx("%s: invalid table id", __func__); log_debug("%s: state %d for host %u %s", __func__, st.up, host->conf.id, host->conf.name); /* XXX Readd hosttrap code later */ #if 0 snmp_hosttrap(env, table, host); #endif /* * Do not change the table state when the host * state switches between UNKNOWN and DOWN. */ if (HOST_ISUP(st.up)) { table->conf.flags |= F_CHANGED; table->up++; host->flags |= F_ADD; host->flags &= ~(F_DEL); } else if (HOST_ISUP(host->up)) { table->up--; table->conf.flags |= F_CHANGED; host->flags |= F_DEL; host->flags &= ~(F_ADD); host->up = st.up; pfe_sync(); } host->up = st.up; break; case IMSG_SYNC: pfe_sync(); break; default: return (-1); } return (0); } int pfe_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) { switch (imsg->hdr.type) { case IMSG_CFG_TABLE: config_gettable(env, imsg); break; case IMSG_CFG_HOST: config_gethost(env, imsg); break; case IMSG_CFG_RDR: config_getrdr(env, imsg); break; case IMSG_CFG_VIRT: config_getvirt(env, imsg); break; case IMSG_CFG_ROUTER: config_getrt(env, imsg); break; case IMSG_CFG_ROUTE: config_getroute(env, imsg); break; case IMSG_CFG_PROTO: config_getproto(env, imsg); break; case IMSG_CFG_RELAY: config_getrelay(env, imsg); break; case IMSG_CFG_RELAY_TABLE: config_getrelaytable(env, imsg); break; case IMSG_CFG_DONE: config_getcfg(env, imsg); init_tables(env); agentx_init(env); break; case IMSG_CTL_START: pfe_setup_events(); pfe_sync(); break; case IMSG_CTL_RESET: config_getreset(env, imsg); break; case IMSG_AGENTXSOCK: agentx_getsock(imsg); break; default: return (-1); } return (0); } int pfe_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg) { struct ctl_natlook cnl; struct ctl_stats crs; struct relay *rlay; struct ctl_conn *c; struct rsession con, *s, *t; int cid; objid_t sid; switch (imsg->hdr.type) { case IMSG_NATLOOK: IMSG_SIZE_CHECK(imsg, &cnl); bcopy(imsg->data, &cnl, sizeof(cnl)); if (cnl.proc > env->sc_conf.prefork_relay) fatalx("%s: invalid relay proc", __func__); if (natlook(env, &cnl) != 0) cnl.in = -1; proc_compose_imsg(env->sc_ps, PROC_RELAY, cnl.proc, IMSG_NATLOOK, -1, -1, &cnl, sizeof(cnl)); break; case IMSG_STATISTICS: IMSG_SIZE_CHECK(imsg, &crs); bcopy(imsg->data, &crs, sizeof(crs)); if (crs.proc > env->sc_conf.prefork_relay) fatalx("%s: invalid relay proc", __func__); if ((rlay = relay_find(env, crs.id)) == NULL) fatalx("%s: invalid relay id", __func__); bcopy(&crs, &rlay->rl_stats[crs.proc], sizeof(crs)); rlay->rl_stats[crs.proc].interval = env->sc_conf.statinterval.tv_sec; break; case IMSG_CTL_SESSION: IMSG_SIZE_CHECK(imsg, &con); memcpy(&con, imsg->data, sizeof(con)); if ((c = control_connbyfd(con.se_cid)) == NULL) { log_debug("%s: control connection %d not found", __func__, con.se_cid); return (0); } imsg_compose_event(&c->iev, IMSG_CTL_SESSION, 0, 0, -1, &con, sizeof(con)); break; case IMSG_CTL_END: IMSG_SIZE_CHECK(imsg, &cid); memcpy(&cid, imsg->data, sizeof(cid)); if ((c = control_connbyfd(cid)) == NULL) { log_debug("%s: control connection %d not found", __func__, cid); return (0); } if (c->waiting == 0) { log_debug("%s: no pending control requests", __func__); return (0); } else if (--c->waiting == 0) { /* Last ack for a previous request */ imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } break; case IMSG_SESS_PUBLISH: IMSG_SIZE_CHECK(imsg, s); if ((s = calloc(1, sizeof(*s))) == NULL) return (0); /* XXX */ memcpy(s, imsg->data, sizeof(*s)); TAILQ_FOREACH(t, &env->sc_sessions, se_entry) { /* duplicate registration */ if (t->se_id == s->se_id) { free(s); return (0); } if (t->se_id > s->se_id) break; } if (t) TAILQ_INSERT_BEFORE(t, s, se_entry); else TAILQ_INSERT_TAIL(&env->sc_sessions, s, se_entry); break; case IMSG_SESS_UNPUBLISH: IMSG_SIZE_CHECK(imsg, &sid); memcpy(&sid, imsg->data, sizeof(sid)); TAILQ_FOREACH(s, &env->sc_sessions, se_entry) if (s->se_id == sid) break; if (s) { TAILQ_REMOVE(&env->sc_sessions, s, se_entry); free(s); } else { DPRINTF("removal of unpublished session %i", sid); } break; default: return (-1); } return (0); } void show(struct ctl_conn *c) { struct rdr *rdr; struct host *host; struct relay *rlay; struct router *rt; struct netroute *nr; struct relay_table *rlt; if (env->sc_rdrs == NULL) goto relays; TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { imsg_compose_event(&c->iev, IMSG_CTL_RDR, 0, 0, -1, rdr, sizeof(*rdr)); if (rdr->conf.flags & F_DISABLE) continue; imsg_compose_event(&c->iev, IMSG_CTL_RDR_STATS, 0, 0, -1, &rdr->stats, sizeof(rdr->stats)); imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, rdr->table, sizeof(*rdr->table)); if (!(rdr->table->conf.flags & F_DISABLE)) TAILQ_FOREACH(host, &rdr->table->hosts, entry) imsg_compose_event(&c->iev, IMSG_CTL_HOST, 0, 0, -1, host, sizeof(*host)); if (rdr->backup->conf.id == EMPTY_TABLE) continue; imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, rdr->backup, sizeof(*rdr->backup)); if (!(rdr->backup->conf.flags & F_DISABLE)) TAILQ_FOREACH(host, &rdr->backup->hosts, entry) imsg_compose_event(&c->iev, IMSG_CTL_HOST, 0, 0, -1, host, sizeof(*host)); } relays: if (env->sc_relays == NULL) goto routers; TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { rlay->rl_stats[env->sc_conf.prefork_relay].id = EMPTY_ID; imsg_compose_event(&c->iev, IMSG_CTL_RELAY, 0, 0, -1, rlay, sizeof(*rlay)); imsg_compose_event(&c->iev, IMSG_CTL_RELAY_STATS, 0, 0, -1, &rlay->rl_stats, sizeof(rlay->rl_stats)); TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) { imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, rlt->rlt_table, sizeof(*rlt->rlt_table)); if (!(rlt->rlt_table->conf.flags & F_DISABLE)) TAILQ_FOREACH(host, &rlt->rlt_table->hosts, entry) imsg_compose_event(&c->iev, IMSG_CTL_HOST, 0, 0, -1, host, sizeof(*host)); } } routers: if (env->sc_rts == NULL) goto end; TAILQ_FOREACH(rt, env->sc_rts, rt_entry) { imsg_compose_event(&c->iev, IMSG_CTL_ROUTER, 0, 0, -1, rt, sizeof(*rt)); if (rt->rt_conf.flags & F_DISABLE) continue; TAILQ_FOREACH(nr, &rt->rt_netroutes, nr_entry) imsg_compose_event(&c->iev, IMSG_CTL_NETROUTE, 0, 0, -1, nr, sizeof(*nr)); imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, rt->rt_gwtable, sizeof(*rt->rt_gwtable)); if (!(rt->rt_gwtable->conf.flags & F_DISABLE)) TAILQ_FOREACH(host, &rt->rt_gwtable->hosts, entry) imsg_compose_event(&c->iev, IMSG_CTL_HOST, 0, 0, -1, host, sizeof(*host)); } end: imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } void show_sessions(struct ctl_conn *c) { int proc, cid; for (proc = 0; proc < env->sc_conf.prefork_relay; proc++) { cid = c->iev.ibuf.fd; /* * Request all the running sessions from the process */ proc_compose_imsg(env->sc_ps, PROC_RELAY, proc, IMSG_CTL_SESSION, -1, -1, &cid, sizeof(cid)); c->waiting++; } } int disable_rdr(struct ctl_conn *c, struct ctl_id *id) { struct rdr *rdr; if (id->id == EMPTY_ID) rdr = rdr_findbyname(env, id->name); else rdr = rdr_find(env, id->id); if (rdr == NULL) return (-1); id->id = rdr->conf.id; if (rdr->conf.flags & F_DISABLE) return (0); rdr->conf.flags |= F_DISABLE; rdr->conf.flags &= ~(F_ADD); rdr->conf.flags |= F_DEL; rdr->table->conf.flags |= F_DISABLE; log_debug("%s: redirect %d", __func__, rdr->conf.id); pfe_sync(); return (0); } int enable_rdr(struct ctl_conn *c, struct ctl_id *id) { struct rdr *rdr; struct ctl_id eid; if (id->id == EMPTY_ID) rdr = rdr_findbyname(env, id->name); else rdr = rdr_find(env, id->id); if (rdr == NULL) return (-1); id->id = rdr->conf.id; if (!(rdr->conf.flags & F_DISABLE)) return (0); rdr->conf.flags &= ~(F_DISABLE); rdr->conf.flags &= ~(F_DEL); rdr->conf.flags |= F_ADD; log_debug("%s: redirect %d", __func__, rdr->conf.id); bzero(&eid, sizeof(eid)); /* XXX: we're syncing twice */ eid.id = rdr->table->conf.id; if (enable_table(c, &eid) == -1) return (-1); if (rdr->backup->conf.id == EMPTY_ID) return (0); eid.id = rdr->backup->conf.id; if (enable_table(c, &eid) == -1) return (-1); return (0); } int disable_table(struct ctl_conn *c, struct ctl_id *id) { struct table *table; struct host *host; if (id->id == EMPTY_ID) table = table_findbyname(env, id->name); else table = table_find(env, id->id); if (table == NULL) return (-1); id->id = table->conf.id; if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL) fatalx("%s: desynchronised", __func__); if (table->conf.flags & F_DISABLE) return (0); table->conf.flags |= (F_DISABLE|F_CHANGED); table->up = 0; TAILQ_FOREACH(host, &table->hosts, entry) host->up = HOST_UNKNOWN; proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_DISABLE, &table->conf.id, sizeof(table->conf.id)); /* Forward to relay engine(s) */ proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_DISABLE, &table->conf.id, sizeof(table->conf.id)); log_debug("%s: table %d", __func__, table->conf.id); pfe_sync(); return (0); } int enable_table(struct ctl_conn *c, struct ctl_id *id) { struct table *table; struct host *host; if (id->id == EMPTY_ID) table = table_findbyname(env, id->name); else table = table_find(env, id->id); if (table == NULL) return (-1); id->id = table->conf.id; if (table->conf.rdrid > 0 && rdr_find(env, table->conf.rdrid) == NULL) fatalx("%s: desynchronised", __func__); if (!(table->conf.flags & F_DISABLE)) return (0); table->conf.flags &= ~(F_DISABLE); table->conf.flags |= F_CHANGED; table->up = 0; TAILQ_FOREACH(host, &table->hosts, entry) host->up = HOST_UNKNOWN; proc_compose(env->sc_ps, PROC_HCE, IMSG_TABLE_ENABLE, &table->conf.id, sizeof(table->conf.id)); /* Forward to relay engine(s) */ proc_compose(env->sc_ps, PROC_RELAY, IMSG_TABLE_ENABLE, &table->conf.id, sizeof(table->conf.id)); log_debug("%s: table %d", __func__, table->conf.id); pfe_sync(); return (0); } int disable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host) { struct host *h; struct table *table; if (host == NULL) { if (id->id == EMPTY_ID) host = host_findbyname(env, id->name); else host = host_find(env, id->id); if (host == NULL || host->conf.parentid) return (-1); } id->id = host->conf.id; if (host->flags & F_DISABLE) return (0); if (host->up == HOST_UP) { if ((table = table_find(env, host->conf.tableid)) == NULL) fatalx("%s: invalid table id", __func__); table->up--; table->conf.flags |= F_CHANGED; } host->up = HOST_UNKNOWN; host->flags |= F_DISABLE; host->flags |= F_DEL; host->flags &= ~(F_ADD); host->check_cnt = 0; host->up_cnt = 0; proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_DISABLE, &host->conf.id, sizeof(host->conf.id)); /* Forward to relay engine(s) */ proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_DISABLE, &host->conf.id, sizeof(host->conf.id)); log_debug("%s: host %d", __func__, host->conf.id); if (!host->conf.parentid) { /* Disable all children */ SLIST_FOREACH(h, &host->children, child) disable_host(c, id, h); pfe_sync(); } return (0); } int enable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host) { struct host *h; if (host == NULL) { if (id->id == EMPTY_ID) host = host_findbyname(env, id->name); else host = host_find(env, id->id); if (host == NULL || host->conf.parentid) return (-1); } id->id = host->conf.id; if (!(host->flags & F_DISABLE)) return (0); host->up = HOST_UNKNOWN; host->flags &= ~(F_DISABLE); host->flags &= ~(F_DEL); host->flags &= ~(F_ADD); proc_compose(env->sc_ps, PROC_HCE, IMSG_HOST_ENABLE, &host->conf.id, sizeof (host->conf.id)); /* Forward to relay engine(s) */ proc_compose(env->sc_ps, PROC_RELAY, IMSG_HOST_ENABLE, &host->conf.id, sizeof(host->conf.id)); log_debug("%s: host %d", __func__, host->conf.id); if (!host->conf.parentid) { /* Enable all children */ SLIST_FOREACH(h, &host->children, child) enable_host(c, id, h); pfe_sync(); } return (0); } void pfe_sync(void) { struct rdr *rdr; struct table *active; struct table *table; struct ctl_id id; struct imsg imsg; struct ctl_demote demote; struct router *rt; bzero(&id, sizeof(id)); bzero(&imsg, sizeof(imsg)); TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { rdr->conf.flags &= ~(F_BACKUP); rdr->conf.flags &= ~(F_DOWN); if (rdr->conf.flags & F_DISABLE || (rdr->table->up == 0 && rdr->backup->up == 0)) { rdr->conf.flags |= F_DOWN; active = NULL; } else if (rdr->table->up == 0 && rdr->backup->up > 0) { rdr->conf.flags |= F_BACKUP; active = rdr->backup; active->conf.flags |= rdr->table->conf.flags & F_CHANGED; active->conf.flags |= rdr->backup->conf.flags & F_CHANGED; } else active = rdr->table; if (active != NULL && active->conf.flags & F_CHANGED) { id.id = active->conf.id; imsg.hdr.type = IMSG_CTL_TABLE_CHANGED; imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; imsg.data = &id; sync_table(env, rdr, active); control_imsg_forward(env->sc_ps, &imsg); } if (rdr->conf.flags & F_DOWN) { if (rdr->conf.flags & F_ACTIVE_RULESET) { flush_table(env, rdr); log_debug("%s: disabling ruleset", __func__); rdr->conf.flags &= ~(F_ACTIVE_RULESET); id.id = rdr->conf.id; imsg.hdr.type = IMSG_CTL_PULL_RULESET; imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; imsg.data = &id; sync_ruleset(env, rdr, 0); control_imsg_forward(env->sc_ps, &imsg); } } else if (!(rdr->conf.flags & F_ACTIVE_RULESET)) { log_debug("%s: enabling ruleset", __func__); rdr->conf.flags |= F_ACTIVE_RULESET; id.id = rdr->conf.id; imsg.hdr.type = IMSG_CTL_PUSH_RULESET; imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; imsg.data = &id; sync_ruleset(env, rdr, 1); control_imsg_forward(env->sc_ps, &imsg); } } TAILQ_FOREACH(rt, env->sc_rts, rt_entry) { rt->rt_conf.flags &= ~(F_BACKUP); rt->rt_conf.flags &= ~(F_DOWN); if ((rt->rt_gwtable->conf.flags & F_CHANGED)) sync_routes(env, rt); } TAILQ_FOREACH(table, env->sc_tables, entry) { if (table->conf.check == CHECK_NOCHECK) continue; /* * clean up change flag. */ table->conf.flags &= ~(F_CHANGED); /* * handle demotion. */ if ((table->conf.flags & F_DEMOTE) == 0) continue; demote.level = 0; if (table->up && table->conf.flags & F_DEMOTED) { demote.level = -1; table->conf.flags &= ~F_DEMOTED; } else if (!table->up && !(table->conf.flags & F_DEMOTED)) { demote.level = 1; table->conf.flags |= F_DEMOTED; } if (demote.level == 0) continue; log_debug("%s: demote %d table '%s' group '%s'", __func__, demote.level, table->conf.name, table->conf.demote_group); (void)strlcpy(demote.group, table->conf.demote_group, sizeof(demote.group)); proc_compose(env->sc_ps, PROC_PARENT, IMSG_DEMOTE, &demote, sizeof(demote)); } } void pfe_statistics(int fd, short events, void *arg) { struct rdr *rdr; struct ctl_stats *cur; struct timeval tv, tv_now; int resethour, resetday; u_long cnt; timerclear(&tv); getmonotime(&tv_now); TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { cnt = check_table(env, rdr, rdr->table); if (rdr->conf.backup_id != EMPTY_TABLE) cnt += check_table(env, rdr, rdr->backup); resethour = resetday = 0; cur = &rdr->stats; cur->last = cnt > cur->cnt ? cnt - cur->cnt : 0; cur->cnt = cnt; cur->tick++; cur->avg = (cur->last + cur->avg) / 2; cur->last_hour += cur->last; if ((cur->tick % (3600 / env->sc_conf.statinterval.tv_sec)) == 0) { cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2; resethour++; } cur->last_day += cur->last; if ((cur->tick % (86400 / env->sc_conf.statinterval.tv_sec)) == 0) { cur->avg_day = (cur->last_day + cur->avg_day) / 2; resethour++; } if (resethour) cur->last_hour = 0; if (resetday) cur->last_day = 0; rdr->stats.interval = env->sc_conf.statinterval.tv_sec; } /* Schedule statistics timer */ evtimer_set(&env->sc_statev, pfe_statistics, NULL); bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv)); evtimer_add(&env->sc_statev, &tv); }